webpack 核心概念和优化指南 · Issue #60 · creeperyang/blog · GitHub 您所在的位置:网站首页 工业冷冻干燥机型号 webpack 核心概念和优化指南 · Issue #60 · creeperyang/blog · GitHub

webpack 核心概念和优化指南 · Issue #60 · creeperyang/blog · GitHub

#webpack 核心概念和优化指南 · Issue #60 · creeperyang/blog · GitHub| 来源: 网络整理| 查看: 265

从功能核心来说,webpack 是JS应用的打包工具(static module bundler)。webpack 会从入口(entry point)开始处理你的应用,构建依赖图,把多个模块(module)合并到一个或多个包(bundle)。

webpack 本身只能处理 JS/JSON 文件,它依赖各种 loader、plugin 来共同完成对复杂应用的支持。

(一)核心概念 loader vs plugin

Loaders are transformations that are applied to the source code of a module. Plugins are the backbone of webpack. Webpack itself is built on the same plugin system that you use in your webpack configuration!

loader 就是转换模块代码的工具函数,允许你预处理你要加载的文件。

plugin 是 webpack 的基石,而 webpack 也是基于同样的插件系统打造。plugin 本质是注册webpack的生命周期事件来做到 loader 做不到的事。

loader 顺序 module.exports = { module: { rules: [ { use: ['a-loader', 'b-loader', 'c-loader'], }, ], }, };

loader 是洋葱型顺序,pitch 从左到右,loader 本身从右到左执行。

|- a-loader `pitch` |- b-loader `pitch` |- c-loader `pitch` |- requested module is picked up as a dependency |- c-loader normal execution |- b-loader normal execution |- a-loader normal execution module vs chunk

Every file used in your project is a Module

模块化编程中,完整的程序可以被分成完成特定功能的模块。在 webpack 里,一个文件就是一个模块。

编译过程中,modules are combined into chunks, Chunks combine into chunk groups。

chunk 有两种形式:

initial chunk:是 main chunk for entry point,包括一个 entry point 的所有模块和模块的依赖; non-initial chunk:是指可能懒加载的 chunk,由懒加载或者使用SplitChunksPlugin时产生。 (二)优化构建效率

总的来说,想要提高打包/构建效率,要么是减少打包工作量,要么是提高打包速度。相关措施可以总结为:

并行打包:多线程/多进程打包。 更高性能的打包工具:利用esbuild、swc。 怎么减少工作量?缓存,以及缩减查找步骤、范围。 持久化缓存,提高二次构建性能

如果不想变更一个模块导致打包过程(可能几百个模块)重复一遍,那么我们需要利用缓存持久化来避免不必要的工作。

Cache the generated webpack modules and chunks to improve build speed.

配置缓存很简单,配置 cache 即可:

