Loading...
墨滴

几年级的这么叼

2021/03/16  阅读:29  主题:默认主题

Webpack 构建速度优化

Webpack 构建速度优化

主要优化方式:

  • 优化配置
  • 多进程并行构建优化
  • 使用DllPlugin优化

优化配置

优化Loader配置

Loader处理文件的转换操作是很耗时的,所以需要让尽可能少的文件被Loader处理。通过使用 includeexclude 字段,避免多余查找的文件,仅将 loader 应用在实际需要将其转换的模块:

{
  test: /\.js$/,
  use: [{
    loader: 'babel-loader',
    options: {
      presets: ['@babel/preset-env'],
      cacheDirectory: true // 使用缓存
    }
  }],
  include: path.resolve(__dirname, 'src'), //只对src目录中文件采用babel-loader
  exclude: path.resolve(__dirname,' ./node_modules'), //排除node_modules目录下的文件
}
babel-loader开启缓存

babel-loader在执行的时候,可能会产生一些运行期间重复的公共文件,造成代码体积大冗余,同时也会减慢编译效率,可以加上cacheDirectory参数或使用 transform-runtime 插件开启缓存

// webpack.config.js
{
  test: /\.js$/,
  use: [{
    loader: 'babel-loader',
    options: {
      presets: ['@babel/preset-env'],
      cacheDirectory: true // 使用缓存
    }
  }],
}

// .bablerc
{
  "presets": [
    "env",
    "react"
  ],
  "plugins": ["transform-runtime"]
}
优化 resolve 配置

优化 resolve 配置可以提高解析速度

  • resolve.modules

resolve.modules用于配置webpack去哪些目录下寻找第三方模块,默认是['node_modules'],但是,它会先去当前目录的./node_modules查找,没有的话再去../node_modules最后到根目录;

所以当安装的第三方模块都放在项目根目录时,就没有必要安默认的一层一层的查找,直接指明存放的绝对位置

  • resolve.extensions

在导入没带文件后缀的路径时,webpack会自动带上后缀去尝试询问文件是否存在,而resolve.extensions用于配置尝试后缀列表;默认为extensions:['js','json'];

例如遇到require('./data')时,webpack会先尝试寻找data.js,没有再去找data.json;如果列表越长,或者正确的后缀越往后,尝试的次数就会越多;所以在配置时为提升构建优化需遵守: 1、频率出现高的文件后缀优先放在前面 2、列表尽可能的小 3、书写导入语句时,尽量写上后缀名

resolve: {
  modules: [path.resolve(__dirname, 'node_modules')],
  extensions: ['.js''.json'], // 而不是['.json''.js']
  alias: {
    '@src': path.resolve(__dirname, 'src'),
  }
},

减少 resolve.modules, resolve.extensions, resolve.mainFiles, resolve.descriptionFiles 中条目数量可以提高解析速度,因为他们会增加文件系统调用的次数。

  • 优化构建时的搜索路径

在webpack打包时,会有各种各样的路径要去查询搜索,我们可以加上一些配置,让它搜索地更快:

1、尽量使用绝对路径

2、以纯模块名来引入的可以加上一些目录路径

3、善用resolve.alias别名,减少检索路径

4、exclude 避免多余查找的文件

多进程并行构建优化

在使用 Webpack 对项目进行构建时,会对大量文件进行解析和处理。当文件数量变多之后,Webpack 构件速度就会变慢。由于运行在 Node.js 之上的 Webpack 是单线程模型的,所以 Webpack 需要处理的任务要一个一个进行操作。

  • 核心插件:HappyPack[1]
  • 核心原理: 将webpack中最耗时的loader文件转换操作任务,分解到多个进程中并行处理,从而减少构建时间。
  • 接入HappyPack:

1、安装:npm i -D happypack

2、重新配置rules部分,将loader交给happypack来分配:

const HappyPack = require('happypack');
const happyThreadPool = HappyPack.ThreadPool({size: 5}); // 构建共享进程池,包含5个进程
...
plugins: [
    // happypack并行处理
    new HappyPack({
        // 用唯一ID来代表当前HappyPack是用来处理一类特定文件的,与rules中的use对应
        id: 'babel',
        // 用法和 rules 中 Loader 配置一样,可以直接是字符串,也可以是对象形式
        loaders: ['babel-loader?cacheDirectory'],
        threadPool: happyThreadPool, // 使用共享池处理
    }),
    new HappyPack({
        // 用唯一ID来代表当前HappyPack是用来处理一类特定文件的,与rules中的use对应
        id: 'css',
        loaders: [
            'css-loader',
            'postcss-loader',
            'sass-loader'
        ],
        threadPool: happyThreadPool
    })
],
module: {
    rules: [
    {
        test: /\.(js|jsx)$/,
        use: ['happypack/loader?id=babel'],
        exclude: path.resolve(__dirname,' ./node_modules'),
    },
    {
        test: /\.(scss|css)$/,
        //使用的mini-css-extract-plugin提取css此处,如果放在上面会出错
        use: [MiniCssExtractPlugin.loader,'happypack/loader?id=css'],
        include:[
            path.resolve(__dirname,'src'),
            path.join(__dirname, './node_modules/antd')
        ]
    },
}
  • Happypack 配置项:

id: String 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件

loaders: Array 用法和 webpack Loader 配置中一样

threads: Number 代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整数

verbose: Boolean 是否允许 HappyPack 输出日志,默认是 true

threadPool: HappyThreadPoo 代表共享进程池,即多个 HappyPack 实例都使用同一个共享进程池中的子进程去处理任务,以防止资源占用过多

verboseWhenProfiling: Boolean 是否允许 happypack 在运行 webpack --profile 时输出日志,默认是 false

debug: Boolean 启用debug 用于故障排查。默认 false

在测试 Demo 或者小型项目中,使用 happypack 对项目构建速度的提升不明显,甚至会增加项目的构建速度

使用DllPlugin优化

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

这样做的好处是每次更改我本地代码的文件的时候,webpack只需要打包我项目本身的文件代码,而不会再去编译第三方库,那么第三方库在第一次打包的时候只打包一次,以后只要我们不升级第三方包的时候,那么webpack就不会对这些库去打包,这样的可以快速的提高打包的速度。

  • 核心插件:

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文件引用到需要的预编译的依赖上来。

也就是第一次打包时,打包第三方库生成manifest.json,后续打包就不再打包第三方库,而是使用该DllReferencePlugin插件读取manifest.json文件。

  • 核心原理:

1、将依赖的第三方模块抽离,打包到一个个单独的动态链接库中

2、当需要导入的模块存在动态链接库中时,让其直接从链接库中获取

3、项目依赖的所有动态链接库都需要被加载

  • 接入 DllPlugin:

1、配置webpack_dll.config.js构建动态链接库

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

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

2、webpack.config.js 中使用

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

3、先构建webpack_dll.config.js对公共依赖进行打包,然后正常构建

"scripts": {
  "build""webpack --profile --json > stats.json",
  // 生成动态链接库,只需要运行一次这个指令,以后打包项目不需要再执行这个指令
  "build:dll""webpack webpack_dll.config.js",
},

4、查看打包后的文件,插入dll文件

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Webpack App</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  // 手动插入 或则使用add-asset-html-webpack-plugin插件
  <script defer src="../dll/utils-dll.js"></script>
  <script defer src="index.e30fdae292fdc4cfffd3.js"></script>
</head>
  <body>
  </body>
</html>

其他

  • 开发环境不做无意义的操作

很多配置,在开发阶段是不需要去做的,我们可以区分出开发和线上的两套配置,这样在需要上线的时候再全量编译即可。比如说 代码压缩、目录内容清理、计算文件hash、提取CSS文件等

  • 并行压缩

UglifyJsPlugin 插件的parallel参数设置为true,使用多进程并行运行可提高构建速度

使用 webpack-parallel-uglify-plugin 插件可以并行的执行代码压缩

  • 使用fast-sass-loader代替sass-loader

fast-sass-loader可以并行地处理sass,速度更快一些

  • 提取公共代码

使用CommonsChunkPlugin提取公共的模块,可以减少文件体积

Webpack构建分析

输出Webpack构建信息的.json文件:

webpack --profile --json > stats.json

--profile:记录构建中的耗时信息

--json:以json格式输出构建结果,最后只输出一个json文件(包含所有的构建信息)

web可视化查看构建分析

  • 方案一:Webpack Analyse[2]

一个在线Web应用,上传stats.json文件就可以查看分析结果

  • 方案二:webpack-bundle-analyzer[3]

npm i -g webpack-bundle-analyzer

生成stats.json后,直接在其文件夹目录执行webpack-bundle-analyzer stats.json,浏览器会打开对应网页并展示构建分析

小作业

简单实践 HappyPack插件或 DllPlugin插件

参考资料

[1]

HappyPack: https://www.npmjs.com/package/happypack

[2]

Webpack Analyse: http://webpack.github.io/analyse/

[3]

webpack-bundle-analyzer: https://www.npmjs.com/package/webpack-bundle-analyzer

几年级的这么叼

2021/03/16  阅读:29  主题:默认主题

作者介绍

几年级的这么叼