Webpack知识点学习与理解

9 阅读17分钟

1. 优化产物

更多内容 知识图谱

优化打包优先方案: 升级 node、升级 webpack、升级你的编译环境的硬件水平

始终使用最新 Webpack 版本,这算的上是性价比最高的优化手段之一了! 从 Webpack V3,到 V4,再到最新的 V5 版本,虽然构建功能在不断叠加增强,但性能反而不断得到优化提升, 这得益于 Webpack 开发团队始终重视构建性能,在各个大版本之间不厌其烦地重构核心实现,例如:

  • V3 到 V4 重写 Chunk 依赖逻辑,将原来的父子树状关系 调整为 ChunkGroup 表达的有序图关系,提升代码分包效率;
  • V4 到 V5 引入 cache 功能,支持将模块、模块关系图、产物等核心要素 持久化缓存到硬盘,减少重复工作。

A、 webpack可采用做法一

大致方向:优化搜索时间、缩小文件搜索范围、减少不必要的编译等方面

1. 使用 Tree Shaking   清除未引用代码

2. 启用压缩(Uglification)  如删除未使用的代码、缩短变量名等

3. 代码分割(Code Splitting)   可以把代码分成多个 bundle,然后按需加载,从而减少初始加载时间

4. 使用 Externals 减轻体积     
        将其中常用的第三库(比如vue、vue-router、vuex等)抽离出来,
        放置在CDN中,通过<script>来引入,减少打包文件体积
        `externals`:用于声明外部资源,Webpack 会直接忽略这部分资源,
        跳过这些资源的解析、打包操作
        
        比如echarts、element-ui其实都非常的大,externals 出去 提高编译时间
5. 利用缓存(Caching)  [contenthash] 替换 [hash] 或 [chunkhash] 来为输出文件命名,只有当文件内容改变时,文件名称才改变
6. 移除未使用的 CSS 自动去除未使用的 CSS
7. 优化图片   image-webpack-loader等图片压缩插件,可以减小图片文件的体积

B、 webpack可采用做法二

优化 loader 配置
合理使用 resolve.extensions // 解析到文件时自动添加拓展名 : extensions:[".warm",".mjs",".js",".json"]
优化 resolve.modules //
优化 resolve.alias // 别名配置
使用 DLLPlugin 插件 //vue-cil弃用 **webpack 4 有着比 dll 更好的打包性能**, dll其实是缓存
使用 cache-loader 
terser 启动多线程 (并行文件加载:
    -   多实例并行构建场景建议使用 Parallel-Webpack 实现并行;
    -   生产环境下还可配合 `terser-webpack-plugin` 的并行压缩功能,提升整体效率。)
合理使用 sourceMap
优化打包chunk-vendors.js, splitChunks: minSize: 20000, // 依赖包超过20000bit将被单独打包
生产环境的 source map,可以将其设置为 false 以加速生产环境构建,默认值是true
sourceMap: false,   //关掉sourcemap 会生成对于调试的完整的.map文件,但同时也会减慢打包速度
parallel: true, //使用多进程并行运行来提高构建速度。默认并发运行数:os.cpus().length - 1。
开启gizp压缩: compression webpack plugin

更多内容

好文:www.cnblogs.com/ypSharing/p…

压缩CSS

压缩CSS:css-loader?minimize、PurifyCSSPlugin: 
  需要配合 extract-text-webpack-plugin 使用,
  它主要的作用是可以去除没有用到的CSS代码,
  类似JSTree Shaking

  webpack4用mini-css-extract-plugin更优, 替代extract-text-webpack-plugin


  const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
  const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  module.exports = {
        //...
        module: {
          rules: [
            {
              test: /.css$/,
              // 注意,这里用的是 `MiniCssExtractPlugin.loader` 而不是 `style-loader`
              use: [MiniCssExtractPlugin.loader, "css-loader"],
            },
          ],
        },
        optimization: {
          minimize: true,
          minimizer: [
            // Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
            "...",
            new CssMinimizerPlugin(),
          ],
        },
        // 需要使用 `mini-css-extract-plugin` 将 CSS 代码抽取为单独文件
        // 才能命中 `css-minimizer-webpack-plugin` 默认的 `test` 规则
        plugins: [new MiniCssExtractPlugin()],
      };

  1. 
  css 独立拆包最大的好处就是 js 和 css 的改动,不会影响对方。
  比如我改了 js 文件并不会导致 css 文件的缓存失效
  而且现在它自动会配合optimization.splitChunks的配置,可以自定义拆分 css 文件


  - `mini-css-extract-plugin` 库同时提供 LoaderPlugin 组件,需要同时使用
  - `mini-css-extract-plugin` 不能与 `style-loader` 混用,否则报错,所以上述示例中第 9 行需要
    判断 `process.env.NODE_ENV` 环境变量决定使用那个 Loader
  - `mini-css-extract-plugin` 需要与 `html-webpack-plugin` 同时使用,才能将产物路径以 `link` 标签方式插入到 html 中

  2. optimize-css-assets-webpack-plugin 这个插件,它不仅能帮你压缩 css 还能优化你的代码
  webpack4 : 
  //配置
  optimization: {
    minimizer: [new OptimizeCSSAssetsPlugin()];
  }


  

2. 文件指纹

webpack 中有三种生成哈希值规则的方式,可以用来区分文件是否修改。

  • hash 与整个项目有关,项目里有文件修改,所有文件的哈希值都会变化。
  • chunkhash 与入口有关,同一入口的文件被视为一个整体,当其中一个文件修改时,同入口的所有文件哈希值发生改变。
  • contenthash 只与文件内容有关,文件内容发生改变,才会更改该文件的哈希值

文件指纹

A1. hash没有缓存

    2. chunkhash : 有缓存但是如果修改css, 也会重新打包

    若对应css改变,则构建出来的bundle文件的的chunkhash也会随之改变,但入口文件xx.js的内容并没有改变,所以没有完全达到缓存意义。

    3. contenthash :更优的缓存
    contenthash 你可以简单理解为是 moduleId + content 所生成的 hash

B、
    给css也加contenthash
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[contenthash].css',
        }),
    ],



C、
    通过file-loader或url-loader来处理图片,这时设置图片的hash,需要注意的是[hash]表示的是根据文件内容生成hash

    `file-loader`、`url-loader`、`raw-loader` 都并不局限于处理图片,
    它们还可以被用于加载任意类型的多媒体或文本文件,使用频率极高,几乎已经成为标配组件!
    所以 Webpack5 直接内置了这些能力,开箱即可使用

3、Loader 和 Plugin 的区别

		  Loader				Plugin
