webpack4从入门到精通保姆级教程【进阶】

1,007 阅读7分钟

前面介绍了webpack4的基础知识,接下来介绍一些进阶用法

自动清理构建目录

避免构建前每次都需要手动删除 dist。使用 clean-webpack-plugin,默认会删除 output 指定的输出目录

module.exports = {
  entry: {
    app: "./src/app.js",
    search: "./src/search.js"
  },
  output: {
    filename: "[name][chunkhash:8].js",
    path: __dirname + "/dist"
  },
+  plugins: [new CleanWebpackPlugin()]
};

PostCSS 插件 autoprefixer ⾃动补⻬ CSS3 前缀

使用 autoprefixer 插件

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          "less-loader",
+          {
+            loader: "postcss-loader",
+            options: {
+              plugins: () => [
+                require("autoprefixer")({
+                  browsers: ["last 2 version", "> 1%", "iOS 7"]
+                })
+              ]
+            }
+          }
        ]
      }
    ]
  }
};

浏览器的分辨率

image.png

CSS 媒体查询实现响应式布局

缺陷:需要写多套适配样式代码

@media screen and (max-width: 980px) {
  .header {
    width: 900px;
  }
}
@media screen and (max-width: 480px) {
  .header {
    height: 400px;
  }
}
@media screen and (max-width: 350px) {
  .header {
    height: 300px;
  }
}

移动端 CSS px ⾃动转换成 rem

使用 px2rem-loader

页面渲染时计算根元素的 font-size 值

module.exports = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          "style-loader",
          "css-loader",
          "less-loader",
+          {
+            loader: "px2rem-loader",
+            options: {
+              remUnit: 75,
+              remPrecision: 8
+            }
+          }
        ]
      }
    ]
  }
};

资源内联的意义

  • 代码层面:

    • 页面框架的初始化脚本
    • 上报相关打点
    • CSS 内联避免页面闪动
  • 请求层面:减少 HTTP 网络请求数

    • 小图片或者字体内联 (url-loader)

HTML 和 JS 内联

  • raw-loader 内联 html
<script>${require(" raw-loader!babel-loader!. /meta.html")}</script>
  • raw-loader 内联 JS
<script>
  ${require("raw-loader!babel-loader!../node_modules/lib-flexible")}
</script>

CSS 内联

  • 借助 style-loader
  • html-inline-css-webpack-plugin
module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          {
            loader: "style-loader",
            options: {
              insertAt: "top", // 样式插入到 <head>
              singleton: true //将所有的style标签合并成一个
            }
          },
          "css-loader",
          "sass-loader"
        ]
      }
    ]
  }
};

多页面应用(MPA)概念

每⼀次页面跳转的时候,后台服务器都会给返回⼀个新的 html 文档,这种类型的⽹站也就是多⻚⽹站,也叫做多页面应用。

多页面打包基本思路

每个页面对应⼀个 entry,⼀个 html-webpack-plugin

缺点:每次新增或删除页面需要改 webpack 配置

module.exports = {
  entry: {
    index: "./src/index.js",
    search: "./src/search.js"
  }
};

多页面打包通用方案

动态获取 entry 和设置 html-webpack-plugin 数量

利⽤ glob.sync

entry: glob.sync(path.join(__dirname, './src/*/index.js')), };

module.exports = {
  entry: {
    index: "./src/index/index.js",
    search: "./src/search/index.js"
  }
};

使用 source map

作用:通过 source map 定位到源代码

开发环境开启,线上环境关闭

  • 线上排查问题的时候可以将 sourcemap 上传到错误监控系统

source map 关键字

  • eval: 使⽤ eval 包裹模块代码
  • source map: 产⽣.map ⽂件
  • cheap: 不包含列信息
  • inline: 将.map 作为 DataURI 嵌⼊,不单独生成.map 文件
  • module:包含 loader 的 sourcemap

基础库分离

思路:将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中

方法:使用 html-webpack-externals-plugin

image.png

利用 SplitChunksPlugin 进行公共脚本分离

Webpack4 内置的,替代 CommonsChunkPlugin 插件

chunks 参数说明:

  • async 异步引⼊的库进⾏分离(默认)
  • initial 同步引⼊的库进⾏分离
  • all 所有引⼊的库进⾏分离(推荐)
module.exports = {
  optimization: {
    splitChunks: {
      chunks: "async",
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: "~",
      name: true,
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        }
      }
    }
  }
};

利用 SplitChunksPlugin 分离基础包

test: 匹配出需要分离的包

module.exports = {
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /(react|react-dom)/,
          name: "vendors",
          chunks: "all"
        }
      }
    }
  }
};

利⽤ SplitChunksPlugin 分离页面公共文件

minChunks: 设置最小引⽤次数为 2 次

minuSize: 分离的包体积的大小

module.exports = {
  optimization: {
    splitChunks: {
      minSize: 0,
      cacheGroups: {
        commons: {
          name: "commons",
          chunks: "all",
          minChunks: 2
        }
      }
    }
  }
};

tree shaking(摇树优化)

概念:1 个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle 里面去,tree shaking 就是只把用到的⽅法打入 bundle ,没用到的方法会在 uglify 阶段被擦除掉

使用:webpack 默认⽀持,在 .babelrc 里设置 modules: false 即可

  • production mode 的情况下默认开启

要求:必须是 ES6 的语法,CJS 的方式不⽀持

