webpack常用plugin和loader,以及打包优化

前言

其实整理下来,我发现webpack基础的plugin大部分都是围绕着文件的压缩,抽离等等进行处理。
loader则围绕着这些文件内容进行处理,比如把ts代码转化为js啊,把sass,less转换为css啊,或者把es6+的语法转换为低版本的语法啊等等。
然后由于项目越来越大,越来越复杂,用了越来越多的loader导致打包速度变慢了,然后又加了一些用于优化的插件。然后webpack配置越来越复杂,丫整个一坨,更新个版本,之前用的某些插件或者loader还有可能会莫名其妙报错。

一、通用环境

1、插件

1.1、HtmlWebpackPlugin

生成 html 文件,并将打包生成的js,和css文件,插入到该html中。

npm install --save-dev html-webpack-plugin

将 webpack 中entry配置的相关入口 chunk 和 extract-text-webpack-plugin抽取的 css 样式 插入到该插件提供的template或者templateContent配置项指定的内容基础上生成一个 html 文件,具体插入方式是将样式link插入到head元素中,script插入到head或者body中。

详细的 options参考 传送门

const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [
  new HtmlWebpackPlugin({
    
    
    filename: 'index.html',
    template: path.join(__dirname, '/index.html'),
    minify: {
    
    
      // 压缩HTML文件
      removeComments: true, // 移除HTML中的注释
      collapseWhitespace: true, // 删除空白符与换行符
      minifyCSS: true, // 压缩内联css
    },
    inject: true,
  }),
]

多页应用打包

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
    
    
  entry: {
    
    
    index: './src/index.js',
    login: './src/login.js',
  },
  output: {
    
    
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].[hash:6].js',
  },
  //...
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './public/index.html',
      filename: 'index.html', //打包后的文件名
    }),
    new HtmlWebpackPlugin({
    
    
      template: './public/login.html',
      filename: 'login.html', //打包后的文件名
    }),
  ],
}

如果需要配置多个 HtmlWebpackPlugin,那么 filename 字段不可缺省,否则默认生成的都是 index.html。
但是有个问题,index.html 和 login.html 会发现,都同时引入了 index…js 和 login.js,通常这不是我们想要的,我们希望 index.html 中只引入 index.js,login.html 只引入 login.js。

HtmlWebpackPlugin 提供了一个 chunks 的参数,可以接受一个数组,配置此参数仅会将数组中指定的 js 引入到 html 文件中。

module.exports = {
    
    
  //...
  plugins: [
    new HtmlWebpackPlugin({
    
    
      template: './public/index.html',
      filename: 'index.html', //打包后的文件名
      chunks: ['index'],
    }),
    new HtmlWebpackPlugin({
    
    
      template: './public/login.html',
      filename: 'login.html', //打包后的文件名
      chunks: ['login'],
    }),
  ],
}

1.2、CleanWebpackPlugin

npm install --save-dev clean-webpack-plugin

使用此插件,可以在每次打包之前,清理dist文件夹。

由于过去每次不同的打包都会生成相应的文件,导致我们的 /dist 文件夹相当杂乱。webpack 会生成文件,然后将这些文件放置在 /dist 文件夹中,但是 webpack 无法追踪到哪些文件是实际在项目中用到的。通常,在每次构建前清理 /dist 文件夹,是比较推荐的做法。

const {
    
     CleanWebpackPlugin } = require('clean-webpack-plugin')

plugins: [
  new CleanWebpackPlugin({
    
    
		cleanAfterEveryBuildPatterns: ['dist'],
  }),
]

1.3、ExtractTextPlugin

npm install --save-dev extract-text-webpack-plugin

将 css 单独抽离出来,成生文件,而非内联。

该插件的主要是为了抽离 css 样式,防止将样式打包在 js 中引起页面样式加载错乱的现象。

扫描二维码关注公众号,回复: 12463282 查看本文章
const ExtractTextPlugin = require('extract-text-webpack-plugin')

plugins: [
  // 将css分离到/dist文件夹下的css文件夹中的index.css
  new ExtractTextPlugin('css/index.css'),
]

在webpack4中有更好的替代者 mini-css-extract-plugin,具体可以参考本文目录生产环境1.1

1.4、PurifyCSSPlugin

npm i -D purifycss-webpack purify-css