典型应用	处理 CSS、图片、字体等资源	  代码压缩、资源管理、环境变量注入等
配置方式	在 module.rules 中配置	     在 plugins 中配置
作用	    转换模块的源代码			  扩展 Webpack 的功能
输入/输出	接收源文件,返回转换后的内容   不直接处理文件,而是操作构建过程
Loader:用于转换模块的源代码。适合处理文件级别的任务(如 CSS、图片、字体等)。通过 module.rules 配置。
Plugin:用于扩展 Webpack 的功能。适合处理构建过程级别的任务(如代码压缩、资源优化等)。通过 plugins 配置。


style-loader: 将css添加到DOM的内联样式标签style里
css-loader :允许将css文件通过require的方式引入,并返回css代码
less-loader: 处理less
sass-loader: 处理sass
postcss-loader: 用postcss来处理CSS
autoprefixer-loader: 处理CSS3属性前缀,已被弃用,建议直接使用postcss
file-loader: 分发文件到output目录并返回相对路径
url-loader: 和file-loader类似,但是当文件小于设定的limit时可以返回一个Data Url
html-minify-loader: 压缩HTML
babel-loader :用babel来转换ES6文件到ES

https://blog.csdn.net/weixin_44869002/article/details/105831784
sourse map 可以知道文件位置
css.sourceMap时,我们最好关闭css.extract

Plugin:

Webpack 插件在代码形态上是一个带 apply 方法的对象, 我们可以在 apply 函数中注册各式各样的 Hook 回调, 监听对应事件,之后在回调中修改上下文状态,达到干预 Webpack 构建逻辑的效果

Webpack 虽然功能非常复杂,但本质上还是一个 Node 程序

Webpack Hook 底层的 Tapable 框架

  1. 初始化阶段:修整配置参数,创建 Compiler、Compilation 等基础对象, 并初始化插件及若干内置工厂、工具类, 并最终根据 entry 配置,找到所有入口模块;
  2. 构建阶段:从 entry 文件开始,调用 loader 将模块 转译为 JavaScript 代码,调用 [Acorn] 将代码转换为 AST 结构,遍历 AST 从中找出该模块依赖的模块; 之后 递归 遍历所有依赖模块,找出依赖的依赖,直至遍历所有项目资源后, 构建出完整的 **[模块依赖关系图]
  3. 生成阶段:根据 entry 配置,将模块组装为一个个 Chunk 对象, 之后调用一系列 Template 工厂类翻译 Chunk 代码并封装为 Asset,最后写出到文件系统

初始化」的重点是根据用户配置设置好构建环境; 「构建阶段」则重在解读文件输入与文件依赖关系; 最后在「生成阶段」按规则组织、包装模块,并翻译为适合能够直接运行的产物包。

为什么需要loader:

loader 本身是一个函数 webpack开箱即用只支持JS和JSON两种文件类型,需要loader将其他类型的文件转为webpack能够处理的文件类型,并添加到依赖图中完成打包。

Plugins: juejin.cn/post/691017… 插件就是一个含有apply方法的类,在apply方法中会传入compiler(即webpack实例)。 我们可以通过调用compiler中暴露的钩子函数,完成在webpack构造过程的相关功能。

class MyExampleWebpackPlugin { apply(compiler) { compiler.hooks.emit.tapAsync( 'MyExampleWebpackPlugin', (compilation, callback) => { ... callback(); } ); } }

Webpack配置文件中,通过entry设置编译入口文件,在output设置编译后的文件目录,通过loader引入其他类型文件,通过plugin来实现一些loader无法达到的功能。

filename:对应于entry里面生成出来的文件名。 chunkFilename:chunkFilename就是未被列在entry中,但有些场景需要被打包出来的文件命名配置。比如按需加载(异步)模块的时候。

Lodash-ES

思路 1:使用 Lodash-ES 替代 Lodash
lodash-es 是 lodash 的 es modules 版本 ,是着具备 ES6 模块化的版本,体积小
如果是使用webpack来进行打包的话, 
我们在使用lodash库时, 尽量通过lodash-es来进行导入操作,可以减轻最终生产环境的代码量

思路 2: 使用 babel-plugin-lodash
插件 babel-plugin-lodash 和 lodash-webpack-plugin 能够在打包时去掉不必要的 lodash 代码,减小产物体积。

为了在不修改现有代码的情况下实现按需加载,可以使用 babel-plugin-lodash 插件。
该插件的原理是将 import _ from 'lodash' 转换为按需引入的方式,
例如 import { deepClone } from 'lodash/deepClone'Vite 的配置文件中(通常是 vite.config.js),你需要集成 Babel 插件。
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import babel from '@rollup/plugin-babel';

export default defineConfig({
  plugins: [
    vue(),
    babel({
      babelHelpers: 'bundled',
      presets: ['@babel/preset-env'],
      plugins: ['babel-plugin-lodash'],
      extensions: ['.js', '.jsx', '.ts', '.tsx', '.vue'],
      exclude: 'node_modules/**', // 排除 node_modules 目录下的文件
    }),
  ],
});

babelHelpers: 'bundled' 是指定 Babel 如何处理辅助函数的选项。bundled 选项告诉 Babel 将辅助函数包含在捆绑包中。
presets: ['@babel/preset-env'] 是 Babel 的预设配置,帮助处理 ES6+ 语法。

在开发阶段保留原始的引入方式,避免构建速度下降。
在生产环境中,通过 Babel 插件进行按需加载,以减小最终打包的体积。

链接:https://juejin.cn/post/7439324075116642355


在整个优化过程中,我们总结了以下几点经验:

逐步替换,分阶段实施:面对大规模的代码替换,不要一蹴而就,而是分阶段实施,并在每个阶段进行详细测试。
自动化测试的重要性:在大规模代码变更后,自动化测试能帮助我们快速验证功能的正确性,避免因手动测试不彻底而导致的上线问题。
工具链的理解与合理使用:深刻理解工具(如 Babel)的工作原理,有助于我们在做技术选型时做出更加合适的决定。

4、使用webpack如何替换字符串

方案	适用场景	配置复杂度	性能影响	插件依赖
DefinePlugin	简单宏替换	★★☆	★★	无
ReplacePlugin	复杂模式匹配	★★★	★★	✔️
(replace-webpack-plugin)
(string-replace-webpack-plugin)
HtmlWebpackPlugin	HTML 文件占位符替换	★★☆	★★	✔️
Environment Variables	环境变量注入	★★☆	★★	✔️(dotenv)
Custom Loader	特定文件深度处理	★★★★	★★★	✔️

5、Webpack 和 vite 的原理 以及热更新

(热更新就是更新受影响的模块,但是不刷新页面)

webpack

