# webpack 做过哪些优化,开发效率方面、打包策略方面等等

# 往期回顾

# 类数组与数组的区别,怎么把类数组转为数组

本质上,类数组只是一个索引对象,他的原型和数组不一样 a instanceof Array, a.__proto__.constructor === Array

Array.from();
Array.prototype.slice.call();
Array.prototype.forEach();

# 今日解题

# 1)优化 webpack 的构建速度

  • 使用高版本的 webpack
  • 多线程/多实例构建:HappyPack(不维护了)、thread-loader
  • 缩小打包作用域
    • exclude/include(确定 loader 规则范围)
    • resolve.modules 指明第三方模块的绝对路径(减少不必要的查找)
    • resolve.extensions 尽可能减少后缀尝试的可能性
    • noParse 对完全不需要解析的库进行忽略(不去解析但仍会打包到 bundle 中,注意被忽略掉的文件不应该包含 import, require, define 等模块化语句)
    • IgnorePlugin(完全排除模块)
    • 合理使用 alias 【别名】
  • 充分利用缓存提升二次构建速度
    • babel-loader 开启缓存
    • terser-webpack-plugin 开启缓存
    • 使用 cache-loader 或 hard-source-webpack-plugin
    • tips: thread-loadercache-loader两个要一起使用的话,请先放cache-loader接着是thread-loader最后才是heavy-loader
  • DLL:
    • 使用DllPlugin进行分包,使用DllReferencePlugin(索引链接)对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译而浪费时间

# 2) 使用 webpack4 优化的原因

  • a. V8 带来的优化(for of代替forEachMapSet代替Object, incluedes代替indexOf)
  • b. 默认使用更快的md4 hash算法
  • c. Webpacks AST 可以直接从 loader 传递给 AST, 减少解析时间
  • d. 使用字符串方法替代正则表达式

# 3) 常用的 webpack 配置

# 1. noParse

  • 不去解析某个库内部的依赖关系
  • 比如 jQuery 这个库是独立的,则不去解析这个库内部依赖的其他东西
  • 在独立库的时候可以使用
module.exports = {
  module: {
    noParse: /jquery/,
    rules: [],
  },
};

# 2. IgnorePlugin

  • 忽略掉某些内容,不去解析依赖库内部引用的某些内容
  • 从 moment 中引用./local则忽略
  • 如果要用 local 的话,则必须在项目中手动引入import "moment/locale/zh-cn"
module.exports = {
  plugins: [
    new Webpack.IgnorePlugin(/./local/, /moment/),
  ]
}

# 3. dillPlugin

  • 不会多次打包,优化打包时间
  • 先把依赖的不变的库打包
  • 生成manifest.json文件
  • 然后在 webpack.config 中引入
  • webpack.DllPlugin Webpack.DllReferencePlugin

# 4. happypack -> thread-loader

  • 大项目的时候开启多线程打包
  • 影响前端发布速度的两个方面,一个是构建,一个是压缩,把这两个方面优化起来,可以减少很多发布时间

# 5. thread-loader

  • thread-loader 会将 loader 放置在一个 worker 池里运行,以达到多线程构建
  • 把这个 loader 放置在其他 loader 之前,放置在这个 loader 之后的 loader 就会在一个单独的 worker 池中运行
  • 每个 worker 都是一个单独的有 600ms 限制的 nodejs 进程,同时跨进程的数据交换也会被限制,因此需要在搞开销的 loader 中使用,否则效果不佳
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        include: path.resolve("src"),
        use: [
          "thread-loader",
          // 这里放置高开销的loader 如: babel-loader
        ],
      },
    ],
  },
};

# 6. 压缩加速——开启多线程压缩

  • 不推荐webpack-paralle-uglify-plugin,因为没人维护,推荐使用terser-webpack-plugin
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin(
        (parallel: true) // 多线程
      ),
    ],
  },
};

