DefinePlugin

定义全局变量。

  1. const webpack = require('webpack')
  2. module.exports = {
  3. mode: 'none',
  4. entry: './src/main.js',
  5. output: {
  6. filename: 'bundle.js'
  7. },
  8. plugins: [
  9. new webpack.DefinePlugin({
  10. // 值要求的是一个代码片段
  11. API_BASE_URL: JSON.stringify('https://api.example.com')
  12. })
  13. ]
  14. }

Tree Shaking

production 模式会自动启用。其他模式需要手动开启。

  1. module.exports = {
  2. mode: 'none',
  3. optimization: {
  4. usedExports: true, // 标记未使用对象
  5. minimize: true, // 压缩代码并去掉未引用对象
  6. }
  7. }

Tree Shaking 与 Babel

新版本的 babel-loader 并不会导致 Tree Shaking 失效。Tree Shaking 能使用的前提是使用 ESM ,如果 babel-loader 开启了 ESM 转换到 CommonJS,Tree Shaking 就会失效。

  1. module: {
  2. rules: [
  3. {
  4. test: /\.js$/,
  5. use: {
  6. loader: 'babel-loader',
  7. options: {
  8. presets: [['@babel/preset-env', { modules: false }]] // 不转换,Tree Shaking 生效
  9. // presets: ['@babel/preset-env'] // 不转换,Tree Shaking 生效
  10. // presets: [['@babel/preset-env', { modules: 'commonjs' }]] // 转换,Tree Shaking 失效
  11. }
  12. }
  13. }
  14. ]
  15. },
  16. optimization: {
  17. usedExports: true,
  18. // concatenateModules: true,
  19. minimize: true,
  20. }

合并模块

concatenateModules 属性会尽量把多个模块合并成一个函数。

  1. module.exports = {
  2. mode: 'none',
  3. optimization: {
  4. usedExports: true, // 标记未使用对象
  5. concatenateModules: true,
  6. minimize: true, // 压缩代码并去掉未引用对象
  7. }
  8. }

SideEffects

Webpack 4 开始支持的特性,一般用于标记 npm 包是否有副作用。

  1. optimization: {
  2. sideEffects: true, // 开启 sideEffects
  3. }

package.json 中 可以标记是否有副作用。sideEffects 设置为 false 表示没有副作用。

  1. "sideEffects": false
  1. 或者设置成一个数组,存放有副作用的文件的路径。
  1. "sideEffects": [
  2. "./src/extend.js",
  3. "*.css"
  4. ]

代码分割

如果是一个大型项目,打包到一个文件,可能导致打包过后的文件体积很大,大多数情况下并不需要把全部模块一并加载,所以可以实现代码的分包/分割。

多入口打包

一般用于多页应用。

示例项目结构:
image.png
配置文件:

  1. // webpack.config.js
  2. const { CleanWebpackPlugin } = require('clean-webpack-plugin')
  3. const HtmlWebpackPlugin = require('html-webpack-plugin')
  4. module.exports = {
  5. mode: 'none',
  6. entry: {
  7. index: './src/index.js',
  8. album: './src/album.js'
  9. }, // 指定打包的文件
  10. output: {
  11. filename: '[name].bundle.js' // [name] 可以自动替换成 entry 的名字
  12. },
  13. module: {
  14. rules: [
  15. {
  16. test: /\.css$/,
  17. use: [
  18. 'style-loader',
  19. 'css-loader'
  20. ]
  21. }
  22. ]
  23. },
  24. plugins: [
  25. new CleanWebpackPlugin(),
  26. new HtmlWebpackPlugin({
  27. title: 'Multi Entry',
  28. template: './src/index.html',
  29. filename: 'index.html',
  30. chunks: ['index'] // 指定入口文件,如果不配置,那么 html 文件 会把所有打包结果加载进来
  31. }),
  32. new HtmlWebpackPlugin({
  33. title: 'Multi Entry',
  34. template: './src/album.html',
  35. filename: 'album.html',
  36. chunks: ['album']
  37. })
  38. ]
  39. }

打包结果:
image.png

提取公共模块

在多个模块中,可能会导入相同的 CSS 或者 JS 模块,这样打包时就有重复的部分了,应该提取出来,整合到一个打包结果。要实现这个功能,配置 optimization 中的 splitChunks 属性即可。

  1. // webpack.config.js
  2. optimization: {
  3. splitChunks: {
  4. // 自动提取所有公共模块到单独 bundle
  5. chunks: 'all'
  6. }
  7. },

动态导入

动态导入可以实现按需加载,动态导入的模块会自动被分包。

示例项目结构:
image.png