{ cache: { type: 'filesystem', // 缓存到 memory 或 filesystem // 额外的依赖文件,当这些文件内容变化时,缓存会完全失效而执行完整的编译构建,通常可设置为项目配置文件 buildDependencies: { config: [path.join(__dirname, 'webpack.dll_config.js')], }, // 缓存文件存放的路径,默认为 node_modules/.cache/webpack cacheDirectory: 'node_modules/.cache/webpack', maxAge: 5184000000, }, } cache.type:默认为memory,这在watch模式下很有用,但是如果想持久化,方便中断后下次打包使用,可以设置为filesystem; cache.cacheDirectory:缓存文件存放的路径,默认为 node_modules/.cache/webpack; cache.maxAge:缓存失效时间,默认为 5184000000; cache.buildDependencies:额外的依赖文件,当这些文件内容变化时,缓存会完全失效而执行完整的编译构建,通常可设置为项目配置文件。

配置完缓存后,测试的两次编译时间为 2047ms 和 417 ms,效果显著。

减少编译查找路径、编译范围(减少查找时间,减少需要编译的文件)

除了缓存,还可以缩减编译查找步骤、范围来减小工作量。

1. Rule 的 exclude/include/issuer 等多种方式减少查找范围 { module: { rules: [ { test: /\.jsx?/, exclude: [/node_modules/], }, { test: /\.css$/, include: [ path.resolve(__dirname, 'app/styles'), path.join(__dirname, 'vendor/styles/'), ], }, ], }, } 2. noParse 跳过编译

使用 noParse 让 webpack 不要去解析特地文件,对忽略一些大型类库,可以节省很多时间。

module.exports = { module: { noParse: /jquery|lodash/, }, }; 3. 配置 resolve 减少查找范围

尽量减少webpack的查找范围。

module.exports = { resolve: { // 视情况可减少 importsFields: ['browser', 'module', 'main'], // 视情况可减少 extensions: ['.js', '.json', '.wasm'], modules: [path.resolve(__dirname, 'src'), 'node_modules'], }, }; 提升编译性能(通过跳过不必要的编译步骤等) 1. 开发阶段禁止产物优化 minimize 压缩 concatenateModules 模块连接 tree-shaking 功能(usedExports: false) splitChunks 分包

除了压缩强烈建议开发阶段关闭,其它几项看个人需要。

2. 合适的 sourcemap 配置 比如开发阶段可以设置为:eval/eval-source-map/(none); 生产环境:source-map。 { devtool: __DEV__ ? 'eval' : 'source-map' } 3. 减少 watch 文件范围 module.exports = { watchOptions: { aggregateTimeout: 600, ignored: '**/node_modules', }, }; 4. experiments.lazyCompilation 需要时再编译 { // define a custom backend backend?: (( compiler: Compiler, callback: (err?: Error, api?: BackendApi) => void ) => void) | ((compiler: Compiler) => Promise) | { /** * A custom client. */ client?: string; /** * Specify where to listen to from the server. */ listen?: number | ListenOptions | ((server: typeof Server) => void); /** * Specify the protocol the client should use to connect to the server. */ protocol?: "http" | "https"; /** * Specify how to create the server handling the EventSource requests. */ server?: ServerOptionsImport | ServerOptionsHttps | (() => typeof Server); }, entries?: boolean, imports?: boolean, test?: string | RegExp | ((module: Module) => boolean) } 多线程/多进程处理 thread-loader: 多进程运行loader,官方方案(HappyPack已停止更新)。 parallel-webpack: 多进程运行Webpack构建实例,适合多entry points。 支持多进程的 loader 和 plugin:比如:TerserWebpackPlugin 的多进程模式。 // TerserWebpackPlugin 的多进程模式 module.exports = { optimization: { minimize: true, minimizer: [ new TerserPlugin({ parallel: true, }), ], }, }; 利用更高性能的 swc/esbuild 来压缩 swc:基于Rust开发的 JS compiler; esbuild:基于Go开发的 JS bundler/minifier(vite也使用)。

两者通过 Rust/Go 提高了性能。

module.exports = { optimization: { minimize: true, minimizer: [ new TerserPlugin({ minify: TerserPlugin.swcMinify, // `terserOptions` options will be passed to `swc` (`@swc/core`) // Link to options - https://swc.rs/docs/config-js-minify terserOptions: {}, minify: TerserPlugin.esbuildMinify, // `terserOptions` options will be passed to `esbuild` // Link to options - https://esbuild.github.io/api/#minify // Note: the `minify` options is true by default (and override other `minify*` options), so if you want to disable the `minifyIdentifiers` option (or other `minify*` options) please use: // terserOptions: { // minify: false, // minifyWhitespace: true, // minifyIdentifiers: false, // minifySyntax: true, // }, }), ], }, }; (三)优化 webpack caching (长期缓存) 理解 hash/chunkhash/contenthash

首先了解 webpack 中 hash 相关概念: https://webpack.js.org/configuration/output/#template-strings

hash/fullhash,Compilation-level,本次编译(compilation)的 hash。可以理解为项目级别的,任意改动基本都会导致 hash 变更。

chunkhash,Chunk-level,chunk 的 hash。不同 chunk 之间互不影响。

contenthash,Module-level,模块相关内容的 hash。

cache 第一步:Output FileNames

如上一节,推荐使用 contenthash 来防止微小改动导致所有文件的文件名更新,从而让缓存失效。

module.exports = { entry: "./src/index.js", output: { filename: "[name].[contenthash].js", path: path.resolve(__dirname, "dist"), clean: true } }; cache 第二步:拆出模板代码(Extracting Boilerplate) runtime 代码拆到单独文件 module.exports = { optimization: { runtimeChunk: "single" // 等价于 /** * runtimeChunk: { * name: 'runtime', * }, */ } };

optimization.runtimeChunk设置为"single" 会为所有生成的 chunk 创建一个共用的 runtime。如果设置为false则会在每个 chunk 里面嵌入 runtime。

公共库单独拆出 module.exports = { entry: './src/index.js', output: { filename: '[name].[contenthash].js', path: path.resolve(__dirname, 'dist'), clean: true, }, optimization: { runtimeChunk: "single", splitChunks: { // 所有 node_modules 内的公共包单独打包为 "vendors.contenthash.js" cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all', }, }, }, } };

以上两步让公共包和runtime可以不因为业务代码变更而缓存失效。

cache 第三步:Module Identifiers

假设这样一个情况:新增一个文件'./src/print.js' 并被 './src/index.js' 作为依赖引入使用。重新编译:

The main bundle changed because of its new content. The vendor bundle changed because its module.id was changed. And, the runtime bundle changed because it now contains a reference to a new module.

我们发现 main/vendors/runtime 文件名(hash)都变了。理论上 vendors 应该不变。

引入 optimization.moduleIds="deterministic" 可以解决这类问题:

natural:基于使用顺序的数字 id。模块增减会导致id变更。 deterministic:模块名hash得出的数字id(默认3位)。跟顺序无关,解决了模块增减导致其它chunk的module的id(natural)也变化的问题。

另外 optimization.chunkIds="deterministic" 也是 production 模式默认的。

Concatenate Module(Scope Hoisting)

optimization.concatenateModules允许webpack去查找可以安全串联的模块来串联/合并到一个模块。

webpack之前会把每个module都放入单独的wrapper function,但这会拖慢执行速度。concatenateModules 可以像 RollupJS 之类尽可能把module安全合并,合并到同一个闭包下面。

image

image

对 Concatenate Module 而言:

ES Module 才能开启,Commonjs 无法使用。 模块被多个 Chunk 引用时,由于避免重复打包,也会失效。


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有