1. Webpack 核心是项目整体打包,将多个文件打包成一个
    首次加载时间过长,bundle文件	
    webpack热更新复杂,要对css,js不同类型的模块针对性不同配置

热更新慢可能的原因:

  1. 没有使用合理的 Devtool souce map 导致

  2. 没有正确使用 exclude/include 处理了不需要处理的如node_modules

  3. 在开发环境不要压缩代码UglifyJs、提取 css、babel polyfill、计算文件 hash 等不需要的操作

Webpack HMR 特性的执行过程并不复杂,核心:

  1. 使用 webpack-dev-server (后面简称 WDS)托管静态资源,同时以 Runtime 方式注入一段处理 HMR 逻辑的客户端代码;
  2. 浏览器加载页面后,与 WDS 建立 WebSocket 连接;
  3. Webpack 监听到文件变化后,增量构建发生变更的模块,并通过 WebSocket 发送 hash 事件;
  4. 浏览器接收到 hash 事件后,请求 manifest 资源文件,确认增量变更范围;
  5. 浏览器加载发生变更的增量模块;
  6. Webpack 运行时触发变更模块的 module.hot.accept 回调,执行代码变更逻辑;
  7. done。 2. vite核心是基于esm的开发服务器,浏览器请求资源,对应发送资源,启动快,

Webpack 的 HMR 特性底层有两个重点,

一是监听文件变化并通过 WebSocket 发送变更消息;

二是需要客户端配合,通过 module.hot.accept 接口定制特定模块的热替换规则。

Webpack 的热更新基于文件监听和模块依赖图,
适合需要兼容性和复杂构建逻辑的项目, 但随着项目规模增大,更新速度会变慢。
       
       
       
       

Vite

Vite 的热更新基于 ESM 和浏览器原生模块系统,更新速度极快,适合现代浏览器环境的大型项目。

生产环境中,Vite 使用 Rollup 进行打包。Rollup 同样支持将 CommonJS 模块转换为 ESM。
开发环境:使用 ESBuild 动态将 CommonJS 模块转换为 ESM。
生产环境:使用 Rollup@rollup/plugin-commonjs 插件打包 CommonJS 模块。
生产构建会进行预构建,就是将非esm转为esm
  esm浏览器原生支持:通过 <script type="module"> 加载。

esbuild 快得惊人,并且已经是一个在构建库方面比较出色的工具,
但一些针对构建应用的重要功能仍然还在持续开发中 —— 特别是代码分割和 CSS 处理方面

所以生产环境用Rollup 在应用打包方面更加成熟和灵活

6. Vue 3 的 Treeshaking

未使用的导出、未引用的代码、副作用无关的代码

1. 启用 Tree Shaking

// vite.config.js
export default {
  optimizeDeps: {
    include: ['vue'], // 确保 Vue 核心库被预优化
    exclude: ['unused-package'], // 排除未使用的依赖
  },
};

若通过 import * as 导入整个模块,Tree Shaking 无法识别具体使用了哪些内容。
​解决方案:始终按需导入(如 import { func } from 'module')。

// webpack.config.js (Vue 3 项目)
module.exports = {
  configureWebpack: {
    output: {
      moduleFilename: 'esm.js', // 强制使用 ES6 Modules 格式
    },
    experiments: {
      outputModule: true, // Webpack 5+ 需要此配置
    },
  },
};

2. 最佳实践:

使用 ES6 Modules 按需导入。
配合 TypeScript 获取更精准的静态分析。
利用动态导入(import())实现代码分割。
通过构建工具(如 Vite/Webpack)启用 Tree Shaking。

3. 副作用

大致可以理解成:一个函数会、或者可能会对函数外部变量产生影响的行为

    eg:
      function go (url) {
          window.location.href = url
      }

      "sideEffects": [
      "./src/some-module.js"
      ]

    "sideEffects": false , 都没有副作用,可以树摇

4. Dead Code

未使用的导出、未引用的代码、副作用无关的代码
    // eg: data.js
    const data = [1, 2, 3];

    export function getData() {
        return data;
    }

    const result = data.reduce((acc, val) => acc + val, 0);
    console.log(result);

    虽然 reduce 函数被调用了,并且计算出了一个结果,但这个结果并没有被导出或者在其他地方使用。
    因此,这段代码对程序的行为没有任何影响(即没有副作用)

    https://juejin.cn/post/7276696853893546036

5. babel使tree-sharking失效

默认 webpack 是支持Tree-Shaking的,但在你的项目中可能会因为babel的原因导致它失效

Tree Shaking这个功能是基于ES6 modules 的静态特性检测,来找出未使用的代码,
所以如果你使用了 babel 插件的时候,如:babel-preset-env,
它默认会将模块打包成commonjs,这样就会让Tree Shaking失效了。

6.启动 Tree Shaking 功能必须同时满足两个条件

在 Webpack 中,:

  • 配置 optimization.usedExportstrue,标记模块导入导出列表;
  • 启动代码优化功能,可以通过如下方式实现:
    • 配置 mode = production
    • 配置 optimization.minimize = true
    • 提供 optimization.minimizer 数组

// webpack.config.js module.exports = { mode: "production", optimization: { usedExports: true, }, };

Webpack 会对所有使用 ESM 方案的模块启动 Tree-Shaking

7. Tree-shaking 的实现

一是需要先 「**标记**」 出模块导出值中哪些没有被用过;
二是使用代码压缩插件 —— 如 [Terser]删掉这些没被用到的导出变量。

> 标记功能需要配置 `optimization.usedExports = true` 开启

标记的效果就是删除那些没有被其它模块使用的“**导出语句**”

ESM 下模块之间的依赖关系是高度确定的,与运行状态无关,
编译工具只需要对 ESM 模块做静态分析,就可以从代码字面量中推断出
哪些模块值未曾被其它模块使用,
这是实现 Tree Shaking 技术的必要条件

8. 实践

与赋值语句类似,JavaScript 中的函数调用语句也可能产生副作用
Webpack 并不会对函数调用做 Tree Shaking 操作
添加 `/*#__PURE__*/` 备注,明确告诉 Webpack 该次函数调用并不会对上下文环境产生副作用

实践:始终使用 ESM
实践:避免无意义的赋值
实践:使用 `#pure` 标注纯函数调用
实践:禁止 Babel 转译模块导入导出语句
实践:优化导出值的粒度
实践:使用支持 Tree Shaking 的包: 使用 `lodash-es` 替代 `lodash`
实践:在异步模块中使用 Tree-Shaking

Tree-Shaking 并不能完美删除所有无效的模块导出,
需要我们在业务代码中遵循若干最佳实践规则,帮助 Tree-Shaking 更好地运行

