提升webpack打包速度的方法

1. 跟上技术的迭代
  • Node、Npm、Yarn、Webpack都要保持最新的版本
2. 在尽可能少的模块上应用Loader
  • 合理的使用exclude或者include的配置,来尽量减少loader被频繁执行的频率。当loader执行频率降低时,也会提升webpack的打包速度。比如:
module: {
    rules: [{ 
      test: /\.js$/,
      include: path.resolve(__dirname, '../src'), // 只对src目录下的js文件做打包转译工作
      // exclude: /node_modules/, // 如果你的js文件在node_modules里边,就不使用babel-loader了,因为它里边的代码都是些第三方代码,已经做好了转译的工作。
      use: [{
        loader: "babel-loader"
      }]
    }]
}
3. Plugin尽可能少并确保可靠
  • 选择性能比较好的、官方推荐的或者社区认可的插件来使用。
  • Plugin尽可能少的使用,减少冗余的代码分析。比如:(css代码压缩插件只在生产环境中配置,开发环境中没有必要使用)
  • 开发环境无用插件的剔除。
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-plugin');
const prodConfig = {
  mode: 'production', // 生产环境
  optimization: {
    minimizer: [new OptimizeCSSAssetsPlugin({})]
  }
} 
module.exports = prodConfig;
4. resolve参数合理配置
  • 通过合理优化resolve配置项,让webpack打包速度更快。
resolve: { // 新增配置项(如果想在模块里引入js文件,直接可省略后缀的话)
    extensions: ['.js', '.jsx'], // 当引入一个其他目录下的模块的时候,首先会去找.js为后缀的文件,如果没有匹配到,再去找.jsx为后缀的文件
    mainFiles: ['index', 'child'], // 当引入一个目录下的内容的时候,不知道具体要引入哪个文件,那么可以通过mainFiles配置先尝试去找index文件,如果没有index,再去找child文件 
    alias: {
      child: path.resolve(__dirname, '../src/a/b/child') // 配置引入模块的别名
    }
}
  • extensions 配置了很多值时,那么在查找文件时,是会有性能损耗的。所以,一般我们在引入一些如js、jsx这种 逻辑型文件 时,我们才会把它配置到extensions的配置项里(像一些’.css’/’.png’等文件,强烈不建议配置在extensions里) 。这样我们的代码写起来也会方便些,性能方面也得到了合理的提升。
  • mainFiles 配置很多值时,也会影响打包性能,所以在项目开发中,要视情况而定,不要盲目的配置mainFiles项。(一般项目不用配置)
  • alias 当逻辑代码中引入的其他模块文件,所在层级较深时,比较适用,其他情况下,不建议使用。
5. 使用DllPlugin提升webpack打包速度

当我们的js代码里,引入第三方库 的时候,每次重新打包,都要重新分析所引入的第三方库代码,最终把他们打包到我们的项目之中。第三方库的代码都有个特点,那就是他们是不变的(不随业务逻辑变化而变化),所以我们可以把所有引入的第三方的代码都打包生成一个文件里,只在第一次打包时分析第三方代码,之后再执行打包时,直接用上次分析好的结果即可。

具体步骤如下:

  1. 我们对第三方库单独进行打包,生成一个打包文件。
  2. 使用 library 通过全局变量的形式把第三方库的所有代码暴露出去。
  3. 通过DllPlugin插件,来对我们暴露的代码做一个分析,生成一个manifest映射文件。
  4. 通过执行npm run build:dll 生成一个单独的第三方库的打包文件。(在scripts中配置)
  5. 在 webpack.common.js(公共的配置文件)文件中,引入DllReferencePlugin插件,去拿打包的映射文件。查找是否存在第三方库的映射关系。

如何结合全局变量和manifest.json映射文件进行webpack的配置??下方的例子:

使用DllReferencePlugin和DllPlugin进行配置:
我们单独配置一个第三方库的打包文件:webpack.dll.js

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

module.exports = {
  mode: 'production',
  entry: {
    vendors: ['react', 'react-dom', 'lodash']
  },
  plugins: [
    new webpack.DllPlugin({
      name: '[name]', // 用DllPlugin对库进行分析
      path: path.resolve(__dirname, '../dll/[name].manifest.json') // 把库里边第三方的一些映射关系,放到name.manifest.json文件下
    }) 
  ],
  output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, '../dll'), // 把第三方库代码打包到dll文件夹下
    library: '[name]' // 暴露全局变量
  }
}
  • library 通过全局变量的形式把第三方库的所有代码暴露出去

scripts配置:package.json文件

"scripts": {
    "build": "webpack --config ./build/webpack.prod.js",
    "build:dll": "webpack --config ./build/webpack.dll.js"
}

执行打包第三方库文件:npm run build:dll

webpack.common.js中的插件配置:

const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');
module.exports = {
	plugins: [
		new AddAssetHtmlWebpackPlugin({ // 为页面引入打包好的第三方库代码文件
	      filepath: path.resolve(__dirname, '../dll/vendors.dll.js')
	    }),
	    new webpack.DllReferencePlugin({ // 查找第三方库的映射关系
	      manifest:  path.resolve(__dirname, '../dll/vendors.manifest.json')
	    })
	]
}
  • webpack内置的DllReferencePlugin插件的作用主要是:在执行webpack打包时,对我们引入的第三方库,它会先去dll下的manifest映射文件中查找第三方模块的映射关系,如果能找到映射关系,那么它就会认为,引入的第三方库,没有必要再次进行打包了, 可以直接去全局变量里拿; 如果发现引入的第三放库的映射关系不在manifest映射文件里,那么它就会再从node_modules里取出该模块,然后再进行打包到我们的源代码里边。

