Webpack通识

Webpack相对其他打包器不同的地方

Webpack忽略具体资源类型之间的差异,将所有代码/非代码文件都统一看作Module------模块对象,以相同的加载、解析、依赖管理、优化、合并流程实现打包,并借助Loader、Plugin两种开放接口将资源差异处理逻辑转交由社区实现,实现统一资源构建模型

优点:

  • 所有资源都是Module,所以可以用同一套代码实现诸多特性,包括:代码压缩、Hot Module Replacement、缓存等

  • 打包时,资源与资源之间非常容易实现信息互换,例如可以轻易在HTML插入Base64格式的图片

  • 借助Loader,Webpack几乎可以用任意方式处理任意类型的资源,例如可以用Less、Stylus、Sass等预编译CSS代码

Webpack的打包过程

  • 输入:从文件系统读入代码文件

  • 模块递归处理:调用Loader转译Module内容,并将结果转换为AST,从中分析出模块依赖关系,进一步递归调用模块处理过程,直到所有依赖文件都处理完毕

  • 后处理:所有模块递归处理完毕后开始执行后处理,包括模块合并、注入运行时、产物优化等,最终输出Chunk集合

  • 输出:将Chunk写出到外部文件系统

Webpack配置项分类

  • 流程类:作用于打包流程某个或若干个环节,直接影响编译打包效果的配置项
  • 工具类:打包主流程之外,提供更多工程化工具的配置项

流程类

  • 输入输出:

    • entry:用于定义项目入口文件,Webpack会从这些入口文件开始按图索骥找出所有项目文件
    • context:项目执行上下文路径
    • output:配置产物输出路径、名称等
  • 模块处理:

    • resolve:用于配置模块路径解析规则,可用于帮助Webpack更精准、高效地找出指定模块
    • module:用于配置模块加载规则,例如针对什么类型的资源需要使用哪些Loader进行处理
    • externals:用于声明外部资源,Webpack会直接忽略这部分资源,跳出这些资源的解析、打包操作
  • 后处理

    • optimization:用于控制如何优化产物包体积,内置Dead Code Elimination、Scope Hoisting、代码混淆、代码压缩等功能
    • target:用于配置编译产物的目标运行环境,支持web、node、electron等值,不同值最终产物会有所差异
    • mode:编译模式短语,支持developmentproduction等值,可以理解为一种声明环境的短语

工具类

  • 开发效率类:

    • watch:用于配置持续监听文件变化,持续构建
    • devtool:用于配置产物Sourcemap生成规则
    • devServer:用于配置与HMR强相关的开发服务器功能
  • 性能优化类:

    • cache:Webpack5之后,该项用于控制如何缓存编译过程信息与编译结果
    • performance:用于配置当产物大小超过阈值时,如何通知开发者
  • 日志类:

    • state:用于精准地控制编译过程的日志内容,在做比较细致的性能调试时非常有用
    • infrastructureLogging:用于控制日志输出方式,例如可以通过该配置将日志输出到磁盘文件

Webpack常用Loader、Plugin

Webpack打包npm包

  1. 相对于普通项目需要在output#library下加name和type
java 复制代码
module.exports = {
  // ...
  output: {
    filename: "[name].js",
    path: path.join(__dirname, "./dist"),
+   library: {
+     name: "_",  // 被调用时的名字
+     type: "umd",
+   },
  },
  // ...
};
  1. 排除第三方库 防止打包体积过大 使用externals
java 复制代码
// webpack.config.js
module.exports = {
  // ...
+  externals: {
+   lodash: {
+     commonjs: "lodash",
+     commonjs2: "lodash",
+     amd: "lodash",
+     root: "_",
+   },
+ },
  // ...
};
  1. 不再打包lodash可以顺手将lodash声明为peerDependencies
json 复制代码
{
  "name": "6-1_test-lib",
  // ...
+ "peerDependencies": {
+   "lodash": "^4.17.21"
+ }
}
  1. 也可以用webpack-node-externals直接排除所有node_modules模块
ini 复制代码
// webpack.config.js
const nodeExternals = require('webpack-node-externals');

module.exports = {
  // ...
+  externals: [nodeExternals()]
  // ...
};
  1. 抽离CSS代码,通常需要使用mini-css-extract-plugin插件将样式抽离成单独文件,由用户自行引入
