提取CSS

因为CSS的下载和JS可以并行,当一个HTML文件很大的时候,我们可以把CSS单独提取出来加载

npm install mini-css-extract-plugin —save-dev

  1. const path = require('path');
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3. +const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  4. module.exports = {
  5. mode: 'development',
  6. devtool: false,
  7. entry: './src/index.js',
  8. output: {
  9. path: path.resolve(__dirname, 'dist'),
  10. filename: '[name].js',
  11. + publicPath: '/'
  12. },
  13. module: {
  14. rules: [
  15. { test: /\.txt$/, use: 'raw-loader' },
  16. + { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'] },
  17. + { test: /\.less$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'] },
  18. + { test: /\.scss$/, use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'] },
  19. {
  20. test: /\.(jpg|png|gif|bmp|svg)$/,
  21. type:'asset/resource',
  22. generator:{
  23. filename:'images/[hash][ext]'
  24. }
  25. }
  26. ]
  27. },
  28. plugins: [
  29. new HtmlWebpackPlugin({ template: './src/index.html' }),
  30. + new MiniCssExtractPlugin({
  31. + filename: '[name].css'
  32. + })
  33. ]
  34. };

可以在调用MiniCssExtractPlugin的时候指定将css放在哪个目录下:

  1. + new MiniCssExtractPlugin({
  2. + filename: 'style/[name].css'
  3. + })

图片优化

webpack5以后,对于文件的处理,使用type: ‘asset/resource’,而不再使用url-loader、file-loader

  1. module: {
  2. rules: [
  3. {
  4. test: /\.(jpg|png|gif|bmp|svg)$/,
  5. type:'asset/resource',
  6. generator:{
  7. filename:'images/[hash][ext]'
  8. }
  9. }
  1. module: {
  2. rules: [
  3. {
  4. test: /\.(jpg|png|gif|bmp|svg)$/,
  5. type:'asset', //必定会输出一个文件
  6. parser: {
  7. //根据这个条件做选择,如果小于maxSize的话就变成base64字符串,如果大于的就拷贝文件并返回新的地址
  8. dataUrlCondition: {
  9. maxSize: 4 * 1024
  10. }
  11. },
  12. generator:{
  13. filename:'images/[hash][ext]'
  14. }
  15. }

压缩HTML、css、js

js压缩:之前有UglifyJsPlugin等插件,但UglifyJsPlugin不支持ES6,因此现在用TerserPlugin更多
css压缩:OptimizeCssAssetsWebpackPlugin,可能会和webpack5不兼容,因此需要改用CssMinimizerPlugin
HTML压缩:HtmlWebpackPlugin里直接配置
图片不推荐用webpack压缩

  1. const path = require('path');
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3. const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  4. +const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
  5. +const TerserPlugin = require('terser-webpack-plugin');
  6. module.exports = {
  7. + mode: 'none',
  8. devtool: false,
  9. entry: './src/index.js',
  10. + optimization: {
  11. + minimize: true,
  12. + minimizer: [
  13. + new TerserPlugin(),
  14. + ],
  15. + },
  16. output: {
  17. path: path.resolve(__dirname, 'dist'),
  18. filename: '[name].js',
  19. publicPath: '/',
  20. },
  21. devServer: {
  22. contentBase: path.resolve(__dirname, 'dist'),
  23. compress: true,
  24. port: 8080,
  25. open: true,
  26. },
  27. module: {
  28. rules: [
  29. {
  30. test: /\.jsx?$/,
  31. loader: 'eslint-loader',
  32. enforce: 'pre',
  33. options: { fix: true },
  34. exclude: /node_modules/,
  35. },
  36. // ...
  37. {
  38. test: /\.(jpg|png|gif|bmp|svg)$/,
  39. type:'asset/resource',
  40. generator:{
  41. filename:'images/[hash][ext]'
  42. }
  43. },
  44. {
  45. test: /\.html$/,
  46. loader: 'html-loader',
  47. },
  48. ],
  49. },
  50. plugins: [
  51. new HtmlWebpackPlugin({
  52. template: './src/index.html',
  53. + minify: {
  54. + collapseWhitespace: true,
  55. + removeComments: true
  56. }
  57. }),
  58. new MiniCssExtractPlugin({
  59. filename: 'css/[name].css',
  60. }),
  61. + new OptimizeCssAssetsWebpackPlugin(),
  62. ],
  63. };

clean-webpack-plugin可以每次在执行build之前将原来打包的dist目录清除掉

purgecss-webpack-plugin

  • 可以去除未使用的 css,一般与 glob、glob-all 配合使用
  • 必须和mini-css-extract-plugin配合使用
  • paths路径是绝对路径

npm i purgecss-webpack-plugin mini-css-extract-plugin css-loader glob -D

  1. const path = require("path");
  2. +const glob = require("glob");
  3. +const PurgecssPlugin = require("purgecss-webpack-plugin");
  4. +const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  5. const PATHS = {
  6. src: path.join(__dirname, 'src')
  7. }
  8. module.exports = {
  9. mode: "development",
  10. entry: "./src/index.js",
  11. module: {
  12. rules: [
  13. {
  14. test: /\.js/,
  15. include: path.resolve(__dirname, "src"),
  16. use: [
  17. {
  18. loader: "babel-loader",
  19. options: {
  20. presets: ["@babel/preset-env", "@babel/preset-react"],
  21. },
  22. },
  23. ],
  24. },
  25. + {
  26. + test: /\.css$/,
  27. + include: path.resolve(__dirname, "src"),
  28. + exclude: /node_modules/,
  29. + use: [
  30. + {
  31. + loader: MiniCssExtractPlugin.loader,
  32. + },
  33. + "css-loader",
  34. + ],
  35. + },
  36. ],
  37. },
  38. plugins: [
  39. + new MiniCssExtractPlugin({
  40. + filename: "[name].css",
  41. + }),
  42. + new PurgecssPlugin({
  43. + paths: glob.sync(`${PATHS.src}/**/*`, { nodir: true }),
  44. + })
  45. ],
  46. };

CDN

  • HTML放在自己的服务器上,不缓存,关闭服务器缓存,每次访问服务器都可以获取最新的资源

  • 里面的静态文件js css image都指向CDN的地址

  • js css image都放在CDN上,并且文件名带上hash值,hash一般是结合CDN缓存来使用,通过webpack构建之后,生成对应文件名自动带上对应的MD5值。如果文件内容改变的话,那么对应文件哈希值也会改变,对应的HTML引用的URL地址也会改变,触发CDN服务器从源服务器上拉取对应数据,进而更新本地缓存。

  • 为了可以并行加载,需要把不同类型的文件和不同的文件放在不同的CDN服务器上

  • 为了避免同一时间对同一个域名请求数并发的限制,不同的资源放在不同的域名服务器进行并行加载,但
    多个域名后会增加域名解析时间

  • dns-prefetch DNS预解析,可以通过在 HTML HEAD 标签中 加入去预解析域名,以降低域名解析带来的延迟

三种hash值
- hash:代表整个项目,每次webpack构建时生成一个唯一的hash值,即使css没动过,只动了js,打包后这个hash依然会变,同样的,公共代码vendor虽然没有改变,但它的hash也会变
vendor.4305adaee27b2a3244e5.js
main.4305adaee27b2a3244e5.js
style/main.4305adaee27b2a3244e5.css

  • chunkhash
    每个入口都有自己的chunkhash
    如果本入口对应的文件发生改变,chunkhash会改变,如果没有改变,chunkhash会保持不变

但是对于css来说,我们通常都是在js文件中通过import ‘./index.css’这种方式引入的,如果我们改了js文件,css文件的chunkhash也会跟着改,这时就要用到contenthash:

  1. new MiniCssExtractPlugin({
  2. filename: 'style/[name].[contenthash].css'
  3. }),

treeshaking

webpack4的tree-shaking原理是找import进来的变量在模块中是否出现过
webpack5可以进行各作用域之间的关系进行优化

tree-shaking 必须是esm模块规范才会生效

ModuleConcatenationPlugin

hello.js:

  1. export default 'hello';

entry.js:

  1. import hello from './hello';
  2. console.log(hello);

hello模块只导出了一个常量,然后在entry中直接被引入,经过ModuleConcatenationPlugin处理后,hello.js并不会单独作为一个模块放到module中,而是直接将’hello’,放到entry模块里:

  1. (() => {
  2. "use strict";
  3. var exports = {};
  4. /*
  5. ****./src/entry1.js + 1 modules ****
  6. */
  7. ;// CONCATATION_MODULE: ./src/hello.js
  8. const hello = ("hello");
  9. ;// CONCATATION_MODULE: ./src/index.js
  10. console.log(hello);
  11. })()

babel-loader增加缓存

  1. {
  2. test: /\.js$/,
  3. exclude: /node_modules/,
  4. use: [
  5. {
  6. loader: 'thread-loader',
  7. options: {
  8. workers: 3
  9. }
  10. },
  11. {
  12. loader: 'babel-loader',
  13. options: {//babel编译 后把结果缓存起来,下次编译 的时候可以复用上次的结果
  14. cacheDirectory: true
  15. }
  16. }
  17. ]
  18. },

babel-loader自带缓存,所以可以通过配置参数来实现,但有些loader不带缓存,就需要配置额外的cache-loader来实现
假设babel-loader没有缓存,我们就可以进行如下配置:
npm install cache-loader -D

  1. {
  2. test: /\.js$/,
  3. exclude: /node_modules/,
  4. use: [
  5. {
  6. loader: 'cache-loader'
  7. },
  8. {
  9. loader: 'babel-loader',
  10. options: {//babel编译 后把结果缓存起来,下次编译 的时候可以复用上次的结果
  11. cacheDirectory: true
  12. }
  13. }
  14. ]
  15. },

但在webpack5中,已经不需要这个loader了,直接写在webpack配置里即可:

  1. module.exports = {
  2. mode: 'development',
  3. devtool: false,
  4. entry: './src/index.js',
  5. cache: {
  6. type: 'filesystem', // memory|filesystem
  7. cacheDirectory: path.resolve(__dirname, 'node_modules/.cache/webpack')
  8. }

可以看到我们配置的cache.type是filesystem,即在磁盘持久缓存
这个缓存的配置会缓存生成的webpack模块和chunk,来改善构建速度
webpack5中默认开启cache.type为’memory’的缓存

moduleId和chunkId的优化

module:每一个文件都可以看成是一个module
chunk:webpack打包最终生成的代码块,代码块会生成文件,一个文件对应一个chunk
在webpack5以前,没有从entry打包的chunk文件,都会以1 2 3的文件命名方式输出:
例如,下面这些以import动态加载进来的模块:
index.js:

import('./one')
import('./two')
import('./three')

这样打包后会生成chunk.0.js chunk.1.js chunk.2.js 3个包
如果在某次更改代码时,我们去掉了import(‘./two’)
那会生成chunk.0.js chunk.1.js两个包,而这一次生成的chunk.1.js和上一次的chunk.1.js是不一样的,因此,我们删除掉的import(‘./two’),可能会导致缓存失败
在webpack5中,做了一些优化,这些模块名默认会变成以路径区分:
例如:src_chunks_one_js.main

我们也可以手动配置这个名字的生成规则

这个名字生成的规则是可以在optimize里面配置的:

module.exports = {
    mode: 'development',
    devtool: false,
    entry: './src/index.js',
    optimization: {
        moduleIds: 'named', // 
        chunkIds: 'named',

image.png
其中deterministic在生产环境中用的较多

sideEffect副作用

package.json中可以配置sideEffect的值:

{
  "name": "9.optimize",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "sideEffects": [
    "*.css"
  ],

配置值为[“*.css”],代表不干掉css文件