index.js:

  1. // import posts from './posts/posts'
  2. // import album from './album/album'
  3. const render = () => {
  4. const hash = window.location.hash || '#posts'
  5. const mainElement = document.querySelector('.main')
  6. mainElement.innerHTML = ''
  7. // 动态导入核心代码
  8. if (hash === '#posts') {
  9. // mainElement.appendChild(posts())
  10. import('./posts/posts').then(({ default: posts }) => {
  11. mainElement.appendChild(posts())
  12. })
  13. } else if (hash === '#album') {
  14. // mainElement.appendChild(album())
  15. import('./album/album').then(({ default: album }) => {
  16. mainElement.appendChild(album())
  17. })
  18. }
  19. }
  20. render()
  21. window.addEventListener('hashchange', render)

打包结果:
image.png

魔法注释

上述动态导入的打包结果,默认名称是序号,如果需要更改,可以使用魔法注释。webpackChunkName 设置成一样,那么会合并到一个文件。

  1. // 动态导入核心代码
  2. if (hash === '#posts') {
  3. // mainElement.appendChild(posts())
  4. import(/* webpackChunkName: 'posts' */'./posts/posts').then(({ default: posts }) => {
  5. mainElement.appendChild(posts())
  6. })
  7. } else if (hash === '#album') {
  8. // mainElement.appendChild(album())
  9. import(/* webpackChunkName: 'album' */'./album/album').then(({ default: album }) => {
  10. mainElement.appendChild(album())
  11. })
  12. }

打包结果:
image.png

CSS 按需加载

mini-css-extract-plugin 可以提取 CSS 部分,从而实现按需加载。

  1. const { CleanWebpackPlugin } = require('clean-webpack-plugin')
  2. const HtmlWebpackPlugin = require('html-webpack-plugin')
  3. const MiniCssExtractPlugin = require('mini-css-extract-plugin')
  4. module.exports = {
  5. mode: 'none',
  6. entry: {
  7. main: './src/index.js'
  8. },
  9. output: {
  10. filename: '[name].bundle.js'
  11. },
  12. module: {
  13. rules: [
  14. {
  15. test: /\.css$/,
  16. use: [
  17. MiniCssExtractPlugin.loader,
  18. 'css-loader'
  19. ]
  20. }
  21. ]
  22. },
  23. plugins: [
  24. new CleanWebpackPlugin(),
  25. new HtmlWebpackPlugin({
  26. title: 'Dynamic import',
  27. template: './src/index.html',
  28. filename: 'index.html'
  29. }),
  30. new MiniCssExtractPlugin()
  31. ]
  32. }

打包结果:
image.png

压缩打包后的 CSS 文件

optimize-css-assets-webpack-plugin 插件可以将 mini-css-extract-plugin 提取出来的 CSS 文件进一步压缩。这种压缩类的插件,一般不配置在 plugins 数组中,而是 optimization 中的 minimizer 数组。minimizer 在生产模式下会自动开启。但是如果配置了 minimizer 数组,那么 Webpack 会认为开发者要使用自定义的压缩插件,这个时候需要再手动配置压缩 JS 的插件 terser-webpack-plugin。

  1. const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
  2. const TerserWebpackPlugin = require('terser-webpack-plugin')
  3. optimization: {
  4. minimizer: [
  5. new OptimizeCssAssetsWebpackPlugin(),
  6. new TerserWebpackPlugin(),
  7. ]
  8. },

输出文件名 Hash

如果打包结果文件以特定规则命名,那么部署后会导致缓存的问题,所以把文件名设置为文件的 hash 值不失为一种好办法。Webpack 支持3种哈希: hash、chunkhash、contenthash。

  • hash

项目级别的哈希,只要项目中任意文件的内容发生改变,整个项目的哈希都会改变。且所有文件都会使用相同的哈希。
image.png

  • chunkhash

同一路的打包使用同一个哈希。
image.png

  • contenthash

文件级别的哈希,当文件改变,只会改变相关的文件。contenthash 后面可以指定哈希长度。
image.png

  1. const { CleanWebpackPlugin } = require('clean-webpack-plugin')
  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 TerserWebpackPlugin = require('terser-webpack-plugin')
  6. module.exports = {
  7. mode: 'none',
  8. entry: {
  9. main: './src/index.js'
  10. },
  11. output: {
  12. filename: '[name]-[contenthash:8].bundle.js'
  13. },
  14. optimization: {
  15. minimizer: [
  16. new TerserWebpackPlugin(),
  17. new OptimizeCssAssetsWebpackPlugin()
  18. ]
  19. },
  20. module: {
  21. rules: [
  22. {
  23. test: /\.css$/,
  24. use: [
  25. // 'style-loader', // 将样式通过 style 标签注入
  26. MiniCssExtractPlugin.loader,
  27. 'css-loader'
  28. ]
  29. }
  30. ]
  31. },
  32. plugins: [
  33. new CleanWebpackPlugin(),
  34. new HtmlWebpackPlugin({
  35. title: 'Dynamic import',
  36. template: './src/index.html',
  37. filename: 'index.html'
  38. }),
  39. new MiniCssExtractPlugin({
  40. filename: '[name]-[contenthash:8].bundle.css'
  41. })
  42. ]
  43. }