diff 复制代码
module.exports = {  
  // ...
+ module: {
+   rules: [
+     {
+       test: /.css$/,
+       use: [MiniCssExtractPlugin.loader, "css-loader"],
+     },
+   ],
+ },
+ plugins: [new MiniCssExtractPlugin()],
};
  1. 生成sourcemap
java 复制代码
// webpack.config.js
module.exports = {  
  // ...
+ devtool: 'source-map'
};
  1. 使用.npmignore文件忽略不需要发布到npm的文件
  2. package.json文件中,使用prepublishOnly指令,在发布前自动执行编译命令,例如
json 复制代码
// package.json
{
  "name": "test-lib",
  // ...
  "scripts": {
    "prepublishOnly": "webpack --mode=production"
  },
  // ...
}
  1. package.json文件中,使用main指定项目入口,同时使用modue指定ES Module模式下的入口,以允许用户直接使用源码版本,例如:
json 复制代码
{
  "name": "6-1_test-lib",
  // ...
  "main": "dist/main.js",
  "module": "src/index.js",
  "scripts": {
    "prepublishOnly": "webpack --mode=production"
  },
  // ...
}

Webpack处理图像

导入图像

  1. file-loader 已经内置不需要安装
java 复制代码
// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [{
      test: /.(png|jpg)$/,
-     use: ['file-loader']
+     type: 'asset/resource'
    }],
  },
};
  1. url-loader 限定文件大小阈值
yaml 复制代码
module.exports = {
  // ...
  module: {
    rules: [{
      test: /.(png|jpg)$/,
-     use: [{
-       loader: 'url-loader',
-       options: {
-         limit: 1024
-       }
-     }]
+     type: "asset",
+     parser: {
+        dataUrlCondition: {
+          maxSize: 1024 // 1kb
+        }
+     }
    }],
  },
};
  1. raw-loader
bash 复制代码
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.svg$/i,
-       use: ['raw-loader']
+       type: "asset/source"
      },
    ],
  },
};

图像优化

压缩

  1. image-webpack-loader
arduino 复制代码
yarn add -D image-webpack-loader

module.exports = {
  // ...
  module: {
    rules: [{
      test: /.(gif|png|jpe?g|svg)$/i,
      // type 属性适用于 Webpack5,旧版本可使用 file-loader
      type: "asset/resource",
      use: [{
        loader: 'image-webpack-loader',
        options: {
          // jpeg 压缩配置
          mozjpeg: {
            quality: 80
          },
          disable: process.env.NODE_ENV === 'development' // 非常消耗性能,建议只在生产环境下使用
        }
      }]
    }],
  },
};

image-webpack-loader底层依赖于imagemin及一系列的图像优化工具:

  • mozjpeg:用于压缩JPG(JPEG)图片
  • optipng:用于压缩PNG图片
  • pngquant:用于压缩PNG图片
  • svgo:用于压缩SVG图片
  • gifsicle:用于压缩Gif图
  • webp:用于将JPG/PNG图片压缩并转化为WebP图片格式

雪碧图

  1. 使用webpack-spritesmith自动实现雪碧图效果
css 复制代码
yarn add -D webpack-spritesmith

module.exports = {
  // ...
  resolve: {
    modules: ["node_modules", "assets"]
  },
  plugins: [
    new SpritesmithPlugin({
      // 需要
      src: {
        cwd: path.resolve(__dirname, 'src/icons'),
        glob: '*.png'
      },
      target: {
        image: path.resolve(__dirname, 'src/assets/sprite.png'),
        css: path.resolve(__dirname, 'src/assets/sprite.less')
      }
    })
  ]
};

webpack-spritesmith会将src.cwd目录内所有匹配src.glob规则的图片合并成一张大图并保存到target.image指定的文件路径

响应式图片

  1. 使用responsive-loader
css 复制代码
yarn add -D responsive-loader sharp

module.exports = {
  // ...
  module: {
    rules: [{
      test: /.(png|jpg)$/,
      oneOf: [{
        type: "javascript/auto",
        resourceQuery: /sizes?/,
        use: [{
          loader: "responsive-loader",
          options: {
            adapter: require("responsive-loader/sharp"),
          },
        }],
      }, {
        type: "asset/resource",
      }],
    }],
  }
};
  1. 注意,实践中我们通常没必要对项目里所有图片都施加响应式特性,因此这里使用resourceQuery过滤出带size/sizes参数的图片引用,使用方法:
