webpack打包构建优化

501 阅读5分钟

在开发过程中,大家多少都会遇到项目越来越大,打包构建速度越来越慢的问题。

下面是我整理总结的一些方法,附带详细步骤。

一、分析report

使用 webpack-bundle-analyzer 对项目模块进行分析生成report,完成后查看哪些模块体积过大,然后针对性优化。

安装:

npm install -–save-dev webpack-bundle-analyzer

配置:

//webpack.prod.config.js
if (config.build.bundleAnalyzerReport) {
  const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

运行:

npm run build --report

查看效果127.0.0.1:8888

二、打包构建优化

1、配置webpack的externals

配置好后,项目运行时从外部获取扩展依赖,而不是将库打包到包中。

externals: {
  'element-ui': 'Element',
  'v-charts': 'VCharts'
}

然后在项目中移除相关库的import或者require。

在index.html中以cdn的形式添加进去。

<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>

2、处理文件的范围

(1)loader

查看配置,将打包范围缩小到src项目文件。

include:表示哪些目录中的 .js 文件需要进行 babel-loader

exclude:表示哪些目录中的 .js 文件不要进行 babel-loader

module: {
  loaders: [{
      test: /\.js$/,
      loader: 'babel-loader',
      include: [
          path.resolve(__dirname, "app/src"),
          path.resolve(__dirname, "app/test")
      ],
      exclude: /node_modules/
  }]
}

(2)eslint

{
    test: /\.js$/,
    loader: 'eslint-loader',
    enforce: "pre",
    include: [path.resolve(__dirname, 'src')], // 指定检查的目录
    options: { // 这里的配置项参数将会被传递到 eslint 的 CLIEngine 
        formatter: require('eslint-friendly-formatter') // 指定错误报告的格式规范
    }
}

3、并行打包(webpack-parallel-uglify-plugin)

webpack默认使用 UglifyJS 插件,该插件采用单线程压缩。

可将此插件替换为 webpack-parallel-uglify-plugin 从而实现并行运行 UglifyJS 插件,减少大量构建时间。

npm i -D webpack-parallel-uglify-plugin

将 webpack.conf.js 里对 UglifyJS 的代码替换成下面。

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
plugins: [
  new ParallelUglifyPlugin({
    cacheDir: '.cache/',
    uglifyJS:{
        output: {
            comments: false
        },
        compress: {
            warnings: false,
            drop_debugger: true,
            drop_console: false
        }
    }
  }),
]

UglifyJS 具体的配置参数如下:

uglifyJS: {
  output: {
    /*
     是否输出可读性较强的代码,即会保留空格和制表符,默认为输出,为了达到更好的压缩效果,
     可以设置为false
    */
    beautify: false,
    /*
     是否保留代码中的注释,默认为保留,为了达到更好的压缩效果,可以设置为false
    */
    comments: false
  },
  compress: {
    /*
     是否在UglifyJS删除没有用到的代码时输出警告信息,默认为输出,可以设置为false关闭这些作用
     不大的警告
    */
    warnings: false,
    /*
     是否删除代码中所有的console语句,默认为不删除,开启后,会删除所有的console语句
    */
    drop_console: true,
    /*
     是否内嵌虽然已经定义了,但是只用到一次的变量,比如将 var x = 1; y = x, 转换成 y = 5, 默认为不
     转换,为了达到更好的压缩效果,可以设置为false
    */
    collapse_vars: true,
    /*
     是否提取出现了多次但是没有定义成变量去引用的静态值,比如将 x = 'xxx'; y = 'xxx'  转换成
     var a = 'xxxx'; x = a; y = a; 默认为不转换,为了达到更好的压缩效果,可以设置为false
    */
    reduce_vars: true
  }
}

此外 ParallelUglifyPlugin 还可配置如下:

new ParallelUglifyPlugin({
  uglifyJS: {},
  test: /.js$/g, // 正则去匹配哪些文件需要被 ParallelUglifyPlugin 压缩,默认是 /.js$/
  include: [],   // 正则去包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
  exclude: [],   // 正则去不包含被 ParallelUglifyPlugin 压缩的文件,默认为 [].
  cacheDir: '',  // 缓存压缩后的结果,下次遇到一样的输入时直接从缓存中获取压缩后的结果并返回,cacheDir 用于配置缓存存放的目录路径。默认不会缓存,想开启缓存请设置一个目录路径。
  workerCount: '',   // 开启几个子进程去并发的执行压缩。默认是当前运行电脑的 CPU 核数减去1。
  sourceMap: false   // 是否为压缩后的代码生成对应的Source Map, 默认不生成,开启后耗时会大大增加,一般不会将压缩后的代码的sourceMap发送给网站用户的浏览器。
});

4、启动node的多线程进行构建(happypack)

node运行下的 webpack 是采用单线程打包的,这是因为nodejs本身就是单线程。采用 happypack 是启动node的多线程进行构建,从而提高构建速度。

npm install --save-dev happypack

修改 webpack.base.conf.js

const HappyPack = require('happypack')
const os = require('os')
let happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
//加入此插件
plugins:[
      new HappyPack({
          id:'babel',
          loaders:['babel-loader?cacheDirectory=true'],
          threadPool:happyThreadPool
      })
  ],
//将js loader作用代码替换
// loader: 'babel-loader'    替换成下方loader
loader: 'happypack/loader?id=babel',

5、使用 DllPlugin 插件单独编译一些不经常改变的代码

DllPlugin是webpack内置的插件,不需要额外安装,直接配置webpack.dll.config.js文件

const path = require("path")
const webpack = require("webpack")
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
  entry: {
    // 第三方库
    react: ['react', 'react-dom', 'react-redux']
  },
  output: {
    // 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,
    filename: '[name].dll.js',
    path: resolve('dist/dll'),
    // library必须和后面dllplugin中的name一致 后面会说明
    library: '[name]_dll_[hash]'
  },
  plugins: [
  	// 接入 DllPlugin
    new webpack.DllPlugin({
      // 动态链接库的全局变量名称,需要和 output.library 中保持一致
      // 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值
      name: '[name]_dll_[hash]',
      // 描述动态链接库的 manifest.json 文件输出时的文件名称
      path: path.join(__dirname, 'dist/dll', '[name].manifest.json')
    }),
    // 压缩打包的文件
    new UglifyJsPlugin({
        uglifyOptions: {
        compress: {
              warnings: false
        }
      },
    }),
  ]
}

在webpack.prod.conf.js和webpack.dev.conf.js中加入当前插件

plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,//与DllPlugin中的context保持一致
      /*这个地址对应webpack.dll.conf.js中生成的那个json文件的路径,这样webpack打包的时候
      会检测当前文件中的映射,不会把已经存在映射的包再次打包进bundle.js */
      manifest: require('./vendor-manifest.json')
  }),
]

在package.json中新加命令。

用于执行webpack.dll.conf.js文件。每次添加新依赖后,重新运行npm run dll这个命令一次。

"dll": "webpack --config ./build/webpack.dll.conf.js"

最后在index.html中引入static/js/vendor.dll.js文件

<script src="dist/dll/react.dll.js"></script>