DCE (Dead code elimination)

  • 代码不会被执行,不可到达
  • 代码执行的结果不会被用到
  • 代码只会影响死变量(只写不读)
if (false) {
  console.log("这段代码永远不会执行");
}

Tree-shaking 原理

利⽤ ES6 模块的特点:

  • 只能作为模块顶层的语句出现
  • import 的模块名只能是字符串常量
  • import binding 是 immutable 的

代码擦除: uglify 阶段删除无用代码

现象:构建后的代码存在大量闭包代码

image.png

会导致什么问题

⼤量作用域包裹代码,导致体积增大(模块越多越明显)。运行代码时创建的函数作用域变多,内存开销变大

模块转换分析

image.png 结论:

  • 被 webpack 转换后的模块会带上⼀层包裹
  • import 会被转换成 __webpack_require

进⼀步分析 webpack 的模块机制

image.png 分析:

  • 打包出来的是⼀个 IIFE (匿名闭包)
  • modules 是⼀个数组,每⼀项是⼀个模块初始化函数
  • __webpack_require 用来加载模块,返回 module.exports
  • 通过 WEBPACK_REQUIRE_METHOD(0) 启动程序

webpack 构建速度和体积优化策略

初级分析:使用 webpack 内置的 stats

stats: 构建的统计信息

package.json 中使用 stats

image.png

速度分析:使用 speed-measure-webpack-plugin

image.png 可以看到每个 loader 和插件执行耗时

image.png

速度分析插件作用

分析整个打包总耗时

每个插件和loader的耗时情况

webpack-bundle-analyzer 分析体积

image.png

image.png

可以分析哪些问题?

依赖的第三方模块文件大小

业务里面的组件代码大小

使用高版本的 webpack 和 Node.js

image.png

使用 webpack4:优化原因

V8 带来的优化(for of 替代 forEach、Map 和 Set 替代 Object、includes 替代 indexOf)

默认使用更快的 md4 hash 算法

webpacks AST 可以直接从 loader 传递给 AST,减少解析时间

使用字符串方法替代正则表达式

多进程/多实例构建:资源并行解析可选方案

image.png

多进程/多实例:使用 HappyPack 解析资源

原理:每次 webapck 解析一个模块,HappyPack 会将它及它的依赖分配给 worker 线程中

image.png

多进程/多实例:使用 thread-loader 解析资源

原理:每次 webpack 解析一个模块,thread- loader 会将它及它的依赖分配给 worker 线程中

image.png

多进程/多实例:并行压缩

方法一:使用 parallel-uglify-plugin 插件

image.png

方法二:uglifyjs-webpack-plugin 开启 parallel 参数

image.png

方法三:terser-webpack-plugin 开启 parallel 参数

image.png

分包:设置 Externals

思路:将 react、react-dom 基础包通过 cdn 引入,不打入 bundle 中

方法:使用 html-webpack-externals- plugin

image.png

进一步分包:预编译资源模块

思路:将 react、react-dom、redux、react-redux 基础包和业务基础包打包成一个文件

方法:使用 DLLPlugin 进行分包,DllReferencePlugin 对 manifest.json 引用

使用 DLLPlugin 进行分包

image.png

使用 DllReferencePlugin 引用 manifest.json

在 webpack.config.js 引入

image.png

引用效果

image.png

缓存

目的:提升二次构建速度

缓存思路:

  • babel-loader 开启缓存
  • terser-webpack-plugin 开启缓存
  • 使用 cache-loader 或者 hard-source-webpack-plugin

缩小构建目标

目的:尽可能的少构建模块

比如 babel-loader 不解析 node_modules

image.png

减少文件搜索范围

优化 resolve.modules 配置(减少模块搜索层级)

优化 resolve.mainFields 配置

优化 resolve.extensions 配置 合理使用 alias

image.png

图片压缩

要求:基于 Node 库的 imagemin 或者 tinypng API

使用:配置 image-webpack-loader

image.png

Imagemin的优点分析

  • 有很多定制选项
  • 可以引入更多第三方优化插件,例如pngquant
  • 可以处理多种图片格式

Imagemin的压缩原理

  • pngquant: 是一款PNG压缩器,通过将图像转换为具有alpha通道(通常比24/32位PNG 文件小60-80%)的更高效的8位PNG格式,可显著减小文件大小。
  • pngcrush:其主要目的是通过尝试不同的压缩级别和PNG过滤方法来降低PNG IDAT数据 流的大小。
  • optipng:其设计灵感来自于pngcrush。optipng可将图像文件重新压缩为更小尺寸,而不 会丢失任何信息。
  • tinypng:也是将24位png文件转化为更小有索引的8位图片,同时所有非必要的metadata 也会被剥离掉

tree shaking(摇树优化)

概念:1 个模块可能有多个方法,只要其中的某个方法使用到了,则整个文件都会被打到 bundle 里面去,tree shaking 就是只把用到的方法打入 bundle ,没用到的方法会在 uglify 阶段被擦除掉

使用:webpack 默认支持,在 .babelrc 里设置 modules: false 即可。production mode的情况下默认开启

要求:必须是 ES6 的语法,CJS 的方式不支持

无用的 CSS 如何删除掉?

PurifyCSS: 遍历代码,识别已经用到的 CSS class

uncss: HTML 需要通过 jsdom 加载,所有的样式通过PostCSS解析,通过 document.querySelector 来识别在 html 文件里面不存在的选择器

在 webpack 中如何使用 PurifyCSS?

使用 purgecss-webpack-plugin

image.png