ini 复制代码
// 引用图片,并设置响应式参数
import responsiveImage from './webpack.jpg?sizes[]=300,sizes[]=600,sizes[]=1024';

const Picture = function () {
  return (
    <img
      srcSet={responsiveImage.srcSet}
      src={responsiveImage.src}
      sizes="(min-width: 1024px) 1024px, 100vw"
      loading="lazy"
    />
  );
};

上例的引用参数./webpack.jpg?sizes[]=300,sizes[]=600,sizes[]=1024最终将生成宽度分别为300、600、1024三张图片,之后设置img标签的srcset属性即可实现图片响应式功能

css 复制代码
.foo {
    background: url("./webpack.jpg?size=1024");
}

@media (max-width: 480px) {
    .foo {
        background: url("./webpack.jpg?size=300");
    }
}

Webpack环境治理

  1. 实现同一份代码打包出多种产物,使用数组方式配置
lua 复制代码
// webpack.config.js
module.exports = [
  {
    output: {
      filename: './dist-amd.js',
      libraryTarget: 'amd',
    },
    name: 'amd',
    entry: './app.js',
    mode: 'production',
  },
  {
    output: {
      filename: './dist-commonjs.js',
      libraryTarget: 'commonjs',
    },
    name: 'commonjs',
    entry: './app.js',
    mode: 'production',
  },
];
  1. 可以借助webpack-merge合并通用配置
php 复制代码
const { merge } = require("webpack-merge");

const baseConfig = {
  output: {
    path: "./dist"
  },
  name: "amd",
  entry: "./app.js",
  mode: "production",
};

module.exports = [
  merge(baseConfig, {
    output: {
      filename: "[name]-amd.js",
      libraryTarget: "amd",
    },
  }),
  merge(baseConfig, {
    output: {
      filename: "./[name]-commonjs.js",
      libraryTarget: "commonjs",
    },
  }),
];
  1. 使用配置函数的方式配置webpack,允许用户根据命令行参数动态创建配置对象,可用于实现简单的多环境治理策略