再次打包 npm run build 我们会发现:

不配置DllReferencePlugin插件时:打包速度基本稳定在1000ms

配置DllReferencePlugin插件之后:打包速度基本稳定在600ms

实际上,在webpack配置大型复杂项目过程中,DllPlugin和DllReferencePlugin用的还是比较多的。

在webpack做打包的时候,我们就可以结合全局变量、以及我们生成的manifest映射文件,然后来对我们的源代码进行分析,一旦发现你使用的第三方库的内容是在vendors.dll.js里边,那么它就会直接使用vendors.dll.js里的内容了。就不会再去node_modules里引入我们的模块了。

6. 控制包文件大小

在我们的项目开发中,经常会引入一些没有用到的模块,引入之后,其实我们并没有使用这些模块。这个时候,如果你没有配置tree-shaking,就会导致在打包的过程中有很多冗余的代码。那么这些冗余的代码,实际上就会拖累webpack的打包速度。所以,在我们做打包的时候,对于一些冗余的代码,我们可以通过 tree-shaking(查看详解) 把它去除掉。(或者直接不去引用它)这样我们就可以控制webpack打包生成文件的大小,从而提升打包速度。

也可以通过splitChunksPlugin插件,对代码进行拆分,把一个大的文件,拆分成几个小的文件,做webpack的打包处理,这样也可以提高webpack的打包速度。

7. thread-loader, parallel-webpack, happypack 多进程打包

webpack默认是基于node.js来运行的,所以它是一个单进程的打包过程。我们也可以借助webpack里的多进程来帮我们提升打包速度
如:thread-loader, parallel-webpack, happypack 多进程打包。
他们可以利于node中的多进程,同时利用多个cpu进行打包,具体需要开几个cpu进行打包速度是最快的,对其他应用影响是最小的,要根据项目实际情况,多做几次尝试来做一个权衡。

8. 合理使用 sourceMap(查看详解)
9. 结合stats.json文件分析打包结果
10. 开发环境内存编译

webpack-dev-server做打包的时候不会生成dist目录,而是会把打包生成的文件放到内存里,那么内存的读取,肯定要比硬盘的读取要快的多,所以采用这种手段,也会让我们在开发的过程中webpack打包的性能得到很大的提升。

另外,提升webpack打包速度的方式还有很多,比如:一些loader会提供一些参数,帮助我们去提升它的打包速度。

知识补充

我们对webpack.dll.js文件里的entry再做一个变更:(对打包的dll文件做拆分)

entry: {
    vendors: ['lodash'],
    react: ['react', 'react-dom'] // 让react、react-dom库的代码打包到文件名为react的文件里
}

在webpack公共配置文件中多配置一份插件:

单独打包第三方库代码:(执行打包)

npm  run  build:dll

此时的dll目录如下:

再次执行打包:

npm  run  dev

页面显示正常,react、vendors也可以正常访问。

如果在大型项目中,对dll文件做拆分比较多的话,这样会导致在webpack公共的配置文件里,重复实例化多个相同的插件,下边分享个更好的方法:

通过node来分析dll目录下究竟有几个dll文件和manifest文件,然后动态的往plugins里添加AddAssetHtmlWebpackPlugin和DllReferencePlugin。

const path = require('path'); // 引入node的path模块(loader模块)
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
const webpack = require('webpack');

// 创建一个插件数组
const plugins = [
  new HtmlWebpackPlugin({
    template: './src/index.html'
  }), 
  new CleanWebpackPlugin()
]

// 使用fs读取dll目录下的文件
const files = fs.readdirSync(path.resolve(__dirname, '../dll'));
// 对获取到的所有文件进行循环
files.forEach(file => {
  if(/.*\.dll.js/) { // 如果是以.dll.js结尾的文件,就以走AddAssetHtmlWebpackPlugin插件
    plugins.push(
      new AddAssetHtmlWebpackPlugin({ // 为页面引入打包好的第三方库代码文件
        filepath: path.resolve(__dirname, '../dll', file)
      })
    )
  }
  if(/.*\.manifest.json/) { // 如果是以.manifest.json结尾的文件,就走DllReferencePlugin插件
    plugins.push( 
      new webpack.DllReferencePlugin({ 
        manifest:  path.resolve(__dirname, '../dll', file)
      })
    )
  }
})

module.exports = {
  entry: {
    main: './src/index.js'
  },
  resolve: { // 新增配置项(如果想在模块里引入js文件,直接可省略后缀的话)
    extensions: ['.js', '.jsx']
  },
  module: {
    rules: [{
      test: /\.jsx?$/, // 这里代表既匹配js文件,也匹配jsx文件
      include: path.resolve(__dirname, '../src'), // 只对src目录下的js文件做打包转译工作
      // exclude: /node_modules/, // 如果你的js文件在node_modules里边,就不使用babel-loader了,因为它里边的代码都是些第三方代码,已经做好了转译的工作。
      use: [{
        loader: "babel-loader"
      }]
    }]
  },
  plugins, // 插件配置项
  optimization: { 
    splitChunks: { // 有默认配置项 
      chunks: "all" // 不管是同步还是异步,都进行代码分割 
    }
  },
  output: {
    filename: '[name].js', // 打包之后的输出文件
    path: path.resolve(__dirname, '../dist') 
  }
}

执行打包:

npm  run  dev

打开控制台我们发现,index.html页面引入了我们打包过后的第三方库文件。

此时,再对打包的dll文件做拆分,就比较方便了,每次做拆分就不必一次次的添加插件的实例了。

发布了54 篇原创文章 · 获赞 22 · 访问量 7237

猜你喜欢

转载自blog.csdn.net/Riona_cheng/article/details/100711812