面试官问你webpack有什么优化策略的时候你可以这么答

1,132 阅读4分钟

webpack的优化问题是老生常谈了,如果遇到面试官问你webpack有什么优化技巧,我认为可以从以下方面去着手。

优化步骤目录

  1. 打包速度分析
  2. 打包体积分析
  3. 多进程构建
  4. 压缩代码
  5. 预编译
  6. 缓存
  7. 缩小文件构建目标/减少文件构建搜索范围
  8. Scope Hoisting (作用域提升)
  9. 配置externals

1、打包速度分析

想要优化webpack打包,应该先观察下打包的耗时,看看到底是哪个loader和plugin耗时比较久,然后再从中找到合适的优化点。

比较常用的就是 speed-measure-webpack-plugin

yarn add speed-measure-webpack-plugin

//配置方法,只需要把webpack的配置对象包裹起来即可

const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");

const smp = new SpeedMeasurePlugin();

const webpackConfig = smp.wrap({
  plugins: [
    ... new plugin
  ]
});

这个插件能够清晰计算出打包总构时和每个plugin和loader的执行耗时

2、打包体积分析

打包后的体积也是一个可以着重优化的点,如果引入的第三方库太大,可以寻找替代品

yarn add -D webpack-bundle-analyzer


//配置方法
// webpack.config.js 文件

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports={
  plugins: [
    new BundleAnalyzerPlugin()  // 使用默认配置
    // 默认配置的具体配置项
    // new BundleAnalyzerPlugin({
    //   analyzerMode: 'server',
    //   analyzerHost: '127.0.0.1',
    //   analyzerPort: '8888',
    //   reportFilename: 'report.html',
    //   defaultSizes: 'parsed',
    //   openAnalyzer: true,
    //   generateStatsFile: false,
    //   statsFilename: 'stats.json',
    //   statsOptions: null,
    //   excludeAssets: null,
    //   logLevel: info
    // })
  ]
}

缺点则是每次构建的时候都会起一个本地服务器以显示打包体积的展示网站,但是并不是每次都需要看

解决方法则是:可以配置命令的方法去避免

1. 配置webpack.config.js

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
module.exports={
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerMode: 'disabled', // 不启动展示打包报告的http服务器
      generateStatsFile: true, // 是否生成stats.json文件
    }),
  ]
}

2. 配置package.json

{
 "scripts": {
    "generateAnalyzFile": "webpack --profile --json > stats.json", // 生成分析文件
    "analyz": "webpack-bundle-analyzer --port 8888 ./dist/stats.json" // 启动展示打包报告的http服务器
  }
}

运行先npm run generateAnalyzFile命令,然后运行npm run analyz命令。
此时就可以看到分析结果了

3、多进程构建

众所周知,webpack是运行在node环境中的,node是单线程的,所以开启多线程进行任务处理,可以提升构建速度

常见的有thread-loader 和 happyPack (不更新)

yarn add -D thread-loader

配置方法:把thread-loader放在耗时大的loader前

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve("src"),
        use: [
          "thread-loader",
          // your expensive loader (e.g babel-loader)
        ]
      }
    ]
  }
}

yarn add -D happypack

配置方法:
const HappyPack = require('happypack');

exports.module = {
  rules: [
    {
      test: /.js$/,
      use: 'happypack/loader',
      include: [ /* ... */ ],
      exclude: [ /* ... */ ]
    }
  ]
};

exports.plugins = [
  new HappyPack({
    loaders: [ 'babel-loader?presets[]=es2015' ]
  })
];

==缺点: 对于小型项目来说打包速度几乎没有影响,甚至可能会增加开销,所以建议尽量在大项目中采用== 。

4、压缩代码

现在通用的方案是terser-webpack-plugin,兼容es6语法

yarn add -D terser-webpack-plugin

const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: 4, //开启4个多线程
        test: /\.js(\?.*)?$/i,
        terserOptions:{
            compress :{
                drop_console : true,  //清楚多余的console
                drop_debugger : true
            }
        }
      }),
    ],
  },
};

5、预编译模块

DllPlugin和DllReferencePlugin提供分离包的方式可以大大提高构建时间性能。主要思想在于,将一些不做修改的依赖文件,提前打包,这样我们开发代码发布的时候就不需要再对这部分代码进行打包。从而节省了打包时间。

DllPlugin

这个插件使用一个单独webpack配置创建一个dll-only-bundle文件。并且它还创建一个manifest.json。

  • context (可选): manifest文件中请求的上下文,默认为该webpack文件上下文。
  • name: 公开的dll函数的名称,和output. library保持一致即可。
  • path: manifest.json生成的文件夹及名字
new webpack.DllPlugin({
  context: __dirname,
  name: "[name]_[hash]",
  path: path.join(__dirname, "manifest.json"),
})

DllReferencePlugin

使用该json文件来做映射依赖性。(这个文件会告诉我们的哪些文件已经提取打包好了)。

  • context: manifest文件中请求的上下文。
  • manifest: DllPlugin插件生成的manifest.json
  • content(可选): 请求的映射模块id(默认为manifest.content)
  • name(可选): dll暴露的名称
  • scope(可选): 前缀用于访问dll的内容
  • sourceType(可选): dll是如何暴露(libraryTarget)
new webpack.DllReferencePlugin({
  context: __dirname,
  manifest: require("./manifest.json"),
  name: "./my-dll.js",
  scope: "xyz",
  sourceType: "commonjs2"
})