css 复制代码
// npx webpack --env app.type=miniapp --mode=production
module.exports = function (env, argv) {
  return {
    mode: argv.mode ? "production" : "development",
    devtool: argv.mode ? "source-map" : "eval",
    output: {
      path: path.join(__dirname, `./dist/${env.app.type}`,
      filename: '[name].js'
    },
    plugins: [
      new TerserPlugin({
        terserOptions: {
          compress: argv.mode === "production", 
        },
      }),
    ],
  };
};
命令: env 参数值:
npx webpack --env prod { prod: true }
npx webpack --env prod --env min { prod: true, min: true }
npx webpack --env platform=app --env production { platform: "app", production: true }
npx webpack --env foo=bar=app { foo: "bar=app"}
npx webpack --env app.platform="staging" --env app.name="test" { app: { platform: "staging", name: "test" }

环境治理策略

需求:

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

方案

  • 上面使用到的配置函数配合命令行参数动态计算配置对象
  • 业界比较流行的是将不同环境配置分别维护在单独的配置文件中 之后配合--config选项指定配置目标
arduino 复制代码
npx webpack --config webpack.development.js
arduino 复制代码
.
└── config
  ├── webpack.common.js
  ├── webpack.development.js
  ├── webpack.testing.js
  └── webpack.production.js

Webpack入口Entry

使用 entry.dependOn 声明入口依赖:

css 复制代码
module.exports = {
  // ...
  entry: {
    main: "./src/index.js",
    foo: { import: "./src/foo.js", dependOn: "main" },
  },
};

foo入口的dependOn属性指向main入口,此时Webpack认为:客户端在加载foo产物之前必然会加载main,因此可以将重复的模块代码、运行时代码等都放到main产物,减少不必要的重复

dependOn适用于哪些有明确入口依赖的场景,例如我们构建了一个主框架Bundle,其中包含了项目基本框架(如React),之后还需要为每个页面单独构建Bundle,这些页面代码也都依赖于主框架代码,此时可用dependOn属性优化产物内容,减少代码重复

使用 entry.runtime 管理运行时代码:

css 复制代码
const path = require("path");

module.exports = {
  mode: "development",
  devtool: false,
  entry: {
    main: { import: "./src/index.js", runtime: "common-runtime" },
    foo: { import: "./src/foo.js", runtime: "common-runtime" },
  },
  output: {
    clean: true,
    filename: "[name].js",
    path: path.resolve(__dirname, "dist"),
  },
};

为支持产物代码在各种环境中正常运行,Webpack会在产物文件中注入一系列运行时代码,用以支撑起整个应用框架,运行时代码的多寡取决于我们用到多少特性,例如:

  • 需要导入导出文件时,将注入__webpack_require_.r
  • 使用异步加载时,将注入__webpack_require_.l

不要小看运行时代码量,极端情况下甚至可能超过业务代码总量,为此,必要时我们可以尝试使用runtime配置将运行时抽离为单独Bundle,如上:

实例中,mainfoo入口均将runtime声明为common-runtime,此时Webpack会将两个入口的运行时代码都抽取出来,放在common-runtimeBundle中

entry.runtime是一种常用的应用性能优化手段

Webpack 出口 Output

  • target支持设置构建目标

    • web
    • browserslist
    • electron
php 复制代码
const path = require("path");
const { merge } = require("webpack-merge");

const baseConfig = {
  mode: "development",
  target: "web",
  devtool: false,
  entry: {
    main: { import: "./src/index.js" },
  },
  output: {
    clean: true,
    path: path.resolve(__dirname, "dist"),
  },
};

module.exports = [
  merge(baseConfig, { target: "web", output: { filename: "web-[name].js" } }),
  merge(baseConfig, { target: "node", output: { filename: "node-[name].js" } }),
];

性能优化

Webpack底层工作流程

  1. 初始化阶段:

    1. 初始化参数:从配置文件、配置对象、Shell参数中读取,与默认配置结合得出最终的参数
    2. 创建编译器对象:用上一步的到的参数创建Compiler对象
    3. 初始化编译环境:包括注入内容插件、注册各种模块工厂、初始化RuleSet集合、加载配置的插件等
    4. 开始编译:执行comiler对象的run方法,创建Compilation对象
    5. 确定入口:根据配置中的entry找出所有的入口文件,调用compilation.addEntry将入口文件转换为dependence对象
  2. 构建阶段:

    1. 编译模块:从entry文件开始,调用loader将模块转译为标准JS内容,调用JS解析器将内容转换为AST对象,从中找出该模块依赖的模块,再递归处理这些依赖模块,直到所有入口依赖的文件都经过了本步骤的处理
    2. 完成模块编译:上一步递归处理所有能触达到的模块后,的到了每个模块被翻译后的内容以及它们之间的依赖关系图
  3. 封装阶段:

    1. 合并:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk
    2. 优化:对上述Chunk施加一系列优化操作,包括:tree-shakingterserscope-hoisting压缩Code Split
    3. 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

可能造成性能问题的地方

  • 构建阶段:

    • 首先需要将文件的相对引用路径转换为绝对路径,这个过程可能涉及多次IO操作,执行效率取决于文件层次深度
    • 找到具体文件后,需要读入文件内容并调用loader-runner遍历Loader数组完成内容转译,这个过程需要执行较密集的CPU操作,执行效率取决于Loader的数量与复杂度
    • 需要将模块内容解析为AST结构,并遍历AST找出模块的依赖资源,这个过程同样需要较密集的CPU操作,执行效率取决于代码复杂度
    • 递归处理依赖资源,执行效率取决于模块数量
  • 封装阶段:

    • 根据splitChunks配置、entry配置、动态模块引用语句等,确定模块与Chunk的映射关系,其中splitChunks相关的分包算法非常复杂,涉及大量CPU计算
    • 根据optimization配置执行一系列产物优化操作,特别是Terser插件需要执行大量AST相关的运算,执行效率取决于产物代码量

性能分析

Webpack内置了stats接口,专门用于统计模块构建耗时,模块依赖关系等信息,推荐用法

  1. 添加profile=true配置
java 复制代码
// webpack.config.js
module.exports = {
  // ...
  profile: true
}
  1. 运行编译命令,并添加--json参数,参数值为最终生成的统计文件名,如:
ini 复制代码
npx webpack --json=stats.json
  1. 最后生成stats.json如下
json 复制代码
{
  "hash": "2c0b66247db00e494ab8",
  "version": "5.36.1",
  "time": 81,
  "builtAt": 1620401092814,
  "publicPath": "",
  "outputPath": "/Users/tecvan/learn-webpack/hello-world/dist",
  "assetsByChunkName": { "main": ["index.js"] },
  "assets": [
    // ...
  ],
  "chunks": [
    // ...
  ],
  "modules": [
    // ...
  ],
  "entrypoints": {
    // ...
  },
  "namedChunkGroups": {
    // ...
  },
  "errors": [
    // ...
  ],
  "errorsCount": 0,
  "warnings": [
    // ...
  ],
  "warningsCount": 0,
  "children": [
    // ...
  ]
}
  • modules:本次打包处理的所有模块列表,内容包含模块的大小、所属chunk、构建原因、依赖模块等,特别是modules.profile属性,包含了构建该模块时,解析路径、编译、打包、子模块打包等各个环节所花费的时间
  • chunks:构建过程生成的chunks列表,数组内容包含chunk名称、大小、包含了哪些模块等
  • assets:编译后最终输出的产物列表、文件路径、文件大小等
  • entrypoints:entry列表,包含动态引入所产生的entry项也会包含在这里面
  • children:子Compiler对象的性能数据,例如extract-css-chunk-plugin插件内部就会调用compilation.createChildCompiler函数创建出子Compiler来做CSS抽取的工作

webpack.js.org/api/stats/

常用性能可视化分析工具

  • Webpack Analysis :Webpack 官方提供的,功能比较全面的 stats 可视化工具;
  • Statoscope:主要侧重于模块与模块、模块与 chunk、chunk 与 chunk 等,实体之间的关系分析;
  • Webpack Visualizer:一个简单的模块体积分析工具,真的很简单!
  • Webpack Bundle Analyzer:应该是使用率最高的性能分析工具之一,主要实现以 Tree Map 方式展示各个模块的体积占比;
  • Webpack Dashboard:能够在编译过程实时展示编译进度、模块分布、产物信息等;
  • Unused Webpack Plugin:能够根据 stats 数据反向查找项目中未被使用的文件。

持久化缓存

仅需在Webpack中设置cache.type='filesystem'

java 复制代码
module.exports = {
    //...
    cache: {
        type: 'filesystem'
    },
    //...
};

cache还提供了若干用于配置缓存效果、缓存周期的配置项,包括

  • type:缓存类型,支持memory|filesystem,需要设置为filesystem才能开启持久缓存
  • cacheDirectory:缓存文件路径,默认为node_modules/.cache/webpack
  • buildDependencies:额外的依赖文件,当这些文件内容发生变化时,缓存会完全失效而执行完整的编译构建,通常可设置为各种配置文件
css 复制代码
module.exports = {
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [
        path.join(__dirname, 'webpack.dll_config.js'),
        path.join(__dirname, '.babelrc')
      ],
    },
  },
};
  • managedPaths:受控目录,Webpack构建时会跳过新旧代码哈希值与时间戳的比较,直接使用缓存副本,默认值为['./node_modules']
  • profile:是否输出缓存处理过程的详细日志,默认为false
  • maxAge:缓存失效时间,默认值为5184000000