7、 Sourcemap

  核心价值:在优化构建和高效调试之间构建桥梁

  Sourcemap 是一种高效的位置映射算法,它将产物到源码之间的位置关系表达为 
  `mappings` 分层设计与 VLQ 编码,
  再通过 Chrome、Safari、VS Code、Sentry 
  等工具异地还原为接近开发状态的源码形式

  [VLQ] “只存差值+压缩成整数”
  是一种将整数数值转换为 Base64 的编码算法,
  它先将任意大的整数转换为一系列六位字节码,再按 Base64 规则转换为一串可见字符。


  在开发环境中,我们需要实时重新加载或热模块替换能力的server,相对完整的source map。
  但是在生产环境中,我们更加关注更小的bundle(压缩输出), 更轻量的source map, 还有更优化的资源等

  开发环境,基础构建需要重新加载,热更新功能,需要尽可能完整的source map
  生产环境,基础构建需要压缩文件,文件指纹功能,需要尽可能小的source map

  当 `devtool` 包含 `cheap` 时,生成的 Sourcemap 内容会抛弃**列**维度的信息

  module : cheap-module-eval-source-map显示loader前的代码,
           cheap-source-map 则是经过 babel-loader 编译处理的内容


  - 对于开发环境,适合使用:
    - `eval`:速度极快,但只能看到原始文件结构,看不到打包前的代码内容;
    - `cheap-eval-source-map`:速度比较快,可以看到打包前的代码内容,但看不到 loader 处理之前的源码;
    - `cheap-module-eval-source-map`:速度比较快,可以看到 loader 处理之前的源码,不过定位不到列级别;
    - `eval-source-map`:初次编译较慢,但定位精度最高;
    

  - 对于生产环境,则适合使用:
    - `source-map`:信息最完整,但安全性最低,外部用户可轻易获取到压缩、混淆之前的源码,慎重使用;
    - `hidden-source-map`:信息较完整,安全性较低,外部用户获取到 `.map` 文件地址时依然可以拿到源码;
    - `nosources-source-map`:源码信息缺失,但安全性较高,需要配合 Sentry 等工具实现完整的 Sourcemap 映射

8、babel

ES6/ES7的代码转化成指定浏览器能支持的代码

babel-runtime 是一个包含 babel 模块化运行时助手的库。
babel-runtime 的主要作用就是
    将这些可能被重用的代码抽取成单独的模块,以避免在每个文件中重复出现相同的代码。
    它通过模块导入的方式引入这些功能,从而避免了对全局作用域的修改或污染

使用 babel-runtime 通常需要配合 babel-plugin-transform-runtime 插件一起使用

9、CommonJS 和 ES Module

1、CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。

2、CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。

3、CommonJs 是单个值导出,ES6 Module可以导出多个

4、CommonJs 是动态语法可以写在判断里,ES6 Module 静态语法只能写在顶层

5、CommonJs 的 this 是当前模块,ES6 Module的 this 是 undefined


ES Module : 
1. 编译时加载:esm 是编译时加载,也就是只有所有import的模块都递归加载完成,才会开始执行
2. 确定的导入导出
3. 无副作用的模块导入

ESM 下模块之间的依赖关系是高度确定的,与运行状态无关,编译工具只需要对 ESM 模块做静态分析

ES6 Modules 不支持循环引用(编译时报错)​

10 、webpack4内置的代码分割策略:

新的 chunk 是否被共享或者是来自 node_modules 的模块
新的 chunk 体积在压缩之前是否大于 30kb
 (个人理解:文件压缩后会更小,如果只有几个页面用到,没必要拆出来,拆出来会多耗费一次http请求,直接打包在一起。
 但是如果每个页面都用到这个,就可以打包合并成一个component-vendor.js的包)
按需加载 chunk 的并发请求数量小于等于 5 个
页面初始加载时的并发请求数量小于等于 3 个

作者:花裤衩
链接:https://juejin.cn/post/6844903652956585992
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

我们现在的策略是按照体积大小、共用率、更新频率重新划分我们的包,使其尽可能的利用浏览器缓存

其实优化就是一个博弈的过程,是让 a bundle 大一点还是 b? 是让首次加载快一点还是让 cache 的利用率高一点? 
但有一点要切记,拆包的时候不要过分的追求颗粒化,什么都单独的打成一个 bundle,
不然你一个页面可能需要加载十几个.js文件,如果你还不是HTTP/2的情况下,请求的阻塞还是很明显的
(受限于浏览器并发请求数)。所以还是那句话资源的加载策略并没什么完全的方案,
都需要结合自己的项目找到最合适的拆包策略。

拆包策略:

基础类库 chunk-libs
UI 组件库 chunk-elementUI
自定义共用组件/函数 chunk-commons
低频组件 chunk-eachrts/chunk-xlsx等
业务代码 lazy-loading xxxx.js

11、Chunk

https://juejin.cn/post/6844903889393680392

Webpack的打包是从一个入口文件开始,也可以说是入口模块, 入口模块引用这其他模块,模块再引用模块。 Webpack通过引用关系逐个打包模块,这些module就形成了一个Chunk。

Chunk是过程中的代码块,Bundle是结果的代码块。

产生Chunk的三种途径 entry入口 异步加载模块 代码分割(code spliting)

Webpack 默认会将以下三种模块做分包处理:

  • Initial Chunk:entry 模块及相应子模块打包成 Initial Chunk;
  • Async Chunk:通过 import('./xx') 等语句导入的异步模块及 相应子模块组成的 Async Chunk;
  • Runtime Chunk:运行时代码抽离成 Runtime Chunk,可通过 [entry.runtime]配置项实现。

模块重复打包、资源冗余 & 低效缓存

Webpack 专门提供了 SplitChunksPlugin 插件, 用于实现更灵活、可配置的分包,提升应用性能

12、webpack插件

TerserWebpackPlugin,还有其他一些替代方案可以用来压缩和最小化 JavaScript 代码, 例如 UglifyJSWebpackPlugin 和 CleanCSSWebpackPlugin

之前压缩使用的UglifyjsWebpackPlugin,但其不支持ES6+语法,所以替换为TerserPlugin

webpack-obfuscator

blog.csdn.net/a123456234/…

  1. uglifyjs-webpack-plugin ( webpack4 不需要uglifyjs-webpack-plugin) 压缩 JavaScript 代码的 Webpack 插件 配置选项说明: test: 匹配需要压缩的文件。 exclude: 排除不需要压缩的文件或目录。 cache: 是否启用缓存,提高构建速度。 parallel: 是否启用并行压缩,提高压缩效率。 sourceMap: 是否生成 source map 文件。 uglifyOptions: UglifyJS 的具体配置选项,如压缩选项、输出选项等。
打包时去除打印信息

