生产前必须要了解的一些webpack配置

883 阅读7分钟

本主题主要是针对上线前,webpack应该做哪些优化处理。

在主要介绍之前先介绍一下基本的知识点,下面的注意点是我遇到的一些问题总结。

一,配置webpack命令

官网给出了全面的配置命令

此处使用文件配置

使用配置文件

npx webpack [--config webpack.config.js]

配置文件中的相关选项,请参阅配置

二,基础的配置文件

给出基础的配置文件,可以实现打包,后续在对优化进行梳理

const path = require('path')
// 使用全局定义
const { DefinePlugin } = require('webpack')
// 清空打包出来的dist
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
// 使用html模板
const HtmlWebpackPlugin = require('html-webpack-plugin')


module.exports = {
    entry: './src/index', // webpack 读取的入口文件
    // 输出的资源文件
    output: {
        filename: 'index.js',
        path: path.resolve(__dirname, 'dist')
    },
    mode: 'development',
    target: 'web',
    devServer: {
        hot: true,
        open: true,
    },
    module: {
        rules: [
             {
                 test: /\.jsx?$/,
                 exclude: /node_modules/,
                 use: ['babel-loader']
             },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader', 'postcss-loader']
             },
            {
                test: /\.less$/,
                use: ['style-loader', 'css-loader', 'postcss-loader', 'less-loader']
            },
            {
                test: /\.(png|jpg|webp)/,  
                use: ['file-loader']
            },
            {
                test: /\.(ttf|woff2?)$/,
                type: 'asset/resource',
                generator: {
                    filename: 'font/[name].[hash:3][ext]'
                }
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: '插件使用',
            template: './index.html'
        }),
        new DefinePlugin({
            BASE_URL: '"./"'
        }),
    ]
}

css抽离和压缩

使用 MiniCssExtractPlugin插件来抽离css

本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。

本插件基于 webpack v5 的新特性构建,并且需要 webpack 5 才能正常工作

与 extract-text-webpack-plugin 相比:

  • 异步加载
  • 没有重复的编译(性能)
  • 更容易使用
  • 特别针对 CSS 开发
npm install --save-dev mini-css-extract-plugin
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  plugins: [new MiniCssExtractPlugin()],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader']
     },
    {
        test: /\.less$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'less-loader']
    },
    ],
  },
};

注意点:less 和css的模块都必须配置MiniCssExtractPlugin.loader,因为test: /.css$/匹配的是css文件只会把css的模块提取出来。同理test: /.less$/ 只会匹配less文件,只会把less文件提取出来。所以都必须配置。

使用css-minimizer-webpack-plugin 插件来压缩css

这个插件使用 cssnano 优化和压缩 CSS。

就像 optimize-css-assets-webpack-plugin 一样,但在 source maps 和 assets 中使用查询字符串会更加准确,支持缓存和并发模式下运行

$ npm install css-minimizer-webpack-plugin --save-dev
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /.s?css$/,
        use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
      },
    ],
  },
  optimization: {
    minimizer: [
      // 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
      // `...`,
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

注意点: 开启压缩插件一般是在生产环境,mode为development下不会生效。如果还想在开发环境下启用 CSS 优化,请将 optimization.minimize 设置为 true

三,Optimization

没有配置 scope hositing / devtool / terser: 5k

没有配置 devtool / terser: 4k

没有配置 terser /: 3k

都有配置 725B

都有配置 开启tree-shaking: 534B

1. 使用scope hoisting

开启Scope Hoisting:

optimization: {
    concatenateModules: true,
},

由于 Scope Hoisting 需要分析出模块之间的依赖关系,因此源码必须采用 ES6 模块化语句,不然它将无法生效。

使用scope hoisting前:

使用scope hoisting后:

Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,尽可能的把打散的模块合并到一个函数中去,但前提是不能造成代码冗余。 因此只有那些被引用了一次的模块才能被合并。

这样做的好处是:

  • 代码体积更小,因为函数申明语句会产生大量代码;
  • 代码在运行时因为创建的函数作用域更少了,内存开销也随之变小

了解socpe hositing 查看这篇文章,4-14 开启 Scope Hoisting · 深入浅出 Webpack。这里表示上生产的时候要开启该模式。

2. sideEffects

webpack副作用开启:

optimization: {
    sideEffects: true,
},

此配置表示webpack开启副作用文件的检测。

那检测哪些文件呢,在项目根目录下的package.json文件中配置:

// package.json
"sideEffects": false,

"sideEffects": false,表示所有的文件都没有副作用,可以安心的剔除没有使用到的代码。

经过测试,副作用的检测好像是文件级别的,什么意思呢?

这里有一个文件,如果该文件export许多变量, 以及包含export defualt,只要外部文件引入其中一个导出的变量,那么改文件都会被视为没有副作用,其中所有的代码都不会做任何删除。

只有导出变量外部没有任何引用,webpack会标记为有副作用,同时打包的时候会剔除全文件代码。

这里引申一下,如果外部只引用了其中一个export代码,此时为没有副作用的文件,其余代码怎么剔除呢?

使用tree-shaking(下面会讲到)摇掉那些没有使用的export.注意一点是非export的代码不会摇掉,比如window.xxx='xxx'还会保留。

除了webpack去检测哪些文件没有副作用外,还可以手动配置。

"sideEffects": [
    '/src/js/login.js',
    'src/js/api.js'
],

数组中的值为文件路径,表示这些文件没有副作用,可以不用剔除。

在项中引入的css一般会作为有副作用的代码。如果不做特殊处理都会被剔除掉。

import '../font/iconfont.css'

手动配置sideEffects看效果可以参考这篇文章webpack 优化配置之 sideEffects | 飘香豆腐の博客

3. Tree-shaking

js的tree-shaking

js的树摇分4个步骤:

  1. 代码要采用ES6模块化写法来书写咋们的业务代码。

    Tree Shaking 可以用来剔除 JavaScript 中用不上的死代码。它依赖静态的 ES6 模块化语法。

  2. 配置 Babel 让其保留 ES6 模块化语句。

{
  "presets": [
    [
      "env",
      {
        "modules": false // "modules": false 的含义是关闭 Babel 的模块转换功能,保留原本的 ES6 模块化语法。
      }
    ]
  ]
}
  1. usedExports设置为 true

    把没用的方法标记

  2. 使用js的压缩软件

webpack v5 开箱即带有最新版本的 terser-webpack-plugin。如果你使用的是 webpack v5 或更高版本,同时希望自定义配置,那么仍需要安装 terser-webpack-plugin。如果使用 webpack v4,则必须安装 terser-webpack-plugin v4 的版本。

$ npm install terser-webpack-plugin --save-dev
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()],
  },
};