并行构建

  • HappyPack:多进程方式运行资源加载(Loader)逻辑;

  • Thread-loader:Webpack 官方出品,同样以多进程方式运行资源加载逻辑;

  • Parallel-Webpack:多进程方式运行多个 Webpack 构建实例;

  • TerserWebpackPlugin:支持多进程方式执行代码压缩、uglify 功能。

Webpack5使用Thread-loader

  1. 安装依赖:
arduino 复制代码
yarn add -D thread-loader
  1. 将 Thread-loader 放在 use 数组首位,确保最先运行,如:
yaml 复制代码
module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        use: [
          {
            loader: "thread-loader",
            options: {
              workers: 2,
              workerParallelJobs: 50,
              // ...
            },
          },
          "babel-loader",
          "eslint-loader",
        ],
      },
    ],
  },
};
  • 在Thread-loader中运行的Loader不能调用emitAsset等接口,这会导致style-loader这一类加载器无法正常工作,解决方案是将这类组件放置在thread-loader之前,如['style-loader','thread-loader','css-loader']

  • Loader中不能活去compilation、compiler等实例对象,也无法活去Webpack配置

并行压缩

Webpack5默认提供Terser来进行代码压缩和混淆

ini 复制代码
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
    optimization: {
        minimize: true,
        minimizer: [new TerserPlugin({
            parallel: 2 // number | boolean
        })],
    },
};