# 4)优化 Webpack 的打包体积

  • 压缩代码
    • webpack-paralle-uglify-plugin
    • uglifyjs-webpack-plugin开启 parallel 参数(不支持 ES6)
    • terser-webpack-plugin开启 parallel 参数
    • 多进程并行压缩
    • 通过mini-css-extract-plugin提取 Chunk 中 CSS 代码到单独文件,通过optimize-css-assets-webpack-plugin插件开启 cssnano 压缩 css.
  • 提取页面公共资源
    • 使用html-webpack-externals-plugin, 将基础包通过 CDN 引入,不打入 bundle 中
    • 使用SplitChunksPlugin进行(公共脚本、基础包、页面公共文件)分离(webpack4 内置),替代了 CommonsChunkPlugin 插件
    • 基础包分离:将一些基础库放到 CDN,比如 vue, webpack 配置 external,使得 vue 不打入 bundle
  • tree shaking
    • purgecss-webpack-pluginmini-css-extract-plugin配合使用
    • 打包过程中检测工程中没有引入过的模块并标记,在资源压缩时将他们从最终的 bundle 中去除(只对 ES6 Modlue 生效),为提高 tree shaking 效率,建议使用 ES6Module 的模块编程
    • 使用PurifyCSS(不在维护)或 uncss 去除无用的 css 代码
  • scope hoisting
    • 构建后的代码会存在大量闭包,造成体积增大,运行代码时创建的函数作用域变多,内存开销变大,scope hoising 将所有模块的代码按引用顺序放在一个函数作用域里,然后适当的重命名一些变量以防止变量冲突
    • 必须是 ES6 的语法,因为有好多第三方库仍采用 Commonjs 语法,为了充分发挥 Scopehoisting 的作用,需要配置 mainFields 对第三方模块优先采用 jsnext:main 中指向 ES6 模块化语法
  • 图片压缩
    • 使用基于 Node 库的 imagemin
    • 配置 image-webpack-loader
  • 动态 polyfill
    • 建议采用 polyfill-service 只给用户返回需要的 polyfill(可能存在兼容,需降级返回所需的全部 polyfill)
    • @babel-preset-env 中通过 useBuiltIns:usage参数来动态加载 polyfill

# 7) speed-measure-webpack-plugin

简称 SMP,分析出 Webpack 打包过程中 loader 和 plugin 的耗时,有助于找到构建过程中的性能瓶颈

# 5) 开发阶段

  • terser-webpack-plugin 开启多核压缩
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  optimization: {
    minimizer: [
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          ecma: 6,
        },
      }),
    ],
  },
};
  • speed-measure-webpack-plugin 监控面板 在打包的时候显示出每个loader, plugin所用的时间,来准确优化
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// 用smp.warp()包裹合并的config
module.exports = smp.wrap(merge(_mergeConfig, webpackConfig));
  • webpack-build-notifier 开启通知面板
const WebpackBuildNotifierPlugin = require("webpack-build-notifier");
const webpackConfig = {
  plugins: [
    new WebpackBuildNotifierPlugin({
      title: "我的webpack",
      suppressSuccess: true,
    }),
  ],
};
  • progress-bar-webpack-plugin开启打包进度
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
const webpackConfig = {
  plugins: [new ProgressBarPlugin()],
};
  • webpack-dashboard 开发模板更清晰
const DashboardPlugin = require("webpack-dashboard/plugin");
const webpackConfig = {
  plugins: [
    new DashboardPlugin(),
  ]
}

// package.json
{
  "scripts":{
    "dev": "webpack-dashboard webpack --mode development",
  }
}
  • node-bash-title 开启窗口的标题
const setTitle = require("node-bash-title");
setTitle("server");
  • friendly-errors-webpack-plugin 识别某些类别的 webpack 错误,并清理,聚合及优先级
new FriendlyErrorsWebpackPlugin({
    compilationSuccessInfo: {
        messages: ['You application is running here http://localhost:3000'],
        notes: ['Some additionnal notes to be displayed unpon successful compilation']
    },
    onErrors: function (severity, errors) {
        // You can listen to errors transformed and prioritized by the plugin
        // severity can be 'error' or 'warning'
    },
    // should the console be cleared between each compilation?
    // default is true
    clearConsole: true,

    // add formatters and transformers (see below)
    additionalFormatters: [],
    additionalTransformers: []
}),