去除未被使用的css代码

有时候我们 css 写得多或者写忘记了,会造成亢余代码,purifycss-webpack可以帮我们去除未被html页面使用的css。

const path = require('path');
const glob = require('glob');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PurifyCSSPlugin = require('purifycss-webpack');

module.exports = {
    
    
  entry: {
    
    ...},
  output: {
    
    ...},
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/,
        loader: ExtractTextPlugin.extract({
    
    
          fallbackLoader: 'style-loader',
          loader: 'css-loader'
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin('[name].[contenthash].css'),
    // Make sure this is after ExtractTextPlugin!
    new PurifyCSSPlugin({
    
    
      // Give paths to parse for rules. These should be absolute!
      paths: glob.sync(path.join(__dirname, 'app/*.html')),
    })
  ]
};

1.5、UglifyJsPlugin

npm install uglifyjs-webpack-plugin --save-dev

uglifyjs 用于压缩 js,支持文件缓存,和多线程压缩。

const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
    
    
  optimization: {
    
    
    minimizer: [
      new UglifyJsPlugin({
    
    
         test: /\.js(\?.*)?$/i,
        // 要处理的文件
         include: /\/includes/,
        // 被排除的文件
         exclude: /\/excludes/,
        // 判断哪些 chunk 可以被压缩(默认所有的 chunk 都会被压缩)。
        // 返回值为 true 则会被压缩,false 则不会被压缩。
         chunkFilter: (chunk) => {
    
    
        // `vendor` 块不压缩
           if (chunk.name === 'vendor') {
    
    
             return false;
           }
          return true;
      	 },
	      // 启用文件缓存。默认值为false。
	      // 默认的缓存目录路径:node_modules/.cache/uglifyjs-webpack-plugin。
	      // cache的值也可以设置为字符串类型,此时意为启用文件缓存并设置缓存目录路径。
	     // cache: 'path/to/cache'
	      cache: true,
	      // 启用多进程并行运行。默认为false, 默认并发运行次数:os.cups().length - 1
	      // 该值设置为字符串时,表示启用多进程并行运行,并设置并发运行的次数。
	      // parallel: 4
		  parallel: true,
		  // 使用源映射将错误信息位置映射到模块(这将会减慢编译速度)
	      sourceMap: true,
	      // 压缩选项配置,具体意思参考 https://github.com/mishoo/UglifyJS#minify-options
	      uglifyOptions: {
    
    
	          warnings: false,
	          parse: {
    
    },
	          compress: {
    
    },
	          mangle: true, // 注意 `mangle.properties` 的默认值是 `false`。
	          output: null,
	          toplevel: false,
	          nameCache: null,
	          ie8: false,
	          keep_fnames: false,
	        },
      }),
    ],
  },
};

还可以配置 extractComments 属性提取注释等,具体可以参考官方文档 传送门

1.6、CompressionPlugin

npm install compression-webpack-plugin --save-dev

可以把文件进行GZip压缩成更小的.gz文件,优化首屏加载时间。

注意事项:

  • 低版本浏览器兼容性,服务器可以设置一些忽略规则忽略为浏览器。
  • 媒体文件无需开启:图片、音乐和视频大多数都已压缩过了。

为什么可以优化首屏加载时间呢?基本原理:

  1. 浏览器请求资源文件时会自动带一个Accept-Encoding的请求头告诉服务器支持的压缩编码类型,服务器配置开启gzip选项。
  2. 接收客户端资源文件请求,查看请求头Content-encoding支持的压缩编码格式,如果是包含gzip,那么在每次响应资源请求之前进行gzip编码压缩后再响应返回资源文件(在响应头会带上Content-encoding: gzip)。
  3. 浏览器接收到响应后查看请求头是否带有Content-encoding:gzip,如果有进行对返回的资源文件进行解压缩然后再进行解析渲染。

由于每次返回请求需要压缩文件,增大了服务器cpu压力。但使用webpack提供的此插件,在打包后所有文件已经被压缩过了,服务器查找到有与源文件同名的.gz文件就会直接读取,不会主动压缩。所以降低cpu了负载,也就不存在增加服务器cpu压力的情况了。

const CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
    
    
	 plugins: [
	 // 更多配置信息可以参考链接 https://www.webpackjs.com/plugins/compression-webpack-plugin/
	  new CompressionPlugin({
    
    
	    // 匹配文件名
	    test: /\.js$|\.html$|\.css/, 
	    // 对超过10kb的数据进行压缩
	    threshold: 10240, 
	    // 是否删除原文件
	    deleteOriginalAssets: false, 
	  }),
	]
}

服务端启用Gzip压缩,参考 传送门

1.7、DefinePlugin

DefinePlugin是webpack模块自带的,不需要安装。

DefinePlugin 允许在 编译时 创建配置的全局常量,这在需要区分开发模式与生产模式进行不同的操作时,非常有用。
例如,如果想在开发构建中进行日志记录,而不在生产构建中进行,就可以定义一个全局常量去判断是否记录日志。

plugins: [
        new webpack.DefinePlugin({
    
    
			'process.env.NODE_ENV': JSON.stringify('dev'),
		}),
	],
// 在代码中可以直接使用
if (process.env.NODE_ENV !== 'production') {
    
    
	console.log('Looks like we are in development mode!');
}

1.8、ProvidePlugin

ProvidePlugin是webpack模块自带的,不需要安装。

自动加载模块,而不必import或require它们。

每当identifier在代码中被用到时,module都会自动加载,并用已加载identifier的导出内容填充module(或property,为了支持命名的导出内容)。
要导入ES2015模块的默认导出,必须指定module的默认属性。

// 默认模块解析路径为当前文件夹(./**)和node_modules。
new webpack.ProvidePlugin({
    
    
  identifier: 'module1',
  // 或者指定具体属性
  identifier: ['module1', 'property1'],
  // ...
});
// 或者指定完整路径
new webpack.ProvidePlugin({
    
    
  identifier: path.resolve(path.join(__dirname, 'src/module1'))
  // ...
});

什么意思呢?举个栗子:

new webpack.ProvidePlugin({
    
    
  $: 'jquery',
});

// 在任何代码中就可以直接使用,而无需引入
$('#item')

1.9、DllPlugin和DllReferencePlugin

建议直接去看这篇文章: 传送门。我刚开始看官方文档的时候是懵逼的,直到看了这篇文章,才搞懂。

在使用webpack进行打包时候,对于依赖的第三方库,比如vue,vuex等这些不会修改的依赖,我们可以让它和我们自己编写的代码分开打包,这样做的好处是每次更改我本地代码的文件的时候,webpack只需要打包我项目本身的文件代码,而不会再去编译第三方库,那么第三方库在第一次打包的时候只打包一次,以后只要我们不升级第三方包的时候,那么webpack就不会对这些库去打包,这样的可以快速的提高打包的速度。因此为了解决这个问题,DllPlugin 和 DllReferencePlugin插件就产生了。

DLLPlugin 这个插件是在一个额外独立的webpack设置中创建一个只有dll的bundle,也就是说我们在项目根目录下除了有webpack.config.js,还会新建一个webpack.dll.config.js文件。webpack.dll.config.js作用是把所有的第三方库依赖打包到一个bundle的dll文件里面,还会生成一个名为 manifest.json清单文件。
该manifest.json的作用是用来让 DllReferencePlugin 映射到相关的依赖上去的。

DllReferencePlugin 插件是在webpack.config.js中使用的,该插件的作用是把刚刚在webpack.dll.config.js中打包生成的dll文件引用到需要的预编译的依赖上来。什么意思呢?就是说在webpack.dll.config.js中打包后比如会生成 vendor.dll.js文件和vendor-manifest.json文件,vendor.dll.js文件包含所有的第三方库文件,vendor-manifest.json文件会包含所有库代码的一个索引,当在使用webpack.config.js文件打包DllReferencePlugin插件的时候,会使用该DllReferencePlugin插件读取vendor-manifest.json文件,看看是否有该第三方库。vendor-manifest.json文件就是有一个第三方库的一个映射而已。

所以说 第一次使用 webpack.dll.config.js 文件会对第三方库打包,打包完成后就不会再打包它了,然后每次运行 webpack.config.js文件的时候,都会打包项目中本身的文件代码,当需要使用第三方依赖的时候,会使用 DllReferencePlugin插件去读取第三方依赖库。所以说它的打包速度会得到一个很大的提升。

具体使用
首先需要在我们的项目根目录下创建一个 webpack.dll.config.js 文件。然后配置代码如下:

const path = require('path');
const DllPlugin = require('webpack/lib/DllPlugin');

module.exports = {
    
    
  // 入口文件
  entry: {
    
    
    // 项目中用到该两个依赖库文件
    jquery: ['jquery'],
    echarts: ['echarts']
  },
  // 输出文件
  output: {
    
    
    // 文件名称
    filename: '[name].dll.js', 
    // 将输出的文件放到dist目录下
    path: path.resolve(__dirname, 'dist'),

    /*
     存放相关的dll文件的全局变量名称,比如对于jquery来说的话就是 _dll_jquery, 在前面加 _dll
     是为了防止全局变量冲突。
    */
    library: '_dll_[name]'
  },
  plugins: [
    // 使用插件 DllPlugin
    new DllPlugin({
    
    
      /*
       该插件的name属性值需要和 output.library保存一致,该字段值,也就是输出的 manifest.json文件中name字段的值。
       比如在jquery.manifest文件中有 name: '_dll_jquery'
      */
      name: '_dll_[name]',

      /* 生成manifest文件输出的位置和文件名称 */
      path: path.join(__dirname, 'dist', '[name].manifest.json')
    })
  ]
};

DllPlugin 插件有三个配置项参数如下:

  • context(可选): manifest文件中请求的上下文,默认为该webpack文件上下文。
  • name: 公开的dll函数的名称,和 output.library保持一致。
  • path: manifest.json 生成文件的位置和文件名称。

下面我们继续看下 webpack.config.js 配置代码如下:

const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');
module.exports = {
    
    
  plugins: [
    // 告诉webpack使用了哪些第三方库代码
    new DllReferencePlugin({
    
    
      // jquery 映射到json文件上去
      manifest: require('./dist/jquery.manifest.json')
    }),
    new DllReferencePlugin({
    
    
      // echarts 映射到json文件上去
      manifest: require('./dist/echarts.manifest.json')
    })
  ]
}

DllReferencePlugin项的参数有如下:

  • context::(绝对路径) manifest (或者是内容属性)中请求的上下文
  • extensions:用于解析 dll bundle 中模块的扩展名 (仅在使用 ‘scope’ 时使用)。
  • manifest: manifest :包含 content 和 name 的对象,或者是一个字符串 —— 编译时用于加载 JSON manifest 的绝对路径
  • content:(可选):请求到模块id的映射(默认值为 manifest.content)
  • name(可选): dll暴露的地方的名称(默认值为manifest.name) scope: dll中内容的前缀。
  • scope (可选):dll 中内容的前缀
  • sourceType(可选): dll是如何暴露的libraryTarget。

执行构建代码

webpack --config webpack.dll.config.js

1.10、HappyPack

先放 传送门

npm install --save-dev happypack

HappyPack 能让 webpack 把任务分解给多个子进程去并发的执行,子进程处理完后再把结果发送给主进程。因此可以很大程度上优化打包速度。

要注意的是 HappyPack 对 file-loader、url-loader 支持的不友好,所以不建议对该 loader 使用。
另外当项目较小时,多线程打包反而会使打包速度变慢。

const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({
    
     size: os.cpus().length });

module.exports = {
    
    
  module: {
    
    
    rules:[
	  {
    
    
	    test: /\.js$/,
	    use: 'happypack/loader?id=jsx'
	  },
	
	  {
    
    
	    test: /\.less$/,
	    use: 'happypack/loader?id=styles'
	  },
	]
  },
plugins: [
  new HappyPack({
    
    
    id: 'jsx',
    threads: 4,
    loaders: [ 'babel-loader' ],
  //共享进程池
    threadPool: happyThreadPool,
    //允许 HappyPack 输出日志
    verbose: true,
  }),

  new HappyPack({
    
    
    id: 'styles',
    threads: 2,
    loaders: [ 'style-loader', 'css-loader', 'less-loader' ],
     //共享进程池
    threadPool: happyThreadPool,
    //允许 HappyPack 输出日志
    verbose: true,
  })
];
}
  • 在 Loader 配置中,所有文件的处理都交给了 happypack/loader 去处理,使用紧跟其后的 querystring?id=babel 去告诉 happypack/loader 去选择哪个 HappyPack 实例去处理文件。
  • 在 Plugin配置中,新增了两个 HappyPack 实例分别用于告诉 happypack/loader 去如何处理 .js 和 .css文件。选项中的 id 属性的值和上面 querystring 中的 id相对应,选项中的 loaders配置,就是处理该类型文件要使用的loader。