上述配置即可设定最大并行进程数为2

注意:

理论上,并行确实能够提升系统运行效率,但 Node 单线程架构下,所谓的并行计算都只能依托与派生子进程执行,而创建进程这个动作本身就有不小的消耗 ------ 大约 600ms,对于小型项目,构建成本可能可能很低,引入多进程技术反而导致整体成本增加,因此建议大家按实际需求斟酌使用上述多进程方案。

按需编译

使用lazyCompilation,用于实现entry或异步引用模块的按需编译

java 复制代码
// webpack.config.js
module.exports = {
  // ...
  experiments: {
    lazyCompilation: true,
  },
};

启动lazyCompilation后,代码中通过异步引用语句如import('./xxx')导入的模块都不会被立即编译,而是直到页面正式请求该模块资源(例如切换到该路由)时才开始构建,效果与Vite相似,能够极大提升冷启动速度

约束Loader执行范围

使用includeexclude等配置项,限定Loader的执行范围------通常可以排除node_modules文件夹

javascript 复制代码
// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: ["babel-loader", "eslint-loader"],
      },
    ],
  },
};
javascript 复制代码
const path = require("path");
module.exports = {
  // ...
  module: {
    rules: [{
      test: /.js$/,
      exclude: {
        and: [/node_modules/],
        not: [/node_modules/lodash/]
      },
      use: ["babel-loader", "eslint-loader"]
    }],
  }
};
// 支持 and/not/or 

上述,跳过出了loadash之外的

使用noParse跳过已经编译好的文件的编译

java 复制代码
// webpack.config.js
module.exports = {
  //...
  module: {
    noParse: /lodash|react/,
  },
};
// 跳过lodash和react的编译

最好不用,因为跳过了npm,可能导致运行时才能发现一些错误

开发模式仅用产物优化

Webpack提供了许多产物优化功能,但是这些优化在开发环境中意义不大,反而会增加构建器的负担,因此建议关闭这一类优化功能:

  • 确保 mode='development'mode = 'none',关闭默认优化策略;
  • optimization.minimize 保持默认值或 false,关闭代码压缩;
  • optimization.concatenateModules 保持默认值或 false,关闭模块合并;
  • optimization.splitChunks 保持默认值或 false,关闭代码分包;
  • optimization.usedExports 保持默认值或 false,关闭 Tree-shaking 功能;
yaml 复制代码
module.exports = {
  // ...
  mode: "development",
  optimization: {
    removeAvailableModules: false,
    removeEmptyChunks: false,
    splitChunks: false,
    minimize: false,
    concatenateModules: false,
    usedExports: false,
  },
};

最小化watch监控范围

通常情况下,node_modules不会频繁更新,不需要一直监听

javascript 复制代码
// webpack.config.js
module.exports = {
  //...
  watchOptions: {
    ignored: /node_modules/
  },
};

跳过TS类型检查

yaml 复制代码
module.exports = {
  // ...
  module: {
    rules: [{
      test: /.ts$/,
      use: [
        {
          loader: 'ts-loader',
          options: {
            // 设置为"仅编译",关闭类型检查
            transpileOnly: true
          }
        }
      ],
    }],
  }
};

将TS检查交给其他的:

  1. 可以借助编辑器的TypeScript插件实现代码检查
  2. 使用fork-ts-checker-webpack-plugin插件将类型检查能力剥离到子进程执行
javascript 复制代码
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');

module.exports = {
  // ...
  module: {
    rules: [{
      test: /.ts$/,
      use: [
        {
          loader: 'ts-loader',
          options: {
            transpileOnly: true
          }
        }
      ],
    }, ],
  },
  plugins:[
    // fork 出子进程,专门用于执行类型检查
    new ForkTsCheckerWebpackPlugin()
  ]
};