这几步完成基本可以做到tree-shaking的效果,但进一步的优化点可以参考 # 4-10 使用 Tree Shaking

tree-shaking原理就是利用ES6 Module的静态分析,在编译阶段正确分析出哪些模块被引用,哪些模块没有被引用从而进行标识,然后利用压缩工具把为未引用的进行剔除。

css的tree-shaking
$ npm install purgecss-webpack-plugin glob -D
const PurgecssPlugin = require('purgecss-webpack-plugin');

module.exports = {
  // 配置插件的地方
  plugins: [
    // 創建實例 (第三步)
    new PurgecssPlugin({
      // 配置需解析檔案 (第四步)
      paths: glob.sync(`${path.resolve(__dirname, 'src')}/**/*`, {
        nodir: true, // 過濾資料夾結果 (第五步)
      }),
    }),
  ],
};

详见: Webpack 前端打包工具 - 使用 purgecss-webpack-plugin 清除多餘的 CSS | Roya's Blog

多入口打包

    多入口打包可以减少首页的打包体积。

    多入口文件打包:

module.exports = {
  mode: 'none',
  entry: {
    index: './src/index.js',
    album: './src/album.js'
  },
  // entry: './src/index.js',
  output: {
    filename: '[name].bundle.js'
  }
}

splitChunks

    splitChunks作用简单描述就是可以控制webpack在最后合并文件的时候,按在splitChunks设置的要求去拆分不同的文件。

基本用法:

module.exports = {
  //...
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  }
};    

这里涉及的内容太多,参考下一片篇文章。

或者这个系列文章:

理解webpack4.splitChunks - 渴望做梦 - 博客园

理解webpack4.splitChunks之cacheGroups - 渴望做梦 - 博客园

按需加载 / 魔法注释

按需加载做法很简单:

const render = () => {
    console.log('hash改不了')
    const mainElement = document.querySelector('.main')
    const hash = window.location.hash || '#post'
    mainElement.innerHTML = ''
    if (hash === '#post') {
        // 按需加载
        import(/* webpackChunkName: 'post' */'./post').then(post => {
            console.log(post, 'post...');
            post['default'](mainElement)
        })
    } else {
        // 按需加载
        import(/* webpackChunkName: 'post' */'./album/album').then(album => {
            album['default'](mainElement)
        })
    }
}

webpack在打包的时候,会把import()动态引入的文件单独打包出来,第一次加载并不会网络请求该文件,从而可以做到减少第一次加载文件的大小。

这里的魔法注释名字如果一样,会被打包到一起。

输出文件名Hash

生产文件带hash有三种类型

第一种:普通hash

module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    filename: '[name]-[hash].bundle.js'
  }
}

维度是项目级别的,项目任何文件内容改变,生成的文件hash值全部改变。

第二种:chunkhash

module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    filename: '[name]-[chunkhash].bundle.js'
  }
}

每次改变文件内容会改变对应的chunk的hash值(打包出来的所有chunk,包括css和js)。

第三种:contenthash

module.exports = {
  mode: 'none',
  entry: './src/index.js',
  output: {
    filename: '[name]-[contenthash].bundle.js'
  }
}

是文件级别的,只有对应文件改变,对应生产的文件的hash就会改变,css文件内容改变只会改变css的打包以后的hash值,js同理。

所有contenthash模式是缓存最好的方法。