webpack4 不需要uglifyjs-webpack-plugin了 production 模式下,由于提供了splitChunks和minimize,所以基本零配置, 代码就会自动分割、压缩、优化,同时 webpack 也会自动帮你 Scope hoisting 和 Tree-shaking

module.exports = {
optimization: {
  minimizer: [
    new UglifyJsPlugin({
      cache: true,
      parallel: true,
    }),
  ],
},

};

  1. 经过SideEffectsFlagPlugin处理后, 没有副作用且没有被使用的模块都会被打上sideEffectFree标记

    // webpack.pord.config.js module.exports = { optimization: { sideEffects: true } };

  2. terser-webpack-plugin来进行压缩

    // webpack.pord.config.js module.exports = { optimization: { minimize: true } }

    // js压缩 const TerserPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimizer: [ new TerserPlugin({ cache: true, parallel: true, // 多进程并发运行 sourceMap: true, // Must be set to true if using source-maps in production terserOptions: { // github.com/webpack-con… } }), ] } }

    多数情况下使用默认 Terser 配置即可 module.exports = { //... optimization: { minimize: true } }; 手动创建 [terser-webpack-plugin] 实例并传入压缩配置实现更精细的压缩功能, 比如去除打印,重复声明

  3. 开启gizp压缩 const CompressionWebpackPlugin = require('compression-webpack-plugin')//gzip压缩

new CompressionWebpackPlugin({
    filename: '[path].gz[query]',
    algorithm: 'gzip',
    test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
    threshold: 10240,
    minRatio: 0.8,
})

5、 图片压缩【image-webpack-loader】使用 图像压缩是一种非常耗时的操作,建议只在生产环境下开启 6、 css-minimizer-webpack-plugin: new CssMinimizerPlugin(), css压缩需要使用插件CssMinimizerWebpack 7. speed-measure-webpack-plugin 这个插件,它能监控 webpack 每一步操作的耗时 8. html-loader 编译后产生的字符串, 会由 html-webpack-plugin 储存为 html 文件到输出目录 那些第三方资源的CDN,请注意先后顺序 通过 html-webpack-plugin注入到 index.html之中

9、[SpeedMeasureWebpackPlugin] 插件能够统计出各个 Loader、 插件的处理耗时,开发者可以根据这些数据分析出哪些类型的文件处理更耗时间 10. 使用 HtmlMinifierTerser 压缩 HTML React、Vue 等 MVVM 框架,这衍生出来的 一个副作用是原生 HTML 的开发需求越来越少,HTML 代码占比越来越低,所以大多数现代 Web 项目中其实并不需要考虑为 HTML 配置代码压缩工作流

十六、、 // 别名配置 Object.assign(config, { // 开发生产共同配置 resolve: { alias: { '@': path.resolve(__dirname, './src'), '@c': path.resolve(__dirname, './src/components'), '@p': path.resolve(__dirname, './src/pages') } } }),

config.output.filename = `[name].${Version}.${Timestamp}.js`  //打包生成的文件
config.output.chunkFilename = `[name].${Version}.${Timestamp}.js`


//兼容性

// lib-flexible
  postcss: {
      plugins: [
          //remUnit这个配置项的数值是多少呢??? 通常我们是根据设计图来定这个值,原因很简单,便于开发。
          //假如设计图给的宽度是750,我们通常就会把remUnit设置为75,这样我们写样式时,可以直接按照设计图标注的宽高来1:1还原开发。
          require('postcss-px2rem')({
              remUnit: 37.5
          })
      ]
  }

13、webpack5特性-持久化缓存

1. [持久化缓存]
Webpack 5 最令人振奋的特性之一,它能够将首次构建的过程与结果数据持久化保存到本地文件系统,
在下次执行构建时跳过解析、链接、编译等一系列非常消耗性能的操作,
直接复用上次的 Module/ModuleGraph/Chunk 对象数据,迅速构建出最终产物。

cache: {
    type: 'filesystem'
},

首次构建出的 Module、Chunk、ModuleGraph 等对象序列化后保存到硬盘中
后面再运行的时候,就可以跳过许多耗时的编译动作,直接复用缓存数据。


2. Webpack4:使用 `cache-loader`
修改配置,注意必须将 `cache-loader` 放在 `loader` 数组首位
`cache-loader` 只缓存了 Loader 执行结果,缓存范围与精度不如 Webpack5 内置
的缓存功能,所以性能效果相对较低


3. [hard-source-webpack-plugin]
 也是一种实现缓存功能的第三方组件,
与 `cache-loader` 不同的是,它并不仅仅缓存了 Loader 运行结果,
还保存了 Webpack 构建过程中许多中间数据,包括:模块、模块关系、
模块 Resolve 结果、Chunks、Assets 等,效果几乎与 Webpack5 自带的 Cache 对齐


缓存路径:`node_module/.cache`


Webpack5 持久化缓存用法简单,且优化效果非常出色,确实是一个特别让人振奋的新功能,
甚至特定情况下能够让构建性能达到 Unbundle 方案的量级,
妥妥的 Webpack 性能优化利器!

而在 Webpack4 中,我们还可以借助下述组件实现缓存优化:

- `cache-loader`:针对 Loader 运行结果的通用缓存方案;
- `hard-source-webpack-plugin`:针对 Webpack 全生命周期的通用缓存方案;
- `babel-loader`:针对 Babel 工具的专用缓存能力;
- `eslint-loader`/`eslint-webpack-plugin`:针对 ESLint 的专用缓存方案;
- `stylelint-webpack-plugin`:针对 StyleLint 的专用缓存方案。

这些方案各有特色,但都无可置疑地能有效提升编译性能,建议你在尝试做性能优化时优先选用。

14、并行文件加载

  1. HappyPack 虽然确实能有效提升 Webpack 的打包构建速度,但它有一些明显的缺点:
  • 作者已经明确表示不会继续维护,扩展性与稳定性缺乏保障, 随着 Webpack 本身的发展迭代,可以预见总有一天 HappyPack 无法完全兼容 Webpack;
  • HappyPack 底层以自己的方式重新实现了加载器逻辑,源码与使用方法 都不如 Thread-loader 清爽简单,而且会导致一些意想不到的兼容性问题, 如 awesome-typescript-loader
  • HappyPack 主要作用于文件加载阶段,并不会影响后续的产物生成、合并、优化等功能, 性能收益有限。
  1. Thread-loader: Thread-loader 放在 use 数组首位,确保最先运行

一些 Loader 无法与 Thread-loader 共同使用,大家需要仔细加以甄别、测试

  1. Parallel-Webpack: Thread-loader、HappyPack 这类组件所提供的并行能力都仅作用于文件加载过程, 对后续 AST 解析、依赖收集、打包、优化代码等过程均没有影响,理论收益还是比较有限的。 对此,社区还提供了另一种并行度更高, 以多个独立进程运行 Webpack 实例的方案 —— [Parallel-Webpack]

parallel-webpack 相对于 Thread-loader、HappyPack 有更高的并行度, 但进程实例之间并没有做任何形式的通讯, 这可能导致相同的工作在不同进程 —— 或者说不同 CPU 核上被重复执行

这种技术实现,对单 entry 的项目没有任何收益,只会徒增进程创建成本; 但特别适合 MPA 等多 entry 场景, 或者需要同时编译出 esm、umd、amd 等多种产物形态的类库场景

Webpack4 默认使用 [Uglify-js] 实现代码压缩,Webpack5 之后则升级为 [Terser]

  • 对于 Webpack4 之前的项目,可以使用 HappyPack 实现并行文件加载;
  • Webpack4 之后则建议使用 Thread-loader;
  • 多实例并行构建场景建议使用 Parallel-Webpack 实现并行;
  • 生产环境下还可配合 terser-webpack-plugin 的并行压缩功能,提升整体效率。

理论上,并行确实能够提升系统运行效率,但 Node 单线程架构下, 所谓的并行计算都只能依托与派生子进程执行, 而创建进程这个动作本身就有不小的消耗 —— 大约 600ms

对于小型项目,构建成本可能可能很低, 引入多进程技术反而导致整体成本增加, 因此建议大家按实际需求斟酌使用上述多进程方案

15.开发模式禁用产物优化

Webpack 提供了许多产物优化功能,例如:Tree-Shaking、SplitChunks、Minimizer 等, 这些能力能够有效减少最终产物的尺寸,提升生产环境下的运行性能,但这些优化在开发环境中意义不大,反而会增加构建器的负担(都是性能大户)。

因此,开发模式下建议关闭这一类优化功能,具体措施:

  • 确保 mode='development'mode = 'none',关闭默认优化策略;
  • optimization.minimize 保持默认值或 false,关闭代码压缩;
  • optimization.concatenateModules 保持默认值或 false,关闭模块合并;
  • optimization.splitChunks 保持默认值或 false,关闭代码分包;
  • optimization.usedExports 保持默认值或 false,关闭 Tree-shaking 功能; 最终,建议开发环境配置如:

module.exports = { // ... mode: "development", optimization: { removeAvailableModules: false, removeEmptyChunks: false, splitChunks: false, minimize: false, concatenateModules: false, usedExports: false, }, };

最小化 watch 监控范围 跳过 TS 类型检查 优化 ESLint 性能 慎用 source-map: Webpack 提供了 devtool 选项, 可以配置 evalsource-mapcheap-source-map 等值, 不考虑其它因素的情况下,最佳实践: - 开发环境使用 eval ,确保最佳编译速度; - 生产环境使用 source-map,获取最高质量。 设置 resolve 缩小搜索范围 Webpack5 中,resolve.extensions 默认值为 ['.js', '.json', '.wasm']

  - 修改 `resolve.extensions` 配置项,减少匹配次数;
  - 代码中尽量补齐文件后缀名;
  - 设置 `resolve.enforceExtension = true` ,
  强制要求开发者提供明确的模块后缀名,不过这种做法侵入性太强,不太推荐。


  Webpack 这一逐层查找的逻辑大多数情况下实用性并不高,
  开发者可以通过修改 `resolve.modules` 配置项,主动关闭逐层搜索功能

  const path = require('path');

  module.exports = {
    //...
    resolve: {
      modules: [path.resolve(__dirname, 'node_modules')],
    },
  };


  除了缓存、多进程构建这一类大杀器之外,还可以通过控制构建范围、能力等方式
  尽可能减少各个环节的耗时,包括文中介绍的:

  - 使用最新 Webpack、Node 版本;
  - 约束 Loader 执行范围;
  - 使用 `noParse` 跳过文件编译等。

  如果下次再遇到性能问题,建议可以先试着分析哪些环节占用时长更多,
  然后有针对性的实施各项优化。

16. 为什么要分包

Webpack 默认会将尽可能多的模块代码打包在一起,
优点是能减少最终页面的 HTTP 请求数,但缺点也很明显:

1. 页面初始代码包过大,影响首屏渲染性能;
2. 无法有效应用浏览器缓存,特别对于 NPM 包这类变动较少的代码,
业务代码哪怕改了一行都会导致 NPM 包缓存失效。

模块重复打包、资源冗余 & 低效缓存

为此,Webpack 提供了 `SplitChunksPlugin` 插件,
专门用于根据产物包的体积、引用次数等做分包优化,规避上述问题,特别适合生产环境使用。

17. SplitChunksPlugin

1. `optimization.splitChunks.cacheGroup` 概念,
用于对不同特点的资源做分组处理,并为这些分组设置更有针对性的分包规则

2. 提供一些开箱即用的分包特性:

- `node_modules` 资源会命中 `defaultVendors` 规则,并被单独打包;
- 只有包体超过 20kb 的 Chunk 才会被单独打包;
- 加载 Async Chunk 所需请求数不得超过 30;
- 加载 Initial Chunk 所需请求数不得超过 30


3. 设置分包范围:
- 字符串 `'all'` :对 Initial Chunk 与 Async Chunk 都生效,建议优先使用该值;
- 字符串 `'initial'` :只对 Initial Chunk 生效;
- 字符串 `'async'` :只对 Async Chunk 生效;
- 函数 `(chunk) => boolean` :该函数返回 `true` 时生效;

module.exports = {
//...
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
}

设置为 `all` 效果最佳,此时 Initial Chunk、Async Chunk 
都会被 `SplitChunksPlugin` 插件优化


4. 根据 Module 使用频率分包:
  optimization: {
  splitChunks: {
    // 设定引用次数超过 2 的模块才进行分包
    minChunks: 2
  },
},
5. 限制分包数量
6. 限制分包体积
     借助这些规则我们可以实现当包体过小时直接取消分包 —— 防止产物过"碎"
7. cacheGroups
   缓存组的作用在于能为不同类型的资源设置更具适用性的分包规则,
   一个典型场景是将所有 
   `node_modules` 下的模块统一打包到 `vendors` 产物,从而实现第三方库与业务代码的分离。

   Webpack 提供了两个开箱即用的 `cacheGroups`,
   分别命名为 `default``defaultVendors`,默认配置:
   module.exports = {
      //...
      optimization: {
        splitChunks: {
          cacheGroups: {
            default: {
              idHint: "",
              reuseExistingChunk: true,
              minChunks: 2,
              priority: -20
            },
            defaultVendors: {
              idHint: "vendors",
              reuseExistingChunk: true,
              test: /[\\/]node_modules[\\/]/i,
              priority: -10
            }
          },
        },
      },
    };

    - 将所有 `node_modules` 中的资源单独打包到 `vendors-xxx-xx.js` 命名的产物
    - 对引用次数大于等于 2 的模块 —— 也就是被多个 Chunk 引用的模块,单独打包


    `splitChunks` 规则比较复杂,大致上可以分类为:

      - 规则类:如 `minSize/minChunks` 等,匹配这些条件的 Module 都会被单独分包;
      - `cacheGroup`:可以理解为针对特定资源的次级规则集合。



      分包策略的好坏直接影响应用的运行性能,
      常用策略一是单独打包 `node_modules` 代码(习惯称为 `vendor`),
      二是单独打包被频繁使用的模块

18. 代码压缩

`optimization.minimizer` 数组接入代码压缩插件,比较常用的插件有:

- `terser-webpack-plugin`:用于压缩 ES6 代码的插件;
- `css-minimizer-webpack-plugin`:用于压缩 CSS 代码的插件;
- `html-minifier-terser`:用于压缩 HTML 代码的插件。

 mini-css-extract-plugin更优, 替代extract-text-webpack-plugin
 - `mini-css-extract-plugin` 需要
 与 `html-webpack-plugin` 同时使用,才能将产物路径以 `link` 标签方式插入到 html 中

 // 需要使用 `mini-css-extract-plugin` 将 CSS 代码抽取为单独文件
 // 才能命中 `css-minimizer-webpack-plugin` 默认的 `test` 规则

19、动态加载

- 使用动态加载,减少首屏资源加载量;
- 使用 `externals`、Tree-Shaking、Scope Hoisting 特性,减少应用体积;
- 正确使用 `[hash]` 占位符,优化 HTTP 资源缓存效率;

使用动态加载,减少首屏资源加载量; 多数情况下我们没必要为小模块使用动态加载能力 常见的用法是配合 SPA 的前端路由能力实现页面级别的动态加载

动态加载是 Webpack 内置能力之一,我们不需要做任何额外配置
就可以通过动态导入语句(`import``require.ensure`)轻易实现。
但请 注意,这一特性有时候反而会带来一些新的性能问题:
**一是过度使用会使产物变得过度细碎,产物文件过多,运行时 HTTP 通讯次数也会变多**,
在 HTTP 1.x 环境下这可能反而会降低网络性能,得不偿失;
**二是使用时 Webpack 需要在客户端注入一大段用于支持动态加载特性的 Runtime**

20、雪碧图

雪碧图曾经是一种使用广泛的性能优化技术,但 HTTP2 实现 TCP 多路复用之后, 雪碧图的优化效果已经微乎其微 —— 甚至是反优化,可以预见随 HTTP2 普及率的提升, 未来雪碧图的必要性会越来越低,因此建议读者们了解作用与基本原理即可,不必深究。

- 开发环境需要使用 `webpack-dev-server` 实现 Hot Module Replacement;
- 测试环境需要带上完整的 Soucemap 内容,以帮助更好地定位问题;
- 生产环境需要尽可能打包出更快、更小、更好的应用代码,确保用户体验。

流程配置、性能优化类配置、日志类配置、开发效率类配置等,这里面较常用,需要着重学习的配置有:

  • entry:声明项目入口文件,Webpack 会从这个文件开始递归找出所有文件依赖;
  • output:声明构建结果的存放位置;
  • target:用于配置编译产物的目标运行环境,支持 webnodeelectron 等值,不同值最终产物会有所差异;
  • mode:编译模式短语,支持 developmentproduction 等值,Webpack 会根据该属性推断默认配置;
  • optimization:用于控制如何优化产物包体积,内置 Dead Code Elimination、Scope Hoisting、代码混淆、代码压缩等功能;
  • module:用于声明模块加载规则,例如针对什么类型的资源需要使用哪些 Loader 进行处理;
  • plugin:Webpack 插件列表。

21、Scope Hoisting “作用域提升”

Webpack 提供了 Scope Hoisting 功能,
用于 **将符合条件的多个模块合并到同一个函数空间** 中,从而减少产物体积,优化性能。
Scope Hoisting 底层基于 ES Module 方案的 [静态特性],
推断模块之间的依赖关系,并进一步判断模块与模块能否合并

实现原理:
Scope Hoisting 的实现原理其实很简单:分析出模块之间的依赖关系,
尽可能将打散的模块合并到一个函数中,前提是不能造成代码冗余。
 因此只有那些被引用了一次的模块才能被合并。

由于 Scope Hoisting 需要分析出模块之间的依赖关系,
因此源码必须采用 ES6 模块化语句,不然它将无法生效。

失效情况:
1. 非 ESM 模块
2. 模块被多个 Chunk 引用

22. 性能优化

压缩、Tree-Shaking、Scope Hoisting 都在减少产物体积; Code Splitting、外置依赖、[hash] 则有助于提升 HTTP 缓存效率; 动态加载则能够确保关键路径最小资源依赖。还可以监控产物体积

Webpack 的功能集非常庞大:模块打包、代码分割、按需加载、 Hot Module Replacement、文件监听、Tree-shaking、 Sourcemap、Module Federation、 Dev Server、DLL、多进程打包、Persistent Cache 等等 最最核心的功能依然是:静态模块打包能力

23. 内容处理阶段 (vue-loader)

插件处理完配置,webpack 运行起来之后,Vue SFC 文件会被多次传入不同的 Loader, 经历多次中间形态变换之后才产出最终的 js 结果,大致上可以分为如下步骤:

  1. 路径命中 /\.vue$/i 规则,调用 vue-loader 生成中间结果 A;
  2. 结果 A 命中 xx.vue?vue 规则,调用 vue-loader Pitch Loader 生成中间结果 B;
  3. 结果 B 命中具体 Loader,直接调用 Loader 做处理

此时第一次执行 vue-loader ,执行如下逻辑:

  1. 调用 @vue/component-compiler-utils 包的parse函数,将SFC 文本解析为AST对象;
  2. 遍历 AST 对象属性,转换为特殊的引用路径;
  3. 返回转换结果。

Pitch Loader 的逻辑比较简单,做的事情也只是转换 import 路径

我们可以将 vue-loader 的核心逻辑总结为:

  1. 首先给原始文件路径增加不同的参数,后续配合 resourceQuery 参数就可以分开处理这些内容, 这样的实现相比于一次性处理,逻辑更清晰简洁,更容易理解;
  2. 经过 Normal Loader、Pitch Loader 两个阶段后,SFC 内容会 被转化为 import xxx from '!-babel-loader!vue-loader?xxx' 格式的 引用路径,以此复用用户配置。
  • Loader 主要负责将资源内容转译为 Webpack 能够理解、处理的标准 JavaScript 形式, 所以通常需要做 Loader 内通过 return/this.callback 方式返回翻译结果;

  • Loader Context 提供了许多实用接口,我们可以借助这些接口读取上下文信息, 或改变 Webpack 运行状态(相当于产生 Side Effect,例如通过 emitFile 接口);

  • 假若我们开发的 Loader 需要对外提供配置选项,建议使用 schema-utils 校验配置参数是否合法;

  • 假若 Loader 需要生成额外的资源文件,建议使用 loader-utils 拼接产物路径;

  • 执行时,Webpack 会按照 use 定义的顺序从前到后执行 Pitch Loader, 从后到前执行 Normal Loader,我们可以将一些预处理逻辑 放在 Pitch 中(如 vue-loader);

24. Runtime

Runtime 代码是指一些为了确保打包产物能正常运行,
而由 Webpack 注入的一系列基础框架代码

Webpack 动态生成的运行时代码,编译时,Webpack 会根据业务代码,
决定输出哪些支撑特性的运行时代码(基于 `Dependency` 子类)

例如:
- 需要 `__webpack_require__.f``__webpack_require__.r` 等
  功能实现最起码的模块化支持;
- 如果用到动态加载特性,则需要写入 `__webpack_require__.e` 函数;
- 如果用到 Module Federation 特性,则需要写入 `__webpack_require__.o` 函数;


- `__webpack_modules__` 对象,包含了除入口外的所有模块,如示例中的 `a.js` 模块;
- `__webpack_module_cache__` 对象,用于存储被引用过的模块;
- `__webpack_require__` 函数,实现模块引用(require) 逻辑;
- `__webpack_require__.d` ,工具函数,实现将模块导出的内容附加的模块对象上;
- `__webpack_require__.o` ,工具函数,判断对象属性用;
- `__webpack_require__.r` ,工具函数,在 ESM 模式下声明 ESM 模块标识;
- 最后的 IIFE,对应 entry 模块即上述示例的 `index.js` ,用于启动整个应用。
它们协作构建起一个简单的模块化体系,从而实现 ES Module 规范所声明的模块化特性
上述函数、对象构成了 Webpack 运行时最基本的能力 —— 模块化

25. ModuleGraph、 ChunkGraph

  「构建」阶段负责根据模块的引用关系构建 ModuleGraph;
「封装」阶段则负责根据 ModuleGraph 构建一系列 Chunk 对象,
并将 Chunk 之间的依赖关系(异步引用、Runtime)组织为 
ChunkGraph —— Chunk 依赖关系图对象。

`ModuleGraph`:记录 Dependency Graph 信息的容器,
记录构建过程中涉及到的所有 `module``dependency` 对象,
以及这些对象互相之间的引用

「构建」阶段如何从 Entry 开始逐步递归读入、解析模块内容,
      并最终构建出模块依赖关系图 —— ModuleGraph 对象。
「封装」阶段,根据 ModuleGraph 内容组织 Chunk,
      并进一步构建出 ChunkGroup、ChunkGraph 依赖关系对象的主流程。

Chunk vs ChunkGroup vs ChunkGraph
`ChunkGraph`:最后,Webpack 会将 Chunk 之间、ChunkGroup 
之间的依赖关系存储到 `compilation.chunkGraph` 对象中

Webpack5 内置的三种分包规则:Entry Chunk、Async Chunk 与 Runtime Chunk
Runtime Chunk:`entry.runtime` 不为空时,会将运行时模块单独组织成一个 Chunk。

26. 构建过程

 compile 开始编译
make 从入口点分析模块及其依赖的模块,创建这些模块对象
build-module 构建模块
seal 封装构建结果
emit 把各个chunk输出到结果文件


- Webpack 构建过程可以简单划分为 Init、Make、Seal 三个阶段;
- Init 阶段负责初始化 Webpack 内部若干插件与状态,逻辑比较简单;
- Make 阶段解决资源读入问题,这个阶段会从 Entry —— 入口模块开始,递归读入、解析所有模块内容,并根据模块之间的依赖关系构建 ModuleGraph —— 模块关系图对象;
- Seal 阶段更复杂:
- 一方面,根据 ModuleGraph 构建 ChunkGraph;
- 另一方面,开始遍历 ChunkGraph,转译每一个模块代码;
- 最后,将所有模块与模块运行时依赖合并为最终输出的 Bundle —— 资产文件。

27. 手写webpack loader

@babel/parser 将源代码解析成 AST
@babel/traverse 对AST节点进行递归遍历,生成一个便于操作、转换的path对象
@babel/generator 将AST解码生成js代码
@babel/types通过该模块对具体的AST节点进行进行增、删、改、查

新建drop-console.js
const parser = require('@babel/parser')
const traverse = require('@babel/traverse').default
const generator = require('@babel/generator').default
const t = require('@babel/types')
module.exports=function(source){
  const ast = parser.parse(source,{ sourceType: 'module'})
  traverse(ast,{
    CallExpression(path){ 
      if(t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.object, {name: "console"})){
        path.remove()
      }
    }
  })
  const output = generator(ast, {}, source);
  return output.code
}


如何使用:
const path = require('path')
module.exports = {
  mode:'development',
  entry:path.resolve(__dirname,'index.js'),
  output:{
    filename:'[name].[contenthash].js',
    path:path.resolve(__dirname,'dist')
  },
  module:{
    rules:[{
      test:/\.js$/,
      use:path.resolve(__dirname,'drop-console.js')
      }
    ]
  }
}

webpack4中已经集成了去除console功能,在minimizer中可配置 去除console

28. 手写webpack plugin

compiler和 compilation的区别在于

compiler代表了整个webpack从启动到关闭的生命周期,而compilation 只是代表了一次新的编译过程 compiler和compilation暴露出许多钩子,我们可以根据实际需求的场景进行自定义处理

在生成打包文件之前自动生成一个关于打包出文件的大小信息

新建一个webpack-firstPlugin.js class firstPlugin{ constructor(options){ this.options = options } apply(compiler){ compiler.plugin('emit',(compilation,callback)=>{ let str = '' for (let filename in compilation.assets){ str += 文件:${filename} 大小${compilation.assets[filename]['size']()}\n } // 通过compilation.assets可以获取打包后静态资源信息,同样也可以写入资源 compilation.assets['fileSize.md'] = { source:function(){ return str }, size:function(){ return str.length } } callback() }) } } module.exports = firstPlugin

如何使用 const path = require('path') const firstPlugin = require('webpack-firstPlugin.js') module.exports = { // 省略其他代码 plugins:[ new firstPlugin() ] }