优化ESLint性能

使用新版eslint-webpack-plugin替代旧版eslint-loader

ini 复制代码
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
  // ...
  plugins: [new ESLintPlugin(options)],
  // ...
};

设置resolve缩小搜索范围

  • resolve.extensions :['.js', '.json', '.wasm'] 可以减少匹配的项

SplitChunksPlugin分包

  • minChunks:用于设置引用阈值,被引用次数超过该阈值的 Module 才会进行分包处理;
  • maxInitialRequest/maxAsyncRequests:用于限制 Initial Chunk(或 Async Chunk) 最大并行请求数,本质上是在限制最终产生的分包数量;
  • minSize: 超过这个尺寸的 Chunk 才会正式被分包;
  • maxSize: 超过这个尺寸的 Chunk 会尝试继续做分包;
  • maxAsyncSize: 与 maxSize 功能类似,但只对异步引入的模块生效;
  • maxInitialSize: 与 maxSize 类似,但只对 entry 配置的入口模块生效;
  • enforceSizeThreshold: 超过这个尺寸的 Chunk 会被强制分包,忽略上述其它 size 限制;
  • cacheGroups:用于设置缓存组规则,为不同类型的资源设置更有针对性的分包策略。
yaml 复制代码
module.exports = {
  //...
  optimization: {
    splitChunks: {
      cacheGroups: {
        default: {
          idHint: "",
          reuseExistingChunk: true,
          minChunks: 2,
          priority: -20
        },
        defaultVendors: {
          idHint: "vendors",
          reuseExistingChunk: true,
          test: /[\/]node_modules[\/]/i,
          priority: -10
        }
      },
    },
  },
};

使用terser-webpack-plugin压缩JS代码

javascript 复制代码
const TerserPlugin = require("terser-webpack-plugin");

module.exports = {
  entry: { foo: "./src/foo.js", bar: "./src/bar.js" },
  output: {
    filename: "[name].js",
    // ...
  },
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        test: /foo.js$/i,
        extractComments: "all",
      }),
      new TerserPlugin({
        test: /bar.js/,
        extractComments: false,
      }),
    ],
  },
};

terser-webpack-plugin 是一个颇为复杂的 Webpack 插件,提供下述 配置项

  • test:只有命中该配置的产物路径才会执行压缩,功能与 module.rules.test 相似;
  • include:在该范围内的产物才会执行压缩,功能与 module.rules.include 相似;
  • exclude:与 include 相反,不在该范围内的产物才会执行压缩,功能与 module.rules.exclude 相似;
  • parallel:是否启动并行压缩,默认值为 true,此时会按 os.cpus().length - 1 启动若干进程并发执行;
  • minify:用于配置压缩器,支持传入自定义压缩函数,也支持 swc/esbuild/uglifyjs 等值,下面我们再展开讲解;
  • terserOptions:传入 minify ------ "压缩器"函数的配置参数;
  • extractComments:是否将代码中的备注抽取为单独文件,可配合特殊备注如 @license 使用。

使用CssMinimizerWebpackPlugin压缩CSS

javascript 复制代码
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()],
};
  • 使用mini-css-extract-plugin将CSS代码抽取为单独的CSS产物文件,这样才能命中css-minimizer-webpack-plugin默认的test逻辑
  • 使用css-minimizer-webpack-plugin压缩CSS代码

使用HtmlMinifierTerser压缩HTML

csharp 复制代码
yarn add -D html-minimizer-webpack-plugin
xml 复制代码
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlMinimizerPlugin = require("html-minimizer-webpack-plugin");

module.exports = {
  // ...
  optimization: {
    minimize: true,
    minimizer: [
      // Webpack5 之后,约定使用 `'...'` 字面量保留默认 `minimizer` 配置
      "...",
      new HtmlMinimizerPlugin({
        minimizerOptions: {
          // 折叠 Boolean 型属性
          collapseBooleanAttributes: true,
          // 使用精简 `doctype` 定义
          useShortDoctype: true,
          // ...
        },
      }),
    ],
  },
  plugins: [
    // 简单起见,这里我们使用 `html-webpack-plugin` 自动生成 HTML 演示文件
    new HtmlWebpackPlugin({
      templateContent: `<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>webpack App</title>
      </head>
      <body>
        <input readonly="readonly"/>
        <!-- comments -->
        <script src="index_bundle.js"></script>
      </body>
    </html>`,
    }),
  ],
};