可以单独创建一个webpack.dll.config.js

//webpack.dll.conf.js

const path = require('path');
const webpack = require('webpack');
module.exports = {
  entry: {
    vendor: [
    'vue/dist/vue.esm.js',
    'vue-router',
    'vuex',
    'babel-polyfill' //提前打包一些基本不怎么修改的文件
    ]
  },
  output: {
    path: path.join(__dirname, '../static/js'), //放在项目的static/js目录下面
    filename: '[name].dll.js', //打包文件的名字
    library: '[name]_library' //可选 暴露出的全局变量名
    // vendor.dll.js中暴露出的全局变量名。
    // 主要是给DllPlugin中的name使用,
    // 故这里需要和webpack.DllPlugin中的`name: '[name]_library',`保持一致。
  },
  plugins: [
    new webpack.DllPlugin({
      path: path.join(__dirname, '.', '[name]-manifest.json'), //生成上文说到清单文件,放在当前build文件下面,这个看你自己想放哪里了。
      name: '[name]_library'
    }),  
    //压缩 只是为了包更小一点 
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false,
        drop_console:true,
        drop_debugger:true
      },
      output:{
        // 去掉注释内容
        comments: false,
      },
      sourceMap: true
    })
  ]
};

在webpack.pro.conf.js文件需要做如下修改,在plugins下面加入如果代码:add-asset-html-webpack-plugin

 const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');

  new webpack.DllReferencePlugin({
      context: path.resolve(__dirname, '..'), 
      manifest: require('./vendor-manifest.json')
    }),
    //这个主要是将生成的vendor.dll.js文件加上hash值插入到页面中。
    new AddAssetHtmlPlugin([{
      filepath: path.resolve(__dirname,'../dist/static/js/vendor.dll.js'),
      outputPath: utils.assetsPath('js'),
      publicPath: path.posix.join(config.build.assetsPublicPath, 'static/js'),
      includeSourcemap: false,
      hash: true,
    }]),

执行

npm run build:dll  //这个命令在最初执行一次之后,之后发布都不需要再重复执行了,除非webpack.dll.conf.js里面的依赖文件有升级。
//发布之前的打包
npm run build 

6、缓存

缓存一般针对第二次构建速度的提升,利用缓存可以显著提升打包和构建速度

一般使用的有三种方法

  • cache-loader
  • babel-loader
  • hard-source-webpack-plugin

cache-loader

在一些性能开销较大的 loader 之前添加此 loader,以将结果缓存到磁盘里

yarn add -D cache-loader

使用方法:
// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          'cache-loader',
          ...loaders
        ],
        include: path.resolve('src')
      }
    ]
  }
}

hard-source-webpack-plugin

HardSourceWebpackPlugin 为模块提供了中间缓存,缓存默认的存放路径是: node_modules/.cache/hard-source,使用这个能使第二次构建的时间提升80%

yarn add -D hard-source-webpack-plugin

var HardSourceWebpackPlugin = require('hard-source-webpack-plugin');

module.exports = {
  entry: // ...
  output: // ...
  plugins: [
    new HardSourceWebpackPlugin()
  ]
}

babel-loader

在一些性能开销较大的 loader 之前添加此 loader,以将结果缓存到磁盘里

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [{
      loader: "babel-loader",
      options: {
        cacheDirectory: true
      }
    }],
}

7、缩小文件构建目标/减少文件构建搜索范围

主要是exclude 与 include的使用

  • exclude: 不需要包含被解析的模块
  • include: 需要包含被解析的模块

缩小文件构建目

// webpack.config.js
const path = require('path');
module.exports = {
  ...
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: ['babel-loader']
      }
    ]
  }

减少文件构建搜索范围

// webpack.config.js
const path = require('path');
module.exports = {
  ...
  resolve: {
    alias: {
      jquery: path.resolve(__dirname, './node_modules/jquery/umd/jquery.min.js')
    }, //直接指定搜索模块,不设置默认会一层层的搜寻
    modules: [path.resolve(__dirname, 'node_modules')], //限定模块路径
    extensions: ['.js'], //限定文件扩展名
    mainFields: ['main'] //限定模块入口文件名

8、作用域提升 Scope Hoisting

可减少函数申明语句,减少创建的函数作用于,使内存开销变小。这是webpack的内置功能,只要配置开启即可。

// webpack.config.js
const webpack = require('webpack')

module.exports = mode => {
  if (mode === 'production') {
    return {}
  }

  return {
    devtool: 'source-map',
    plugins: [new webpack.optimize.ModuleConcatenationPlugin()],
  }
}

9、配置externals

防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)

//webpack.config.js
module.exports = {
    //...
    externals: {
        //jquery通过script引入之后,全局中即有了 jQuery 变量
        'jquery': 'jQuery'
    }
}

// 然后通过script在页面引入
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="root">root</div>
    <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
</body>
</html>

又或者可以借助一些webpack插件来帮我们实现这个目标

const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');

module.exports = {
  // ...
  plugins: [
    new HtmlWebpackExternalsPlugin({
      externals: [
        {
          module: 'react',
          entry: 'https://unpkg.com/react@17.0.2/umd/react.production.min.js',
          global: 'React',
        },
        {
          module: 'react-dom',
          entry:
            'https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js',
          global: 'ReactDOM',
        },
      ],
    }),
  ],
};

以上就是我整理的webpack相关的优化策略的方案,希望在面试的过程中可以顺利的通过面试官的提问。