1.11、IgnorePlugin

IgnorePlugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去。

new webpack.IgnorePlugin({
    
    resourceRegExp, contextRegExp});
// 仅支持webpack4及之前版本, 在webpack5中不支持该写法
new webpack.IgnorePlugin(resourceRegExp, [contextRegExp]);

  • resourceRegExp 匹配(test)资源请求路径的正则表达式。
  • contextRegExp(可选)匹配(test)资源上下文(目录)的正则表达式。

1.12、splitChunks (原CommonsChunkPlugin)

抽取代码中公共模块的插件,简单来讲也就是把很多个项目代码中都引入了的模块抽离出来,形成一个单独的文件。这样能有效减少包文件大小

更详细的配置项参考 官方文档,这里要吐槽下,有些属性的意思文档写的也不全,完全理解不了。还得自行谷歌,后面有时间的话慢慢整理一下。 可以参考下这篇文章,有部分属性解释。

module.exports = {
    
    
  //...
  optimization: {
    
    
    splitChunks: {
    
    
      // 此属性有三个值 ,async,initial,all,
      // 反正一般用all,官方文档说这个最强大嘛,其他两个看不懂
      chunks: 'all',
      // 大于此值的才会被拆分,单位是字节
      minSize: 20000,
      minRemainingSize: 0,
      maxSize: 0,
      // 入口点的最大并行请求数,看不懂我吐了
      // 在有篇文章里写的大概意思是 被其他entry引用次数大于此值,默认1
      minChunks: 1,
      maxAsyncRequests: 30,
      maxInitialRequests: 30,
      automaticNameDelimiter: '~',
      enforceSizeThreshold: 50000,
      cacheGroups: {
    
    
        defaultVendors: {
    
    
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
    
    
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};

2、loader

更多 loader 参考 传送门

2.1、css-loader + style-loader

npm install --save-dev css-loader style-loader

css-loader主要是帮我们解析css文件内的css代码,将 CSS 转化成 CommonJS 模块,而style-loader则帮我们将css-loader解析后的内容挂载到html页面中。

具体的 options 配置,参考 css-loaderstyle-loader

module.exports = {
    
    
  module: {
    
    
    rules: [
      {
    
    
        test: /\.css$/i,
        use: [
 		  {
    
    
            loader: 'style-loader',
            options: {
    
     
           		...
            },
          },
          {
    
    
		    loader: "css-loader",
		    options: {
    
    
		       ...
		    },
   	   	 },
		],
      },
    ],
  },
};

2.2、file-loader

npm install --save-dev file-loader

解析项目中的文件

详细的options 配置参考 传送门

module.exports = {
    
    
  module: {
    
    
    rules: [
      {
    
    
        test: /\.(png|jpg|gif)$/,
        use: [
          {
    
    
            loader: 'file-loader',
             options: {
    
    
                // 如果不配置 默认为文件内容的 MD5 哈希值并会保留所引用资源的原始扩展名
			    name: '[path][name].[ext]'
			  }
          }
        ]
      }
    ]
  }
}
// 关于name的配置还可以这么写
{
    
    
  loader: 'file-loader',
  options: {
    
    
    name (file) {
    
    
      if (process.env.NODE_ENV === 'development') {
    
    
        return '[path][name].[ext]'
      }
      return '[hash].[ext]'
    }
  }
}

2.3、sass-loader

npm install sass-loader node-sass webpack --save-dev

将scss转化为css代码

更多options配置 传送门

// webpack.config.js
module.exports = {
    
    
  ...
  module: {
    
    
    rules: [{
    
    
      test: /\.scss$/,
      use: [{
    
    
          loader: "style-loader" // 将 JS 字符串生成为 style 节点
      }, {
    
    
          loader: "css-loader" //  将 CSS 转化成 CommonJS 模块
      }, {
    
    
          loader: "sass-loader" ,// 将 Sass 编译成 CSS
          options: {
    
    
          	...
          }
      }]
    }]
  }
};

通常,生产环境下比较推荐的做法是,使用 ExtractTextPlugin 将样式表抽离成专门的单独文件。这样,样式表将不再依赖于 JavaScript:

const ExtractTextPlugin = require("extract-text-webpack-plugin");

const extractSass = new ExtractTextPlugin({
    
    
    filename: "[name].[contenthash].css",
    disable: process.env.NODE_ENV === "development"
});

module.exports = {
    
    
    ...
    module: {
    
    
        rules: [{
    
    
            test: /\.scss$/,
            use: extractSass.extract({
    
    
                use: [{
    
    
                    loader: "css-loader"
                }, {
    
    
                    loader: "sass-loader"
                }],
                // 在开发环境使用 style-loader
                fallback: "style-loader"
            })
        }]
    },
    plugins: [
        extractSass
    ]
};

2.4、less-loader

直接参考 传送门,用法跟sass-loader差不多。

2.5、babel-loader

npm install --save-dev @babel/core @babel/cli @babel/preset-env
npm install --save @babel/polyfill

Babel是一个工具链,主要用于在旧的浏览器或环境中将ECMAScript 2015+代码转换为向后兼容版本的JavaScript代码。简单来说就是把高阶语法转换为浏览器支持的低阶语法。

具体用法参考链接:传送门,一般都是使用presets。起码我自己很少去配置。

module: {
    
    
  rules: [
    //...
    {
    
    
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel-loader',
      options: {
    
    
        presets: [
          '@babel/preset-env',
        ]
      }
    }
    //...
  ]
}

更详细的可以去babel官网看,知识点太多了,学不过来。

2.6、awesome-typescript-loader

npm install awesome-typescript-loader --save-dev

把 ts代码 转化为 js

这里就不写ts-loader了,相比之下awesome-typescript-loader更有优势。
另外Webpack 转译 Typescript 现有方案 可以参考 这篇文章

const {
    
     CheckerPlugin } = require('awesome-typescript-loader')

module.exports = {
    
    

  // Currently we need to add '.ts' to the resolve.extensions array.
  resolve: {
    
    
    extensions: ['.ts', '.tsx', '.js', '.jsx']
  },

  // Source maps support ('inline-source-map' also works)
  devtool: 'source-map',

  // Add the loader for .ts files.
  module: {
    
    
    rules: [
      {
    
    
        test: /\.tsx?$/,
        loader: 'awesome-typescript-loader'
      }
    ]
  },
  plugins: [
      new CheckerPlugin()
  ]
};

awesome-typescript-loader与 ts-loader的区别
awesome-typescript-loader 不需要安装额外的插件,可以通过内置的 CheckerPlugin 插件,把类型检查放在独立的进程中执行。

编译时间对比
如果都是用默认配置的话,awesome-typescript-loader 的速度相对快一些。
如果都设置了禁止类型检查的选项,ts-loader 的速度相对快一些。
如果都设置了禁止类型检查的选项并且将类型检查放到独立的进程中执行。

2.7、eslint-loader

npm install eslint-loader --save-dev

打包时通过 ESLint 检查 JavaScript 代码

这个可选可不选吧,因为可以在vscodd安装eslint插件,编辑器自带代码检查。当启用了eslint-loader之后,会影响打包速度。

module.exports = {
    
    
  // ...
  module: {
    
    
    rules: [
      {
    
    
        enforce: 'pre',
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'eslint-loader',
      },
      {
    
    
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
    ],
  },
  // ...
};

具体的options配置参考 传送门

二、开发环境

1、插件

1.1、webpack dev serve

npm install --save-dev webpack-dev-server

webpack-dev-server实际上相当于启用了一个express的Http服务器+调用webpack-dev-middleware。它的作用主要是用来伺服资源文件。这个Http服务器和client使用了websocket通讯协议,原始文件作出改动后,webpack-dev-server会用webpack实时的编译,再用webpack-dev-middleware将webpack编译后文件会输出到内存中。由于每次更新都会重新加载页面,这时就需要用到热更新了,具体热更新的用法在后面有写到。
如果需要对 webpack-dev-middleware 做更多配置,可以安装这个插件

npm install --save-dev express webpack-dev-middleware

详细用法参考 传送门

1.2、热更新 HotModuleReplacementPlugin

模块热更新插件。Hot-Module-Replacement 的热更新是依赖于 webpack-dev-server,后者是在打包文件改变时更新打包文件或者 reload 刷新整个页面,HRM 是只更新修改的部分。
详细 参考 传送门
HotModuleReplacementPlugin是webpack模块自带的,所以引入webpack后,在plugins配置项中直接使用即可。

const webpack = require('webpack')
devServer: {
    
    
		contentBase: './dist',
		hot: true,  // 开启热更新
		port: 8080,
		compress: true,
	},
	plugins: [
        new webpack.HotModuleReplacementPlugin(),
    ],

2、配置

2.1、devtool

此选项控制是否生成,以及如何生成 source map。

什么是source map?
当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js, b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含一个错误,那么堆栈跟踪就会简单地指向到 bundle.js。这并通常没有太多帮助,因为你可能需要准确地知道错误来自于哪个源文件。
为了更容易地追踪错误和警告,JavaScript 提供了 source map 功能,将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。

不同的值会明显影响到构建(build)和重新构建(rebuild)的速度。

在这里插入图片描述

二、生产环境

1、插件

1.1、MiniCssExtractPlugin

将 CSS 提取为独立的文件的插件,对每个包含 css 的 js 文件都会创建一个 CSS 文件

支持按需加载 css 和 sourceMap。只能用在 webpack4 中。
对比另一个插件 extract-text-webpack-plugin 有以下特点:

  • 异步加载
  • 不重复编译,性能更好
  • 更容易使用
  • 只针对 CSS
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
    
    
  module: {
    
    
    rules: [
      {
    
    
        test: /\.(le|c)ss$/,
        use: [
          {
    
    
            loader: MiniCssExtractPlugin.loader,
            options: {
    
    
              publicPath: '../',
            },
          },
          'css-loader',
          'postcss-loader',
          'less-loader',
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
    
    
      filename: 'css/[name].[contenthash:8].css',
      chunkFilename: 'css/[id].[contenthash:8].css',
    }),
  ],
}

此插件不能跟style-loader一起使用。并且在开发环境中,更推荐使用style-loader,官方文档中建议使用如下写法

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';

const plugins = [];
if (!devMode) {
    
    
  // enable in production only
  plugins.push(new MiniCssExtractPlugin());
}

module.exports = {
    
    
  plugins,
  module: {
    
    
    rules: [
      {
    
    
        test: /\.(sa|sc|c)ss$/,
        use: [
          devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },
    ],
  },
};

1.2、CssMinimizerPlugin

npm install css-minimizer-webpack-plugin --save-dev

该插件用于压缩css文件,并且可以使用缓存,以及开启多线程压缩

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
    
    
  module: {
    
    
    loaders: [
      {
    
    
        test: /.s?css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
      },
    ],
  },
  optimization: {
    
    
    minimize: true, // 设为true,表示在开发环境下也启用压缩
    minimizer: [
      new CssMinimizerPlugin({
    
    
       // 设为true,则默认开启的线程数为 os.cpus().length - 1 ,也可以设为任意数量,如 parallel: 4
        parallel: true, 
        // 开启源地图映射,方便开发时调试代码
        sourceMap: true, 
        // 设为true,则开始缓存,也可直接设为字符串类型的路径 如'path/to/cache',在webpack5中的配置发生了变化,可以参考
        // https://webpack.js.org/configuration/other-options/#cache
        cache: true, 
    ],
  },
};

使用此插件要注意,如果在开发环境使用此插件,则 loader 内不推荐使用 MiniCssExtractPlugin.loader,可以用style-loader。

三、其他问题

1、切换环境

先安装用于合并config配置的工具 webpack-merge

npm install --save-dev webpack-merge

然后新建

  • webpack.common.js
  • webpack.dev.js
  • webpack.prod.js

webpack.common.js

 const path = require('path');
 const CleanWebpackPlugin = require('clean-webpack-plugin');
 const HtmlWebpackPlugin = require('html-webpack-plugin');

 module.exports = {
    
    
   entry: {
    
    
     app: './src/index.js'
   },
   plugins: [
     new CleanWebpackPlugin(['dist']),
     new HtmlWebpackPlugin({
    
    
       title: 'Production'
     })
   ],
   output: {
    
    
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist')
   }
 };

webpack.dev.js


 const merge = require('webpack-merge');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
    
    
   devtool: 'inline-source-map',
   devServer: {
    
    
     contentBase: './dist'
   }
 });

webpack.prod.js

 const merge = require('webpack-merge');
 const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
 const common = require('./webpack.common.js');

 module.exports = merge(common, {
    
    
   plugins: [
     new UglifyJSPlugin()
   ]
 });

最后在package.json中更新script脚本

    "scripts": {
    
    
      "start": "webpack-dev-server --open --config webpack.dev.js",
      "build": "webpack --config webpack.prod.js"
    },

2、webpack打包优化

直观的查看打包体积,部分参考自:传送门
要想对打包体积进行优化,首先得找到体积大的模块,在这里我们可以使用webpack插件webpack-bundle-analyzer来查看整个项目的体积结构对比。它是以treemap的形式展现出来,很形象直观,还有一些具体的交互形式。既可以查看你项目中用到的所有依赖,也可以直观看到各个模块体积在整个项目中的占比。
在这里插入图片描述
先安装

npm install webpack-bundle-analyzer --save-dev

在config中配置

plugins: [
new BundleAnalyzerPlugin()
]

2.1、noParse:包名

webpack打包的时候,有时不需要解析某些模块的依赖(这些模块并没有依赖别的模块,或者根本就没有模块化),我们可以直接加上这个参数,直接跳过这个模块的解析,如jquery 、lodash等。

在module中设置noParse属性,值是一个正则表达式

//webpack.config.js
module.exports = {
    
    
    //...
    module: {
    
    
        noParse: /jquery|lodash/
    }
}

2.2、在loader配置中,使用exclude,和include

因为webpack会引入所有的包,但是有时我们没用到

	rules: [
			{
    
    
                test: /\.css$/,
                exclude:/node_modules/,
				use: ['style-loader', 'css-loader'],
			},
			{
    
    
				test: /\.(png|svg|jpg|gif|jpeg)$/,
				use: ['file-loader'],
			},
		],

2.3、IgnorePlugin移除不必要的模块

IgnorePlugin用于忽略某些特定的模块,让 webpack 不把这些指定的模块打包进去
参考通用插件部分的1.11。

2.4、happypack 开始多进程打包

给loader开启多个子进程去处理。具体使用参考通用插件部分的1.10

2.5、抽离公共代码

简单来讲也就是把很多个项目代码中都引入了的模块抽离出来,形成一个单独的文件。这样能有效减少包文件大小。参考 本文 通用插件部分1.12 splitChunks

2.6、模块化引入

仔细想想,我们在使用这类工具库的时候往往只使用到了其中的很少的一部分功能,但却把整个库都引入了。因此这里也可以进一步优化,只引用需要的部分。

import {
    
    chain, cloneDeep} from 'lodash';
// 可以改写为
import chain from 'lodash/chain';
import cloneDeep from 'lodash/cloneDeep';

2.7、通过CDN引用

对于一些必要的库,但又无法对该库进行更好的体积优化的话,可以尝试通过外部引入的方式来减小打包文件的体积。采用该方法只需要在cdn站点找到需要引用的库的外部链接,以及对webpack进行简单配置即可。

// 在html中添加script引用
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

// 这里externals的key指的是使用时需要require的包名,value指的是该库通过script引入后在全局注册的变量名
// 看不懂的可以参考 https://webpack.docschina.org/configuration/externals/#root
module.exports = {
    
    
  //...
  externals: {
    
    
    jquery: 'jQuery'
  }
};
// 使用方法
import $ from 'jquery';

$('.my-element').animate(/* ... */);

2.8、通过DLLPlugin 和 DLLReferencePlugin 拆分依赖的第三方库和项目代码

参考 本文 通用插件部分1.9 DllPlugin和DllReferencePlugin

2.9、开启Gzip压缩

参考 本文 通用插件部分1.6 CompressionPlugin

2.10、压缩混淆代码

可以去掉不必要的空格、注释、console信息等,有效的减小代码体积。参考 本文 通用插件部分1.5 UglifyJsPlugin

写在最后

部分插件整理自: 传送门,其他大部分来源于官方网站,还有一些来源于在某个地方意外看到的文章,找不到地址了。
我只想说webpack 配起来是真要命啊。有空了得去github找找有没有大佬配好的拆箱即用的那种配置,或者自己去尝试配一套。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41885871/article/details/109305910