零碎的优化方案

动态加载

dart 复制代码
document.getElementById("someButton").addEventListener("click", async () => {
  // 使用 `import("module")` 动态加载模块
  const someBigMethod = await import("./someBigMethod");
  someBigMethod();
});

HTTP缓存优化 HASH名协商缓存

css 复制代码
module.exports = {
  // ...
  entry: { index: "./src/index.js", foo: "./src/foo.js" },
  output: {
    filename: "[name]-[contenthash].js",
    path: path.resolve(__dirname, "dist"),
  },
  plugins: [new MiniCssExtractPlugin({ filename: "[name]-[contenthash].css" })],
};

使用Tree-Shaking删除多余模块导出

在 Webpack 中,启动 Tree Shaking 功能必须同时满足两个条件:

  • 配置 optimization.usedExportstrue,标记模块导入导出列表;

  • 启动代码优化功能,可以通过如下方式实现:

    • 配置 mode = production
    • 配置 optimization.minimize = true
    • 提供 optimization.minimizer 数组
java 复制代码
// webpack.config.js
module.exports = {
  mode: "production",
  optimization: {
    usedExports: true,
  },
};

使用Scope Hoisting合并模块

Webpack 提供了三种开启 Scope Hoisting 的方法:

  • 使用 mode = 'production' 开启生产模式;
  • 使用 optimization.concatenateModules 配置项;
  • 直接使用 ModuleConcatenationPlugin 插件。
ruby 复制代码
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');

module.exports = {
    // 方法1: 将 `mode` 设置为 production,即可开启
    mode: "production",
    // 方法2: 将 `optimization.concatenateModules` 设置为 true
    optimization: {
        concatenateModules: true,
        usedExports: true,
        providedExports: true,
    },
    // 方法3: 直接使用 `ModuleConcatenationPlugin` 插件
    plugins: [new ModuleConcatenationPlugin()]
};

监控产物体积

javascript 复制代码
module.exports = {
  // ...
  performance: {    
    // 设置所有产物体积阈值
    maxAssetSize: 172 * 1024,
    // 设置 entry 产物体积阈值
    maxEntrypointSize: 244 * 1024,
    // 报错方式,支持 `error` | `warning` | false
    hints: "error",
    // 过滤需要监控的文件类型
    assetFilter: function (assetFilename) {
      return assetFilename.endsWith(".js");
    },
  },
};

Loader

Webpack流程

webpack打包阶段

  1. 初始化阶段:负责设置构建环境,初始化若干工厂类、注入内置插件等
  2. 构建阶段:读入并分析Entry模块,找到模块依赖,之后递归处理这些依赖、依赖的依赖,直到所有模块都处理完毕,这个过程解决资源输入问题
  3. 生成阶段:根据Entry配置将模块封装进不同Chunk对象,经过一系列优化后,再将模块代码翻译为产物形态,按Chunk合并成最终产物文件,这个过程解决资源输出的问题
相关推荐
清云随笔12 分钟前
axios 实现 无感刷新方案
前端
鑫宝Code14 分钟前
【React】状态管理之Redux
前端·react.js·前端框架
忠实米线22 分钟前
使用pdf-lib.js实现pdf添加自定义水印功能
前端·javascript·pdf
pink大呲花25 分钟前
关于番外篇-CSS3新增特性
前端·css·css3
少年维持着烦恼.29 分钟前
第八章习题
前端·css·html
我是哈哈hh32 分钟前
HTML5和CSS3的进阶_HTML5和CSS3的新增特性
开发语言·前端·css·html·css3·html5·web
田本初1 小时前
如何修改npm包
前端·npm·node.js
明辉光焱1 小时前
[Electron]总结:如何创建Electron+Element Plus的项目
前端·javascript·electron
牧码岛2 小时前
Web前端之汉字排序、sort与localeCompare的介绍、编码顺序与字典顺序的区别
前端·javascript·web·web前端