webpack性能优化篇

随着业务代码不断增加,使用webpack进行构建的时长也会随之不断增加。下面我们来通过一些配置来减少构建时长。

speed-measure-webpack-plugin

该插件能够测量出在你的构建过程中,每一个 LoaderPlugin 的执行时长。

这样,你就能根据耗时较长的loader或者plugin来进行代码的优化。

使用:

只需要在你导出 Webpack 配置时,为你的原始配置包一层 smp.wrap 就可以了,接下来执行构建

const speedMeasurePlugin = require('speed-measure-webpack-plugin')
const config = {
  entry: "./src/index.js",
  output: {
    filename: '[name].[hash].js',
    clean: true
  }
}

const smp = new speedMeasurePlugin();

module.exports = smp.wrap(config);

优化

虽然通过speed-measure-webpack-plugin我们可以找到耗时较长的loader或者plugin,但是如果我们稍微对AST有点概念的话,都会知道其实最耗时的是会是在编译jscssloader上(转化 AST -> 遍历树 -> 转化回代码)。

对此,我们大致可以将优化分成4个方向:

  • 缓存
  • 多核
  • 抽离
  • 拆分

缓存

在每次启动webpack的时候,都默认会将所有的文件都编译一次,那么我们试想一下,是否可以将一些没有变更的文件给缓存下来呢?

大部分 Loader 都提供了 cache 配置项,比如在 babel-loader 中,可以通过设置 cacheDirectory 来开启缓存。这样,babel-loader 就会将每次的编译结果写进硬盘文件(默认是在项目根目录下的node_modules/.cache/babel-loader目录内,当然你也可以自定义)。

const speedMeasurePlugin = require('speed-measure-webpack-plugin')
const config = {
  mode: 'development',
  entry: "./src/index.js",
  output: {
    filename: '[name].[hash].js',
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        include: [
          path.resolve(__dirname, 'src')
        ],
        use: [
          {
            loader: 'babel-loader',
            options: {
              cacheDirectory: 'babel-loader'
            }
          }
        ]
      }
    ]
  }
}

const smp = new speedMeasurePlugin();

module.exports = smp.wrap(config);

对于其他不支持缓存的loader,可以使用cache-loader

注意: cache-loader必须放在第一个

const speedMeasurePlugin = require('speed-measure-webpack-plugin')
const config = {
  mode: 'development',
  entry: "./src/index.js",
  output: {
    filename: '[name].[hash].js',
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        include: [
          path.resolve(__dirname, 'src')
        ],
        use: [
          {
            loader: 'cache-loader',
            options: {
              cacheDirectory: 'cache-loader'
            }
          }
          ...otherLoaders
        ]
      }
    ]
  }
}

const smp = new speedMeasurePlugin();

module.exports = smp.wrap(config);

cache-loader 默认将缓存存放的路径是项目根目录下的 .cache-loader 目录内,我们习惯将它配置到项目根目录下的 node_modules/.cache 目录下,与 babel-loader 等其他 Plugin 或者 Loader 缓存存放在一块。

多核

多核就是使用多个进程去编译处理。在webpack中最常用的就是happypack

happypack原理: 将webpack中最耗时的loader文件转换操作任务,分解到多个进程中并行处理,从而减少构建时间。

const speedMeasurePlugin = require('speed-measure-webpack-plugin')
const HappyPack = require('happypack');
const path = require('path');
const threadPool = 5;
const config = {
  mode: 'development',
  entry: "./src/index.js",
  output: {
    filename: '[name].[hash].js',
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        // 只处理src目录下的文件
        include: [
          path.resolve(__dirname, 'src')
        ],
        use: 'happypack/loader?id=babel'
      }
    ]
  },
  plugins: [
    new HappyPack({
      id: 'babel',
      loaders: [
        {
          loader: 'cache-loader',
          options: {
            cacheDirectory: 'cache-loader'
          }
        },
        {
          loader: 'babel-loader',
        }
      ],
      threads: threadPool
    })
  ]
}
const smp = new speedMeasurePlugin();

module.exports = smp.wrap(config);

happypack 中的 Loaders 做一层包装就好了,向外暴露一个id ,而在 module.rules 里,就不需要写loader,直接引用这个 id 即可,

speed-measure-webpack-plugin中耗时较久的loaders使用happypack做一层多核编译的包装。

抽离

对于一些不常变更的静态依赖,比如axioslodash等,我们不希望这些依赖被集成进每一次构建逻辑中,因为它们真的太少时候会被变更了,所以每次的构建的输入输出都应该是相同的。因此,我们会设法将这些静态依赖从每一次的构建逻辑中抽离出去,以提升我们每次构建的构建效率。

常见方案:

  • 使用DllPlugin
  • Externals方式
1. 使用DllPlugin

在使用webpack进行打包时候,对于依赖的第三方库,如axios等这些不会修改的依赖,可以让它和业务代码分开打包;

只要不升级依赖库版本,之后webpack就只需要打包项目业务代码,遇到需要导入的模块在某个动态链接库中时,就直接去其中获取;而不用再去编译第三方库,这样第三方库就只需要打包一次。

webpack中已经内置该插件(webpack.DllPlugin用于打包出一个个单独的动态链接库文件)。我们看看怎么使用:

新建一个文件,webpack.dll.config.js

const webpack = require('webpack');
const path = require('path');

module.exports = {
  mode: 'development',
  entry: {
    utils: ['axios'],
    // 将vue相关模块放在同一个动态链接库下
    vue: ['vue', 'vue-router']
  },
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, 'dll'),
    // 存放动态链接库的全局变量名,加上_dll_防止全局变量冲突
    library: '_dll_[name]'
  },
  plugins: [
    new webpack.DllPlugin({
      name: '_dll_[name]',
      // 动态链接库的全局变量名称,需要可output.library中保持一致,也是输出的manifest.json文件中name的字段值
      // 如axios.manifest.json字段中存在"name":"_dll_axios"
      path: path.join(__dirname, 'dll', '[name].manifest.json')
    })
  ]
}

在命令行中输入npx webpack --config webpack.dll.config.js.

可以看到生成了一个dll目录
请添加图片描述

然后在webpack.config.js中对生成的dll进行配置。

使用webpack.DllReferencePlugin引入DllPlugin插件打包好的动态链接库文件。

  plugins: [
    // 告诉webpack使用了哪些动态链接库
    new webpack.DllReferencePlugin({
      manifest: require('./dll/utils.manifest.json')
    }),
    new webpack.DllReferencePlugin({
      manifest: require('./dll/vue.manifest.json')
    }),
  ]

注意:在webpack.dll.config.js文件中,webpack.DllPlugin中的name参数必须和output.library中的一致;因为DllPluginname参数影响输出的manifest.jsonname;而webpack.config.js中的webpack.DllReferencePlugin会读取manifest.jsonname,将值作为从全局变量中获取动态链接库内容时的全局变量名

2. Externals方式

作为webpack内置的一个参数,其使用非常简单。

  1. HTML中引入第三方库的cdn
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  1. webpack中配置externals
  externals: {
    'axios': 'axios'
  }
  1. js中引用
import axios from 'axios';
axios.post()

拆分

这个概念就很简单了,虽然说在大前端时代下,SPA 已经成为主流,但我们不免还是会有一些项目需要做成 MPA(多页应用),得益于 webpack 的多 entry 支持,因此我们可以把多页都放在一个 repo 下进行管理和维护。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