首页
关于
Search
1
分享一些收集的Sync Key
5,508 阅读
2
mysql错误ERROR 1130 (HY000): Host 'localhost' is not allowed to connect to this MySQL server
1,634 阅读
3
对比win10系统上的三种软件包管理器scoop、chocolatey、winget
1,626 阅读
4
Resilio Sync 许可证下载
1,594 阅读
5
阿里云盘资源分享
1,249 阅读
前端
CSS
NodeJS
Javascript
小程序
Webpack
Vue
Typescript
Linux
软件教程
云服务器
脚本编程
技术扩展
Scoop
SSR
Youtube-dl
You-Get
Typecho
Annie
奇技淫巧
资源分享
Sync Key
随笔
疑难杂症
mysql
Docker
Python
Serverless
登录
Search
标签搜索
docker
K3S
powershell
scoop
webstorm
jQuery
webpack
typecho
mysql
windows10
linux
typescript
ssh
windows11
vue
git
Sync
fastify
winget
github
偏向技术
累计撰写
99
篇文章
累计收到
2
条评论
首页
栏目
前端
CSS
NodeJS
Javascript
小程序
Webpack
Vue
Typescript
Linux
软件教程
云服务器
脚本编程
技术扩展
Scoop
SSR
Youtube-dl
You-Get
Typecho
Annie
奇技淫巧
资源分享
Sync Key
随笔
疑难杂症
mysql
Docker
Python
Serverless
页面
关于
搜索到
3
篇与
Webpack
的结果
2021-01-13
Webpack配置参数
#入口(entry) 入口起点,默认值是./src/index.js,可以设置为一个或多个入口起点。 #单个入口(简写)语法 用法:entry: string | [string] javascript复制 // 单入口(简写)语法 module.exports = { entry: './src/index.js' }; module.exports = { entry: { main: './src/index.js' } }; module.exports = { entry: [ './src/index.js', './src/app.js' ], output: { filename: 'bundle.js' } }; 123456789101112131415161718 #对象语法 用法:entry: { <entryChunkName> string | [string] } | {} 对象语法会比较繁琐。然而,这是应用程序中定义入口的最可扩展的方式。 javascript复制 module.exports = { entry: { app: './src/app.js', adminApp: './src/adminApp.js' } }; 123456 #常见场景 分离 app(应用程序) 和 vendor(第三方库) 入口 下面示例是告诉 webpack 我们想要配置 2 个单独的入口点,这样你就可以在 vendor.js 中存入未做修改的必要 library 或文件(例如 Bootstrap, jQuery, 图片等),然后将它们打包在一起成为单独的 chunk。内容哈希保持不变,这使浏览器可以独立地缓存它们,从而减少了加载时间。 javascript复制 // webpack.config.js module.exports = { entry: { main: './src/app.js', vendor: './src/vendor.js' } }; // webpack.prod.js module.exports = { output: { filename: '[name].[contenthash].bundle.js' } }; // webpack.dev.js module.exports = { output: { filename: '[name].bundle.js' } }; 12345678910111213141516171819 多页面应用程序 下面示例是告诉 webpack 需要三个独立分离的依赖图,在多页面应用程序中,server 会拉取一个新的 HTML 文档给你的客户端。页面重新加载此新文档,并且资源被重新下载。然而,这给了我们特殊的机会去做很多事,例如使用 `optimization.splitChunks`在新窗口打开 为页面间共享的应用程序代码创建 bundle。由于入口起点数量的增多,多页应用能够复用多个入口起点之间的大量代码/模块,从而可以极大地从这些技术中受益。 javascript复制 // webpack.config.js module.exports = { entry: { pageOne: './src/pageOne/index.js', pageTwo: './src/pageTwo/index.js', pageThree: './src/pageThree/index.js' } }; 12345678 #输出(output) 可以通过配置 output 选项,告知 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个 entry 起点,但只能指定一个 output 配置。 #用法 在 webpack 配置中,output 属性的最低要求是,将它的值设置为一个对象,然后为将输出文件的文件名配置为一个 `output.filename`在新窗口打开: 以下配置将一个单独的 bundle.js 文件输出到 dist 目录中。 javascript复制 // webpack.config.js module.exports = { output: { filename: 'bundle.js', } }; 123456 #多个入口起点 如果配置中创建出多于一个 "chunk"(例如,使用多个入口起点或使用像 CommonsChunkPlugin 这样的插件),则应该使用 占位符(substitutions)在新窗口打开 来确保每个文件具有唯一的名称。 javascript复制 module.exports = { entry: { app: './src/app.js', search: './src/search.js' }, output: { filename: '[name].js', path: __dirname + '/dist' } }; // 写入到硬盘:./dist/app.js, ./dist/search.js 123456789101112 #高级进阶 以下是对资源使用 CDN 和 hash 的复杂示例: javascript复制 // config.js module.exports = { //... output: { path: '/home/proj/cdn/assets/[fullhash]', publicPath: 'https://cdn.example.com/assets/[fullhash]/' } }; 12345678 如果在编译时,不知道最终输出文件的 publicPath 是什么地址,则可以将其留空,并且在运行时通过入口起点文件中的 __webpack_public_path__ 动态设置。 javascript复制 __webpack_public_path__ = myRuntimePublicPath; // 应用程序入口的其余部分 123 #loader loader 用于对模块的源代码进行转换。loader 可以使你在 import 或 "load(加载)" 模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的得力方式。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript 或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件! #示例 例如,你可以使用 loader 告诉 webpack 加载 CSS 文件,或者将 TypeScript 转为 JavaScript。为此,首先安装相对应的 loader: bash复制 npm install --save-dev css-loader ts-loader 1 然后指示 webpack 对每个 .css 使用 `css-loader`在新窗口打开,以及对所有 .ts 文件使用 `ts-loader`在新窗口打开: js复制 // webpack.config.js module.exports = { module: { rules: [ { test: /\.css$/, use: 'css-loader' }, { test: /\.ts$/, use: 'ts-loader' } ] } }; 123456789 #使用 loader 在你的应用程序中,有三种使用 loader 的方式: 配置方式在新窗口打开(推荐):在 webpack.config.js 文件中指定 loader。 内联方式在新窗口打开:在每个 import 语句中显式指定 loader CLI 方式在新窗口打开:在 shell 命令中指定它们。 #配置方式 `module.rules`在新窗口打开 允许你在 webpack 配置中指定多个 loader。 这种方式是展示 loader 的一种简明方式,并且有助于使代码变得简洁和易于维护。同时让你对各个 loader 有个全局概览: loader 从右到左(或从下到上)地取值(evaluate)/执行(execute)。在下面的示例中,从 sass-loader 开始执行,然后继续执行 css-loader,最后以 style-loader 为结束。查看 loader 功能在新窗口打开 章节,了解有关 loader 顺序的更多信息。 js复制 module.exports = { module: { rules: [ { test: /\.css$/, use: [ // [style-loader](/loaders/style-loader) { loader: 'style-loader' }, // [css-loader](/loaders/css-loader) { loader: 'css-loader', options: { modules: true } }, // [sass-loader](/loaders/sass-loader) { loader: 'sass-loader' } ] } ] } }; 12345678910111213141516171819202122 #内联方式 可以在 import 语句或任何 与 "import" 方法同等的引用方式在新窗口打开 中指定 loader。使用 ! 将资源中的 loader 分开。每个部分都会相对于当前目录解析。 vue复制 import Styles from 'style-loader!css-loader?modules!./styles.css'; 1 通过为内联 import 语句添加前缀,可以覆盖 配置在新窗口打开 中的所有 loader, preLoader 和 postLoader: 使用 ! 前缀,将禁用所有已配置的 normal loader(普通 loader) vue复制 import Styles from '!style-loader!css-loader?modules!./styles.css'; 1 使用 !! 前缀,将禁用所有已配置的 loader(preLoader, loader, postLoader) vue复制 import Styles from '!!style-loader!css-loader?modules!./styles.css'; 1 使用 -! 前缀,将禁用所有已配置的 preLoader 和 loader,但是不禁用 postLoaders vue复制 import Styles from '-!style-loader!css-loader?modules!./styles.css'; 1 选项可以传递查询参数,例如 ?key=value&foo=bar,或者一个 JSON 对象,例如 ?{"key":"value","foo":"bar"}。 尽可能使用 module.rules,因为这样可以减少源码中样板文件的代码量,并且可以在出错时,更快地调试和定位 loader 中的问题。 #CLI 方式 还可以通过 CLI 使用 loader: shell复制 webpack --module-bind pug-loader --module-bind 'css=style-loader!css-loader' 1 这会对 .jade 文件使用 pug-loader,以及对 .css 文件使用 `style-loader`在新窗口打开 和 `css-loader`在新窗口打开。 #loader 特性 loader 支持链式调用。链中的每个 loader 会将转换应用在已处理过的资源上。一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript。 loader 可以是同步的,也可以是异步的。 loader 运行在 Node.js 中,并且能够执行任何操作。 loader 可以通过 options 对象配置(仍然支持使用 query 参数来设置选项,但是这种方式已被废弃)。 除了常见的通过 package.json 的 main 来将一个 npm 模块导出为 loader,还可以在 module.rules 中使用 loader 字段直接引用一个模块。 插件(plugin)可以为 loader 带来更多特性。 loader 能够产生额外的任意文件。 可以通过 loader 的预处理函数,为 JavaScript 生态系统提供更多能力。用户现在可以更加灵活地引入细粒度逻辑,例如:压缩、打包、语言翻译和 更多其他特性在新窗口打开。 #解析 loader loader 遵循标准 模块解析在新窗口打开 规则。多数情况下,loader 将从 模块路径在新窗口打开 加载(通常是从 npm install, node_modules 进行加载)。 我们预期 loader 模块导出为一个函数,并且编写为 Node.js 兼容的 JavaScript。通常使用 npm 进行管理 loader,但是也可以将应用程序中的文件作为自定义 loader。按照约定,loader 通常被命名为 xxx-loader(例如 json-loader)。更多详细信息,请查看 编写一个 loader在新窗口打开。 #plugin 插件是 webpack 的 支柱在新窗口打开 功能。webpack 自身也是构建于你在 webpack 配置中用到的相同的插件系统之上! 插件目的在于解决 loader在新窗口打开 无法实现的其他事。 如果在插件中使用了 `webpack-sources`在新窗口打开 的 package,请使用 require('webpack').sources 替代 require('webpack-sources'),以避免持久缓存的版本冲突。 #剖析 webpack 插件是一个具有 `apply`在新窗口打开 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在整个编译生命周期都可以访问 compiler 对象。 javascript复制 // ConsoleLogOnBuildWebpackPlugin.js const pluginName = 'ConsoleLogOnBuildWebpackPlugin'; class ConsoleLogOnBuildWebpackPlugin { apply(compiler) { compiler.hooks.run.tap(pluginName, compilation => { console.log('webpack 构建过程开始!'); }); } } module.exports = ConsoleLogOnBuildWebpackPlugin; 123456789101112 compiler hook 的 tap 方法的第一个参数,应该是驼峰式命名的插件名称。建议为此使用一个常量,以便它可以在所有 hook 中重复使用。 #用法 由于插件可以携带参数/选项,你必须在 webpack 配置中,向 plugins 属性传入一个 new 实例。 取决于你的 webpack 用法,对应有多种使用插件的方式。 #配置方式 javascript复制 // webpack.config.js const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装 const webpack = require('webpack'); // 访问内置的插件 const path = require('path'); module.exports = { entry: './path/to/my/entry/file.js', output: { filename: 'my-first-webpack.bundle.js', path: path.resolve(__dirname, 'dist') }, module: { rules: [ { test: /\.(js|jsx)$/, use: 'babel-loader' } ] }, plugins: [ new webpack.ProgressPlugin(), new HtmlWebpackPlugin({template: './src/index.html'}) ] }; 123456789101112131415161718192021222324 ProgressPlugin 用于自定义编译过程中的进度报告,HtmlWebpackPlugin 将生成一个 HTML 文件,并在其中使用 script 引入一个名为 my-first-webpack.bundle.js 的 JS 文件。 #Node API 方式 在使用 Node API 时,还可以通过配置中的 plugins 属性传入插件。 javascript复制 // some-node-script.js const webpack = require('webpack'); // 访问 webpack 运行时(runtime) const configuration = require('./webpack.config.js'); let compiler = webpack(configuration); new webpack.ProgressPlugin().apply(compiler); compiler.run(function(err, stats) { // ... }); 1234567891011 #配置(Configuration) 你可能已经注意到,很少有 webpack 配置看起来完全相同。这是因为 webpack 的配置文件是 JavaScript 文件,文件内导出了一个 webpack 配置的对象在新窗口打开。 webpack 会根据该配置定义的属性进行处理。 由于 webpack 遵循 CommonJS 模块规范,因此,你可以在配置中使用: 通过 require(...) 引入其他文件 通过 require(...) 使用 npm 下载的工具函数 使用 JavaScript 控制流表达式,例如 ?: 操作符 对 value 使用常量或变量赋值 编写并执行函数,生成部分配置 请在合适的场景,使用这些功能。 虽然技术上可行,但还是应避免如下操作: 当使用 webpack CLI 工具时,访问 CLI 参数(应编写自己的 CLI 工具替代,或者使用 `--env`在新窗口打开) 导出不确定的结果(两次调用 webpack 应产生相同的输出文件) 编写超长的配置(应将配置文件拆分成多个) 此文档中得出最重要的结论是,webpack 的配置可以有许多不同的样式和风格。关键在于,为了易于维护和理解这些配置,需要在团队内部保证一致。 接下来的示例中,展示了 webpack 配置如何实现既可表达,又可灵活配置,这主要得益于配置即为代码: #基本配置 查看:配置章节在新窗口打开中所有支持的配置选项。 javascript复制 // webpack.config.js var path = require('path'); module.exports = { mode: 'development', entry: './foo.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'foo.bundle.js' } }; 1234567891011 #多个 target 除了可以将单个配置导出为 object,function在新窗口打开 或 Promise在新窗口打开 以外,还可以将其导出为多个配置。 查看:导出多个配置在新窗口打开 #使用其它配置语言 webpack 支持由多种编程和数据语言编写的配置文件。 查看:配置语言在新窗口打开 #模块(Modules) 在模块化编程在新窗口打开中,开发者将程序分解为功能离散的 chunk,并称之为 模块。 每个模块都拥有小于完整程序的体积,使得验证、调试及测试变得轻而易举。 精心编写的 模块提供了可靠的抽象和封装界限,使得应用程序中每个模块都具备了条理清晰的设计和明确的目的。 Node.js 从一开始就支持模块化编程。 然而,web 的_模块化_正在缓慢支持中。 在 web 界存在多种支持 JavaScript 模块化的工具,这些工具各有优势和限制。 webpack 从这些系统中汲取了经验和教训,并将_模块_的概念应用到项目的任何文件中。 #何为 webpack 模块 与 Node.js 模块在新窗口打开相比,webpack _模块_能以各种方式表达它们的依赖关系。下面是一些示例: ES2015 `import`在新窗口打开 语句 CommonJS在新窗口打开 require() 语句 AMD在新窗口打开 define 和 require 语句 css/sass/less 文件中的 `@import` 语句在新窗口打开。 stylesheet url(...) 或者 HTML <img src=...> 文件中的图片链接。 #支持的模块类型 webpack 天生支持如下模块类型: ECMAScript 模块在新窗口打开 CommonJS 模块 AMD 模块 Assets在新窗口打开 WebAssembly 模块 通过 loader 可以使 webpack 支持多种语言和预处理器语法编写的模块。loader 向 webpack 描述了如何处理非原生模块,并将相关依赖引入到你的 bundles中。 webpack 社区已经为各种流行的语言和预处理器创建了 loader,其中包括: CoffeeScript在新窗口打开 TypeScript在新窗口打开 ESNext (Babel)在新窗口打开 Sass在新窗口打开 Less在新窗口打开 Stylus在新窗口打开 Elm在新窗口打开 当然还有更多!总得来说,webpack 提供了可定制,强大且丰富的 API,允许在 任何技术栈 中使用,同时支持在开发、测试和生产环境的工作流中做到 无侵入性。 关于 loader 的相关信息,请参考 **loader 列表**在新窗口打开 或 **自定义 loader**在新窗口打开。 #模块解析(Module Resolution) resolver 是一个帮助寻找模块绝对路径的库。 一个模块可以作为另一个模块的依赖模块,然后被后者引用,如下: js复制 import foo from 'path/to/module'; // 或者 require('path/to/module'); 123 所依赖的模块可以是来自应用程序的代码或第三方库。 resolver 帮助 webpack 从每个 require/import 语句中,找到需要引入到 bundle 中的模块代码。 当打包模块时,webpack 使用 enhanced-resolve在新窗口打开 来解析文件路径。 #webpack 中的解析规则 使用 enhanced-resolve,webpack 能解析三种文件路径 #绝对路径 js复制 import '/home/me/file'; import 'C:\\Users\\me\\file'; 123 由于已经获得文件的绝对路径,因此不需要再做进一步解析。 #相对路径 js复制 import '../src/file1'; import './file2'; 12 在这种情况下,使用 import 或 require 的资源文件所处的目录,被认为是上下文目录。在 import/require 中给定的相对路径,会拼接此上下文路径,来生成模块的绝对路径。 #模块路径 js复制 import 'module'; import 'module/lib/file'; 12 在 `resolve.modules`在新窗口打开 中指定的所有目录检索模块。 你可以通过配置别名的方式来替换初始模块路径,具体请参照 `resolve.alias`在新窗口打开 配置选项。 如果 package 中包含 package.json 文件,那么在 `resolve.exportsFields`在新窗口打开 配置选项中指定的字段会被依次查找,package.json 中的第一个字段会根据 package 导出指南在新窗口打开确定 package 中可用的 export。 一旦根据上述规则解析路径后,resolver 将会检查路径是指向文件还是文件夹。如果路径指向文件: 如果文件具有扩展名,则直接将文件打包。 否则,将使用 `resolve.extensions`在新窗口打开 选项作为文件扩展名来解析,此选项会告诉解析器在解析中能够接受那些扩展名(例如 .js,.jsx)。 如果路径指向一个文件夹,则进行如下步骤寻找具有正确扩展名的文件: 如果文件夹中包含 package.json 文件,则会根据 `resolve.mainFields`在新窗口打开 配置中的字段顺序查找,并根据 package.json 中的符合配置要求的第一个字段来确定文件路径。 如果不存在 package.json 文件或 `resolve.mainFields`在新窗口打开 没有返回有效路径,则会根据 `resolve.mainFiles`在新窗口打开 配置选项中指定的文件名顺序查找,看是否能在 import/require 的目录下匹配到一个存在的文件名。 然后使用 `resolve.extensions`在新窗口打开 选项,以类似的方式解析文件扩展名。 webpack 会根据构建目标,为这些选项提供合理的默认在新窗口打开配置。 #解析 loader loader 的解析规则也遵循特定的规范。但是 `resolveLoader`在新窗口打开 配置项可以为 loader 设置独立的解析规则。 #缓存 每次文件系统访问文件都会被缓存,以便于更快触发对同一文件的多个并行或串行请求。在 watch 模式在新窗口打开 下,只有修改过的文件会被从缓存中移出。如果关闭 watch 模式,则会在每次编译前清理缓存。 欲了解更多上述配置信息,请查阅 Resolve API在新窗口打开。 #Module Federation #动机 多个独立的构建可以组成一个应用程序,这些独立的构建之间不应该存在依赖关系,因此可以单独开发和部署它们。 这通常被称作微前端,但并不仅限于此。 #底层概念 我们区分本地模块和远程模块。本地模块即为普通模块,是当前构建的一部分。远程模块不属于当前构建,并在运行时从所谓的容器加载。 加载远程模块被认为是异步操作。当使用远程模块时,这些异步操作将被放置在远程模块和入口之间的下一个 chunk 的加载操作中。如果没有 chunk 加载操作,就不能使用远程模块。 chunk 的加载操作通常是通过调用 import() 实现的,但也支持像 require.ensure 或 require([...]) 之类的旧语法。 容器是由容器入口创建的,该入口暴露了对特定模块的异步访问。暴露的访问分为两个步骤: 加载模块(异步的) 执行模块(同步的) 步骤 1 将在 chunk 加载期间完成。步骤 2 将在与其他(本地和远程)的模块交错执行期间完成。这样一来,执行顺序不受模块从本地转换为远程或从远程转为本地的影响。 容器可以嵌套使用,容器可以使用来自其他容器的模块。容器之间也可以循环依赖。 #重载(Overriding) 容器能够将选定的本地模块标记为“可重载”。容器的使用者能够提供“重载”,即替换容器中的一个“可重载”的模块。当使用者提供重载模块时,容器的所有模块将使用替换模块而非本地模块。当使用者不提供替换模块时,容器的所有模块将使用本地模块。 容器管理可重载模块的方式为:当使用者已经重写它们后,就不需要下载了。这通常是通过将它们放在单独的 chunk 中来实现的。 另一方面,替换模块的提供者,将只提供异步加载函数。它允许容器仅在需要替换模块时才去加载。提供者管理替换模块的方式为:当容器不请求替换模块时,则无需下载。这通常是通过将它们放在单独的 chunk 中来实现的。 "name" 用于标识容器中可重载的模块。 重载(Overrides)的提供和容器暴露模块类似,它分为两个步骤: 加载(异步) 执行(同步) 当嵌套使用时,向容器提供重载将自动覆盖嵌套容器中具有相同 "name" 的模块。 必须在容器模块加载之前提供重载。在初始 chunk 中使用的重载只能被不使用 Promise 的同步模块重载。一旦执行,就不可再次被重载。 #高级概念 每个构建都充当一个容器,也可将其他构建作为容器。通过这种方式,每个构建都能够通过从对应容器中加载模块来访问其他容器暴露出来的模块。 共享模块是指既可重写的又可作为向嵌套容器提供重写的模块。它们通常指向每个构建中的相同模块,例如相同的库。 packageName 选项允许通过设置包名来查找所需的版本。默认情况下,它会自动推断模块请求,当想禁用自动推断时,请将 requiredVersion 设置为 false 。 #构建块(Building blocks) #OverridablesPlugin (底层 API) 这个插件使得特定模块“可重载”。一个本地 API ( __webpack_override__ ) 允许提供重载。 javascript复制 // webpack.config.js const OverridablesPlugin = require('webpack/lib/container/OverridablesPlugin'); module.exports = { plugins: [ new OverridablesPlugin([ { // 通过 OverridablesPlugin 定义一个可重载的模块 test1: './src/test1.js', }, ]), ], }; 123456789101112 javascript复制 // src/index.js __webpack_override__({ // 这里我们重写 test1 模块 test1: () => 'I will override test1 module under src', }); 12345 #ContainerPlugin (底层 API) 该插件使用指定的公开模块来创建一个额外的容器入口。它还会在内部使用 OverridablesPlugin,并向容器的使用者暴露 override API。 #ContainerReferencePlugin (底层 API) 该插件将特定的引用添加到作为外部资源(externals)的容器中,并允许从这些容器中导入远程模块。它还会调用这些容器的 override API 来为它们提供重载。本地的重载(当构建也是一个容器时,通过 __webpack_override__ 或 override API)和指定的重载被提供给所有引用的容器。 #ModuleFederationPlugin (高级 API) 该插件组合了 ContainerPlugin 和 ContainerReferencePlugin。重载(overrides)和可重载(overridables)被合并到指定共享模块的单个列表中。 #概念目标 它既可以暴露,又可以使用 webpack 支持的任何模块类型 代码块加载应该并行加载所需的所有内容(web:到服务器的单次往返) 从使用者到容器的控制 重写模块是一种单向操作 同级容器不能重写彼此的模块。 概念适用于独立于环境 可用于 web、Node.js 等 共享中的相对和绝对请求 会一直提供,即使不使用 会将相对路径解析到 config.context 默认不会使用 requiredVersion 共享中的模块请求 只在使用时提供 会匹配构建中所有使用的相等模块请求 将提供所有匹配模块 将从图中这个位置的 package.json 提取 requiredVersion 当你有嵌套的 node_modules 时,可以提供和使用多个不同的版本 共享中尾部带有 / 的模块请求将匹配所有具有这个前缀的模块请求 #用例 #每个页面单独构建 单页应用的每个页面都是在单独的构建中从容器暴露出来的。主体应用程序(application shell)也是独立构建,会将所有页面作为远程模块来引用。通过这种方式,可以单独部署每个页面。在更新路由或添加新路由时部署主体应用程序。主体应用程序将常用库定义为共享模块,以避免在页面构建中出现重复。 #将组件库作为容器 许多应用程序共享一个通用的组件库,可以将其构建成暴露所有组件的容器。每个应用程序使用来自组件库容器的组件。可以单独部署对组件库的更改,而不需要重新部署所有应用程序。应用程序自动使用组件库的最新版本。 #动态远程容器 该容器接口支持 get 和 init 方法。 init 是一个兼容 async 的方法,调用时,只含有一个参数:共享作用域对象(shared scope object)。此对象在远程容器中用作共享作用域,并由 host 提供的模块填充。 可以利用它在运行时动态地将远程容器连接到 host 容器。 javascript复制 // init.js (async () => { // 初始化共享作用域(shared scope)用提供的已知此构建和所有远程的模块填充它 await __webpack_init_sharing__('default'); const container = window.someContainer; // 或从其他地方获取容器 // 初始化容器 它可能提供共享模块 await container.init(__webpack_share_scopes__.default); const module = await container.get('./module'); })(); 123456789 容器尝试提供共享模块,但是如果共享模块已经被使用,则会发出警告,并忽略所提供的共享模块。容器仍能将其作为降级模块。 你可以通过动态加载的方式,提供一个共享模块的不同版本,从而实现 A/B 测试。 在尝试动态连接远程容器之前,确保已加载容器。 例子: javascript复制 // init.js function loadComponent(scope, module) { return async () => { // 初始化共享作用域(shared scope)用提供的已知此构建和所有远程的模块填充它 await __webpack_init_sharing__('default'); const container = window[scope]; // 或从其他地方获取容器 // 初始化容器 它可能提供共享模块 await container.init(__webpack_share_scopes__.default); const factory = await window[scope].get(module); const Module = factory(); return Module; }; } loadComponent('abtests', 'test123'); 123456789101112131415 查看完整实现在新窗口打开 #故障排除 #Uncaught Error: Shared module is not available for eager consumption 应用程序正急切地执行一个作为全局主机运行的应用程序。有如下选项可供选择: 你可以在模块联邦的高级 API 中将依赖设置为即时依赖,此 API 不会将模块放在异步 chunk 中,而是同步地提供它们。这使得我们在初始块中可以直接使用这些共享模块。但是要注意,由于所有提供的和降级模块是要异步下载的,因此,建议只在应用程序的某个地方提供它,例如 shell。 我们强烈建议使用异步边界(asynchronous boundary)。它将把初始化代码分割成更大的块,以避免任何额外的开销,以提高总体性能。 例如,你的入口看起来是这样的: javascript复制 // index.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root')); 12345 让我们创建 bootstrap.js 文件,并将入口文件的内容放到里面,然后将 bootstrap 引入到入口文件中: index.js diff复制 + import('./bootstrap'); - import React from 'react'; - import ReactDOM from 'react-dom'; - import App from './App'; - ReactDOM.render(<App />, document.getElementById('root')); 12345 bootstrap.js diff复制 + import React from 'react'; + import ReactDOM from 'react-dom'; + import App from './App'; + ReactDOM.render(<App />, document.getElementById('root')); 1234 这种方法有效,但存在局限性或缺点。 通过 ModuleFederationPlugin 将依赖的 eager 属性设置为 true webpack.config.js javascript复制 // ... new ModuleFederationPlugin({ shared: { ...deps, react: { eager: true, } } }); 123456789 #Uncaught Error: Module "./Button" does not exist in container. 错误提示中可能不会显示 "./Button",但是信息看起来差不多。这个问题通常会出现在将 webpack beta.16 升级到 webpack beta.17 中。 在 ModuleFederationPlugin 里,更改 exposes: diff复制 new ModuleFederationPlugin({ exposes: { - 'Button': './src/Button' + './Button':'./src/Button' } }); 123456 #Uncaught TypeError: fn is not a function 此处错误可能是丢失了远程容器,请确保在使用前添加它。 如果已为试图使用远程服务器的容器加载了容器,但仍然看到此错误,则需将主机容器的远程容器文件也添加到 HTML 中。 #依赖图(dependency graph) 每当一个文件依赖另一个文件时,webpack 都会将文件视为直接存在 依赖关系。这使得 webpack 可以获取非代码资源,如 images 或 web 字体等。并会把它们作为 依赖 提供给应用程序。 当 webpack 处理应用程序时,它会根据命令行参数中或配置文件中定义的模块列表开始处理。 从 *入口*在新窗口打开 开始,webpack 会递归的构建一个依赖关系图,这个依赖图包含着应用程序中所需的每个模块,然后将所有模块打包为少量的 bundle —— 通常只有一个 —— 可由浏览器加载。 对于 HTTP/1.1 的应用程序来说,由 webpack 构建的 bundle 非常强大。当浏览器发起请求时,它能最大程度的减少应用的等待时间。而对于 HTTP/2 来说,你还可以使用代码分割在新窗口打开进行进一步优化。 #target 由于 JavaScript 即可以编写服务端代码也可以编写浏览器代码,所以 webpack 提供了多种部署 target,你可以在 webpack 的配置选项在新窗口打开中进行设置。 webpack 的 target 属性,不要和 output.libraryTarget 属性混淆。有关 output 属性的更多信息,请参阅 output 指南在新窗口打开 #用法 想设置 target 属性,只需在 webpack 配置中设置 target 字段: webpack.config.js javascript复制 module.exports = { target: 'node' }; 123 在上述示例中,target 设置为 node,webpack 将在类 Node.js 环境编译代码。(使用 Node.js 的 require 加载 chunk,而不加载任何内置模块,如 fs 或 path)。 每个 target 都包含各种 deployment(部署)/environment(环境)特定的附加项,以满足其需求。具体请参阅 target 可用值在新窗口打开。 #多 target 虽然 webpack 不支持 向 target 属性传入多个字符串,但是可以通过设置两个独立配置,来构建对 library 进行同构: webpack.config.js javascript复制 const path = require('path'); const serverConfig = { target: 'node', output: { path: path.resolve(__dirname, 'dist'), filename: 'lib.node.js' } //… }; const clientConfig = { target: 'web', // <=== 默认为 'web',可省略 output: { path: path.resolve(__dirname, 'dist'), filename: 'lib.js' } //… }; module.exports = [ serverConfig, clientConfig ]; 1234567891011121314151617181920 上述示例中,将会在 dist 文件夹下创建 lib.js 和 lib.node.js 文件。 #资源 从上面选项可以看出,你可以选择部署不同的 target。下面是可以参考的示例和资源: compare-webpack-target-bundles在新窗口打开:测试并查看 webpack target 的绝佳资源。同样包含错误上报。 Boilerplate of Electron-React Application在新窗口打开: 一个关于 electron 主进程和渲染进程构建过程的优秀示例。 #manifest 在使用 webpack 构建的典型应用程序或站点中,有三种主要的代码类型: 你或你的团队编写的源码。 你的源码会依赖的任何第三方的 library 或 "vendor" 代码。 webpack 的 runtime 和 manifest,管理所有模块的交互。 本文将重点介绍这三个部分中的最后部分:runtime 和 manifest,特别是 manifest。 #runtime runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行过程中,webpack 用来连接模块化应用程序所需的所有代码。它包含:在模块交互时,连接模块所需的加载和解析逻辑。包括:已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑。 #manifest 一旦你的应用在浏览器中以 index.html 文件的形式被打开,一些 bundle 和应用需要的各种资源都需要用某种方式被加载与链接起来。在经过打包、压缩、为延迟加载而拆分为细小的 chunk 这些 webpack `优化`在新窗口打开 之后,你精心安排的 /src 目录的文件结构都已经不再存在。所以 webpack 如何管理所有所需模块之间的交互呢?这就是 manifest 数据用途的由来…… 当 compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 "manifest",当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。无论你选择哪种 模块语法在新窗口打开,那些 import 或 require 语句现在都已经转换为 __webpack_require__ 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块。 #问题 所以,现在你应该对 webpack 在幕后工作有一点了解。“但是,这对我有什么影响呢?”,你可能会问。答案是大多数情况下没有。runtime 做完成这些工作:一旦你的应用程序加载到浏览器中,使用 manifest,然后所有内容将展现出魔幻般运行结果。然而,如果你决定通过使用浏览器缓存来改善项目的性能,理解这一过程将突然变得极为重要。 通过使用内容散列(content hash)作为 bundle 文件的名称,这样在文件内容修改时,会计算出新的 hash,浏览器会使用新的名称加载文件,从而使缓存无效。一旦你开始这样做,你会立即注意到一些有趣的行为。即使某些内容明显没有修改,某些 hash 还是会改变。这是因为,注入的 runtime 和 manifest 在每次构建后都会发生变化。 查看_管理输出_指南的 manifest 部分在新窗口打开,了解如何提取 manifest,并阅读下面的指南,以了解更多长效缓存错综复杂之处。 #模块热替换(hot module replacement) 模块热替换(HMR - hot module replacement)功能会在应用程序运行过程中,替换、添加或删除 模块在新窗口打开,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度: 保留在完全重新加载页面期间丢失的应用程序状态。 只更新变更内容,以节省宝贵的开发时间。 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。 #在应用程序中 通过以下步骤,可以做到在应用程序中置换(swap in and out)模块: 应用程序要求 HMR runtime 检查更新。 HMR runtime 异步地下载更新,然后通知应用程序。 应用程序要求 HMR runtime 应用更新。 HMR runtime 同步地应用更新。 你可以设置 HMR,以使此进程自动触发更新,或者你可以选择要求在用户交互时进行更新。 #在 compiler 中 除了普通资源,compiler 需要发出 "update",将之前的版本更新到新的版本。"update" 由两部分组成: 更新后的 manifest在新窗口打开 (JSON) 一个或多个 updated chunk (JavaScript) manifest 包括新的 compilation hash 和所有的 updated chunk 列表。每个 chunk 都包含着全部更新模块的最新代码(或一个 flag 用于表明此模块需要被移除)。 compiler 会确保在这些构建之间的模块 ID 和 chunk ID 保持一致。通常将这些 ID 存储在内存中(例如,使用 webpack-dev-server在新窗口打开 时),但是也可能会将它们存储在一个 JSON 文件中。 #在模块中 HMR 是可选功能,只会影响包含 HMR 代码的模块。举个例子,通过 `style-loader`在新窗口打开 为 style 追加补丁。为了运行追加补丁,style-loader 实现了 HMR 接口;当它通过 HMR 接收到更新,它会使用新的样式替换旧的样式。 类似的,当在一个模块中实现了 HMR 接口,你可以描述出当模块被更新后发生了什么。然而在多数情况下,不需要在每个模块中强行写入 HMR 代码。如果一个模块没有 HMR 处理函数,更新就会冒泡(bubble up)。这意味着某个单独处理函数能够更新整个模块树。如果在模块树的一个单独模块被更新,那么整组依赖模块都会被重新加载。 有关 module.hot 接口的详细信息,请查看 HMR API 页面在新窗口打开。 #在 runtime 中 这件事情比较有技术性……如果你对其内部不感兴趣,可以随时跳到 HMR API 页面在新窗口打开 或 HMR 指南在新窗口打开。 对于模块系统运行时(module system runtime),会发出额外代码,来跟踪模块 parents 和 children 关系。在管理方面,runtime 支持两个方法 check 和 apply。 check 方法,发送一个 HTTP 请求来更新 manifest。如果请求失败,说明没有可用更新。如果请求成功,会将 updated chunk 列表与当前的 loaded chunk 列表进行比较。每个 loaded chunk 都会下载相应的 updated chunk。当所有更新 chunk 完成下载,runtime 就会切换到 ready 状态。 apply 方法,将所有 updated module 标记为无效。对于每个无效 module,都需要在模块中有一个 update handler,或者在此模块的父级模块中有 update handler。否则,会进行无效标记冒泡,并且父级也会被标记为无效。继续每个冒泡,直到到达应用程序入口起点,或者到达带有 update handler 的 module(以最先到达为准,冒泡停止)。如果它从入口起点开始冒泡,则此过程失败。 之后,所有无效 module 都会被(通过 dispose handler)处理和解除加载。然后更新当前 hash,并且调用所有 accept handler。runtime 切换回 idle 状态,一切照常继续。 #揭示内部原理 打包,是指处理某些文件并将其输出为其他文件的能力。 但是,在输入和输出之间,还包括有 模块在新窗口打开, 入口起点在新窗口打开, chunk, chunk 组和许多其他中间部分。 #主要部分 项目中使用的每个文件都是一个 模块在新窗口打开 ./index.js js复制 import app from './app.js'; 1 ./app.js js复制 export default 'the app'; 1 通过互相引用,这些模块会形成一个图(ModuleGraph)数据结构。 在打包过程中,模块会被合并成 chunk。 chunk 合并成 chunk 组,并形成一个通过模块互相连接的图(ModuleGraph)。 那么如何通过以上来描述一个入口起点:在其内部,会创建一个只有一个 chunk 的 chunk 组。 ./webpack.config.js js复制 module.exports = { entry: './index.js' }; 123 这会创建出一个名为 main 的 chunk 组(main 是入口起点的默认名称)。 此 chunk 组包含 ./index.js 模块。随着 parser 处理 ./index.js 内部的 import 时, 新模块就会被添加到此 chunk 中。 另外的一个示例: ./webpack.config.js js复制 module.exports = { entry: { home: './home.js', about: './about.js' } }; 123456 这会创建出两个名为 home 和 about 的 chunk 组。 每个 chunk 组都有一个包含一个模块的 chunk:./home.js 对应 home,./about.js 对应 about 一个 chunk 组中可能有多个 chunk。例如,SplitChunksPlugin在新窗口打开 会将一个 chunk 拆分为一个或多个 chunk。 #chunk chunk 有两种形式: initial(初始化) 是入口起点的 main chunk。此 chunk 包含为入口起点指定的所有模块及其依赖项。 non-initial 是可以延迟加载的块。可能会出现在使用 动态导入(dynamic imports)在新窗口打开 或者 SplitChunksPlugin在新窗口打开 时。 每个 chunk 都有对应的 asset(资源)。资源,是指输出文件(即打包结果)。 webpack.config.js js复制 module.exports = { entry: './src/index.jsx' }; 123 ./src/index.jsx js复制 import React from 'react'; import ReactDOM from 'react-dom'; import('./app.jsx').then(App => { ReactDOM.render(<App />, root); }); 123456 这会创建出一个名为 main 的 initial chunk。其中包含: ./src/index.jsx react react-dom 以及除 ./app.jsx 外的所有依赖 然后会为 ./app.jsx 创建 non-initial chunk,这是因为 ./app.jsx 是动态导入的。 Output: /dist/main.js - 一个 initial chunk /dist/394.js - non-initial chunk 默认情况下,这些 non-initial chunk 没有名称,因此会使用唯一 ID 来替代名称。 在使用动态导入时,我们可以通过使用 magic comment(魔术注释)在新窗口打开 来显式指定 chunk 名称: js复制 import( /* webpackChunkName: "app" */ './app.jsx' ).then(App => { ReactDOM.render(<App />, root); }); 123456 Output: /dist/main.js - 一个 initial chunk /dist/app.js - non-initial chunk #output(输出) 输出文件的名称会受配置中的两个字段的影响: `output.filename`在新窗口打开 - 用于 initial chunk 文件 `output.chunkFilename`在新窗口打开 - 用于 non-initial chunk 文件 在某些情况下,使用 initial 和 non-initial 的 chunk 时,可以使用 output.filename。 这些字段中会有一些 占位符在新窗口打开。常用的占位符如下: [id] - chunk id(例如 [id].js -> 485.js) [name] - chunk name(例如 [name].js -> app.js)。如果 chunk 没有名称,则会使用其 id 作为名称 [contenthash] - 输出文件内容的 md4-hash(例如 [contenthash].js -> 4ea6ff1de66c537eb9b2.js)
2021年01月13日
129 阅读
0 评论
0 点赞
2021-01-07
Webpack插件和loader开发配置
#Compilation Object Compilation 对象有很多可用的方法和钩子。在此页面,我们将会列举出这些可用的方法和属性。 #compilation 对象方法 getStats function 返回当前编译的状态对象。 addModule function (module, cacheGroup) 向当前编译添加一个模块。 参数: module - 要添加的模块 cacheGroup - 模块的 cacheGroup getModule function (module) 通过编译的标识符获取其模块。 参数: module - 要获取的模块。标识符是通过编译使用 module.identifier() 方法从模块中提取的。 findModule function (module) 尝试通过其标识符搜索模块。 参数: module - 要搜索的模块。标识符是通过编译使用 module.identifier() 方法从模块中提取的。 waitForBuildingFinished function (module, callback) 在构建给定模块时运行给定的 callback 函数。 参数: module - 有问题的模块。 callback - 要调用的函数。 buildModule function (module, optional, origin, dependencies) 构建给定的模块。 参数: module - 要构建的模块。 optional - 可选标志。 origin - 请求此模块构建的原始模块。 dependencies - 要构建模块的可选依赖。 processModuleDependencies function (module, callback) 处理给定模块依赖。 参数: module - 要被处理依赖的模块。 callback - 模块依赖处理完成时回调的函数。 addModuleDependencies function (module, dependencies, bail, cacheGroup, recursive, callback) 向模块添加依赖。处理依赖之后被 processModuleDependencies 自动调用。 参数: module - 要添加依赖的模块。 dependencies - 要遍历添加到模块的一组已排序依赖。 bail - 发生错误时是否中断进程并抛出 error。 cacheGroup - 模块的 cacheGroup。 recursive - 是否要递归遍历。 callback - 添加模块依赖之后回调的函数。 addEntry function (context, entry, name, callback) 为编译添加入口。 参数: context - 入口的上下文路径。 entry - 入口依赖。 name - 入口名称。 callback - 添加入口完成之后回调的函数。 prefetch function (context, dependency, callback) 根据给定的依赖创建一个模块。 参数: context - 上下文路径。 dependency - 被用来创建模块的依赖。 callback - 向上一级发送模块的模块回调。 rebuildModule function (module, thisCallback) 触发模块的重建。 参数: module - 要被重建的模块。 thisCallback - 模块重建完成之后调用的函数。 finish function (callback) 完成编译并调用给定的回调。 参数: callback - 编译完成之后调用的函数。 seal function (callback) 封闭编译。 参数: callback - 封闭编译时回调的函数。 unseal function 解除封闭编译。 参数: callback - 解除封闭编译时回调的函数。 reportDependencyErrorsAndWarnings function (module, blocks) 将给定模块的错误和警告添加到编译的错误和警告中。 参数: module - 要被报告错误与警告的模块。 blocks - 一组要报告的依赖块。 addChunkInGroup function (groupOptions, module, loc, request) 将模块添加到现有 chunk 组或创建一个新的组。返回一个 chunkGroup。 参数: groupOptions - chunk 组的选项。 module - 引用 chunk 组的模块。 loc - 引用 chunk 组的位置(模块内部)。 request - 引用 chunk 组的请求。 addChunk function (name) 向 compilation.chunks 创建或添加一个新的 chunk。返回这个 chunk. 参数: name - chunk 的名称。 assignDepth function (module) 为给定的模块及其依赖块递归分配 depth 。 参数: module - 要被分配 depth 的模块。 getDependencyReference function (module, dependency) 返回给定模块对依赖的引用。 参数: module - 有问题的模块。 dependency - 要引用的依赖。 processDependenciesBlocksForChunkGroups function (inputChunkGroups) 通过 Module 图创建 Chunk 图。该过程分为两个阶段完成。阶段一:遍历模块图,在 chunkDependencies 中创建一个基础 chunk 图。阶段二:通过基本 chunk 图遍历所有可能的方法并且跟踪可用模块。遍历过程中 processDependenciesBlocksForChunkGroups 将 chunk 相互连接,并将 Blocks 与 Chunks 连接. 当一个 chunk 的所有模块都已经可用且未连接不需要的 chunk 时,它将停止遍历。 参数: inputChunkGroups - 被处理的 chunk 组。 removeReasonsOfDependencyBlock function (module, block) 移除模块与依赖块之间的关系。 参数: module - 要移除的模块关系。 block - 依赖块。 patchChunksAfterReasonRemoval function (module, chunk) 删除依赖性原因后,修补模块和 chunk 的关系。被 removeReasonsOfDependencyBlock 自动调用。 参数: module - 要修复关系的模块。 chunk - 要修复关系的 chunk。 removeChunkFromDependencies function (block, chunk) 在除去依赖性原因后,从依赖块模块和 chunk 中移除给定的 chunk。会被 removeReasonsOfDependencyBlock 自动调用。 参数: block - Chunk 的块连接。 chunk - 从依赖中删除的块。 sortItemsWithModuleIds function sortItemsWithChunkIds function summarizeDependencies function createHash function createModuleAssets function createChunkAssets function getPath function (filename, data) 返回插值路径。 参数: filename - 用于通过哈希获取资源路径。 data - 数据对象。 getPathWithInfo function (filename, data) 返回插值路径和资源信息。 参数: filename - 用于通过哈希获取资源路径。 data - 数据对象。 createChildCompiler function (name, outputOptions, plugins) 允许在 webpack 中运行另一个 webpack 实例。但是,子编译器会应用不同的设置和配置。他会从父编译器(或者顶级编译器)中复制所有的钩子(hook)和插件(plugin),并且创建一个子 Compiler 实例。 返回值为创建好的 Compiler 实例。 参数: name - 子 Compiler 的名称。 outputOptions - 输出选项。 plugins - 将被提供的 webpack 插件。 checkConstraints function emitAsset function (file, source, assetInfo = {}) webpack 4.40.0 后可用。 参数: file - 资源名称。 source - 资源来源。 assetInfo - 附加资源信息。 updateAsset function (file, newSourceOrFunction, assetInfoUpdateOrFunction) webpack 4.40.0 后可用。 参数: file - 资源名称。 newSourceOrFunction - 新资源来源或将旧资源转换为新资源的函数。 assetInfoUpdateOrFunction - 新资源信息或将旧资源转换为新资源的函数。 deleteAsset function (file) 参数: file —— 资源的文件名 getAssets function webpack 4.40.0 后可用。 返回当前编译下所有资源的数组。 getAsset function (name) webpack 4.40.0 后可用。 参数: name - 要返回的资源名称。 #compiler 钩子 Compiler 模块是 webpack 的主要引擎,它通过 CLI在新窗口打开 传递的所有选项, 或者 Node API在新窗口打开,创建出一个 compilation 实例。 它扩展(extend)自 Tapable 类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler 上注册。 此模块会暴露在 webpack.Compiler, 可以直接通过这种方式使用。 关于更多信息,请查看 这个示例在新窗口打开。 在为 webpack 开发插件时,你可能需要知道每个钩子函数是在哪里调用的。想要了解这些内容,请在 webpack 源码中搜索 hooks.<hook name>.call。 #监听(watching) Compiler 支持可以监控文件系统的 监听(watching)在新窗口打开 机制,并且在文件修改时重新编译。 当处于监听模式(watch mode)时, compiler 会触发诸如 watchRun, watchClose 和 invalid 等额外的事件。 通常在 开发环境在新窗口打开 中使用, 也常常会在 webpack-dev-server 这些工具的底层调用, 由此开发人员无须每次都使用手动方式重新编译。 还可以通过 CLI在新窗口打开 进入监听模式。 #钩子 以下生命周期钩子函数,是由 compiler 暴露, 可以通过如下方式访问: js复制 compiler.hooks.someHook.tap('MyPlugin', (params) => { /* ... */ }); 123 取决于不同的钩子类型,也可以在某些钩子上访问 tapAsync 和 tapPromise。 关于钩子类型的描述,请查看 Tapable 文档在新窗口打开. entryOption SyncBailHook 在 webpack 选项中的 `entry`在新窗口打开 被处理过之后调用。 回调参数:`context`在新窗口打开, `entry`在新窗口打开 js复制 compiler.hooks.entryOption.tap('MyPlugin', (context, entry) => { /* ... */ }); 123 参数:context, entry afterPlugins SyncHook 在初始化内部插件集合完成设置之后调用。 回调参数:compiler afterResolvers SyncHook resolver 设置完成之后触发。 回调参数:compiler environment SyncHook 在初始化配置文件中的插件之后立即调用,在 compiler environment 准备时调用。 afterEnvironment SyncHook 在 environment 钩子之后立即调用,在 compiler environment 完成设置时调用。 beforeRun AsyncSeriesHook 在开始执行一次构建之前调用,compiler.run 方法开始执行后立刻进行调用。 回调参数:compiler additionalPass AsyncSeriesHook This hook allows you to do a one more additional pass of the build. run AsyncSeriesHook 在开始读取 `records`在新窗口打开 之前调用。 回调参数:compiler watchRun AsyncSeriesHook 在监听模式下,一个新的 compilation 触发之后,但在 compilation 实际开始之前执行。 回调参数:compiler normalModuleFactory SyncHook NormalModuleFactory 创建之后调用。 回调参数:normalModuleFactory contextModuleFactory SyncHook ContextModuleFactory 创建之后调用。 回调参数:contextModuleFactory initialize SyncHook 在初始化 compiler 对象时调用。 beforeCompile AsyncSeriesHook 在创建 compilation parameter 之后执行。 回调参数:compilationParams 初始化 compilationParams 变量的示例如下: js复制 compilationParams = { normalModuleFactory, contextModuleFactory, }; 1234 此钩子可用于添加/修改 compilation parameter: js复制 compiler.hooks.beforeCompile.tapAsync('MyPlugin', (params, callback) => { params['MyPlugin - data'] = 'important stuff my plugin will use later'; callback(); }); 1234 compile SyncHook beforeCompile 之后立即调用,但在一个新的 compilation 创建之前。 回调参数:compilationParams thisCompilation SyncHook 初始化 compilation 时调用,在触发 compilation 事件之前调用。 回调参数:compilation, compilationParams compilation SyncHook compilation 创建之后执行。 回调参数:compilation, compilationParams make AsyncParallelHook compilation 结束之前执行。 回调参数:compilation afterCompile AsyncSeriesHook compilation 结束和封印之后执行。 回调参数:compilation shouldEmit SyncBailHook 在输出 asset 之前调用。返回一个布尔值,告知是否输出。 回调参数:compilation js复制 compiler.hooks.shouldEmit.tap('MyPlugin', (compilation) => { // 返回 true 以输出 output 结果,否则返回 false return true; }); 1234 emit AsyncSeriesHook 输出 asset 到 output 目录之前执行。 回调参数:compilation afterEmit AsyncSeriesHook 输出 asset 到 output 目录之后执行。 回调参数:compilation assetEmitted AsyncSeriesHook 在 asset 被输出时执行。此钩子可以访问被输出的 asset 的相关信息,例如它的输出路径和字节内容。 回调参数:file, info 例如,可以通过 info.content 访问 asset 的内容 buffer: js复制 compiler.hooks.assetEmitted.tap( 'MyPlugin', (file, { content, source, outputPath, compilation, targetPath }) => { console.log(content); // <Buffer 66 6f 6f 62 61 72> } ); 123456 done AsyncSeriesHook 在 compilation 完成时执行。 回调参数:stats failed SyncHook 在 compilation 失败时调用。 回调参数:error invalid SyncHook 在一个观察中的 compilation 无效时执行。 回调参数:fileName, changeTime watchClose SyncHook 在一个观察中的 compilation 停止时执行。 infrastructureLog SyncBailHook 在配置中启用 `infrastructureLogging` 选项在新窗口打开 后,允许使用 infrastructure log(基础日志)。 回调参数:name, type, args log SyncBailHook 启用后允许记录到 stats在新窗口打开 对象,请参阅 `stats.logging`, `stats.loggingDebug` 和 `stats.loggingTrace` 选项在新窗口打开。 回调参数:origin, logEntry #compilation 钩子 Compilation 模块会被 Compiler 用来创建新的 compilation 对象(或新的 build 对象)。 compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。 Compilation 类扩展(extend)自 Tapable,并提供了以下生命周期钩子。 可以按照 compiler 钩子的相同方式来调用 tap: js复制 compilation.hooks.someHook.tap(/* ... */); 1 和 compiler 用法相同,取决于不同的钩子类型, 所以也可以在某些钩子上访问 tapAsync 和 tapPromise。 buildModule SyncHook 在模块构建开始之前触发,可以用来修改模块。 回调参数:module js复制 compilation.hooks.buildModule.tap('SourceMapDevToolModuleOptionsPlugin', module => { module.useSourceMap = true; } ); 12345 rebuildModule SyncHook 在重新构建一个模块之前触发。 回调参数:module failedModule SyncHook 模块构建失败时执行。 回调参数:module error succeedModule SyncHook 模块构建成功时执行。 回调参数:module finishModules AsyncSeriesHook 所有模块都完成构建并且没有错误时执行。 回调参数:modules finishRebuildingModule SyncHook 一个模块完成重新构建时执行,在都成功或有错误的情况下。 回调参数:module seal SyncHook compilation 对象停止接收新的模块时触发。 unseal SyncHook compilation 对象开始接收新模块时触发。 optimizeDependencies SyncBailHook 依赖优化开始时触发。 回调参数:modules afterOptimizeDependencies SyncHook 依赖优化之后触发。 回调参数:modules optimize SyncHook 优化阶段开始时触发。 optimizeModules SyncBailHook 在模块优化阶段开始时调用。插件可以 tap 此钩子对模块进行优化。 回调参数:modules afterOptimizeModules SyncHook 在模块优化完成之后调用。 回调参数:modules optimizeChunks SyncBailHook 在 chunk 优化阶段开始时调用。插件可以 tap 此钩子对 chunk 执行优化。 回调参数:chunks afterOptimizeChunks SyncHook chunk 优化完成之后触发。 回调参数:chunks optimizeTree AsyncSeriesHook 在优化依赖树之前调用。插件可以 tap 此钩子执行依赖树优化。 回调参数:chunks modules afterOptimizeTree SyncHook 在依赖树优化成功完成之后调用。 回调参数:chunks modules optimizeChunkModules SyncBailHook 在树优化之后,chunk 模块优化开始时调用。插件可以 tap 此钩子来执行 chunk 模块的优化。 回调参数:chunks modules afterOptimizeChunkModules SyncHook 在 chunk 模块优化成功完成之后调用。 回调参数:chunks modules shouldRecord SyncBailHook 调用来决定是否存储 record。返回任何内容 !== false 将阻止执行所有其他 "record" 钩子(`record`在新窗口打开, `recordModules`在新窗口打开, `recordChunks`在新窗口打开 和 `recordHash`在新窗口打开)。 reviveModules SyncHook 从 record 中恢复模块信息。 回调参数:modules records beforeModuleIds SyncHook 在为每个模块分配 id 之前执行。 回调参数:modules moduleIds SyncHook 调用来每个模块分配一个 id。 回调参数:modules optimizeModuleIds SyncHook 在模块 id 优化开始时调用。 回调参数:modules afterOptimizeModuleIds SyncHook 在模块 id 优化完成时调用。 回调参数:modules reviveChunks SyncHook 从 record 中恢复 chunk 信息。 回调参数:chunks records beforeChunkIds SyncHook 在为每个 chunk 分配 id 之前执行。 回调参数:chunks chunkIds SyncHook 调用时,会为每个 chunk 分配一个 id。 回调函数的参数为:chunks optimizeChunkIds SyncHook 在 chunk id 优化阶段开始时调用。 回调参数:chunks afterOptimizeChunkIds SyncHook chunk id 优化结束之后触发。 回调参数:chunks recordModules SyncHook 将模块信息存储到 record 中。`shouldRecord`在新窗口打开 返回 truthy 值时触发。 回调参数:modules records recordChunks SyncHook 将 chunk 存储到 record 中。`shouldRecord`在新窗口打开 返回 truthy 值时触发。 回调参数:chunks records beforeHash SyncHook 在 compilation 添加哈希(hash)之前。 afterHash SyncHook 在 compilation 添加哈希(hash)之后。 recordHash SyncHook 将有关 record 的信息存储到 records 中。仅在 `shouldRecord`在新窗口打开 返回 truthy 值时触发。 回调参数:records record SyncHook 将 compilation 相关信息存储到 record 中。仅在 `shouldRecord`在新窗口打开 返回 truthy 值时触发。 回调参数:compilation records beforeModuleAssets SyncHook 在创建模块 asset 之前执行。 additionalChunkAssets SyncHook additionalChunkAssets 已弃用(可使用 Compilation.hook.processAssets在新窗口打开 来代替,并且可使用 Compilation.PROCESSASSETS_STAGE* 作为其选项参数。) 为这些 chunk 创建其他 asset。 回调参数:chunks shouldGenerateChunkAssets SyncBailHook 调用以确定是否生成 chunk asset。返回任何 !== false 将允许生成 chunk asset。 beforeChunkAssets SyncHook 在创建 chunk asset 之前。 additionalAssets AsyncSeriesHook 为 compilation 创建额外 asset。 这个钩子可以用来下载图像,例如: js复制 compilation.hooks.additionalAssets.tapAsync('MyPlugin', callback => { download('https://img.shields.io/npm/v/webpack.svg', function(resp) { if(resp.status === 200) { compilation.assets['webpack-version.svg'] = toAsset(resp); callback(); } else { callback(new Error('[webpack-example-plugin] Unable to download the image')); } }); }); 12345678910 optimizeChunkAssets AsyncSeriesHook optimizeChunkAssets 已弃用。(可使用 Compilation.hook.processAssets在新窗口打开 来代替,并且可使用 Compilation.PROCESSASSETS_STAGE* 作为其选项参数。 优化所有 chunk asset。asset 存储在 compilation.assets 中。 每个 Chunk 都具有一个 files 属性,其指向由一个 chunk 创建的所有文件。 任何额外 chunk asset 都存储在 compilation.additionalChunkAssets 中。 回调参数:chunks Here's an example that simply adds a banner to each chunk. js复制 compilation.hooks .optimizeChunkAssets .tapAsync('MyPlugin', (chunks, callback) => { chunks.forEach(chunk => { chunk.files.forEach(file => { compilation.assets[file] = new ConcatSource( '\/**Sweet Banner**\/', '\n', compilation.assets[file] ); }); }); callback(); }); 123456789101112131415 afterOptimizeChunkAssets SyncHook afterOptimizeChunkAssets 已弃用。(可使用 Compilation.hook.processAssets在新窗口打开 来代替,并且可使用 Compilation.PROCESSASSETS_STAGE*作为其选项参数。 chunk asset 已经被优化。 回调参数:chunks 这里是一个来自 @boopathi在新窗口打开 的示例插件,详细地输出每个 chunk 里有什么。 js复制 compilation.hooks.afterOptimizeChunkAssets.tap('MyPlugin', chunks => { chunks.forEach(chunk => { console.log({ id: chunk.id, name: chunk.name, includes: chunk.getModules().map(module => module.request) }); }); }); 123456789 optimizeAssets AsyncSeriesHook 优化存储在 compilation.assets 中的所有 asset。 回调参数:assets afterOptimizeAssets SyncHook asset 已经优化。 回调参数:assets processAssets AsyncSeriesHook Asset processing. Callback Parameters: assets Here's an example: js复制 compilation.hooks.processAssets.tap( { name: 'MyPlugin', stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, // see below for more stages }, (assets) => { // code here } ); 123456789 In addition to name and stage, you can pass a additionalAssets 5.8.0+ option which accepts either a value of true or a function with assets as parameter: true - Run callback against assets added later by plugins. js复制 compilation.hooks.processAssets.tap( { name: 'MyPlugin', stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, additionalAssets: true }, (assets) => { // this callback will run against assets added later by plugins. } ); 12345678910 (assets) => {} - Run this specified callback against assets added later by plugins. js复制 compilation.hooks.processAssets.tap( { name: 'MyPlugin', stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, additionalAssets: (assets) => { // this callback will run against assets added later by plugins. } }, (assets) => { // code } ); 123456789101112 Here's a list of stages we can use: PROCESS_ASSETS_STAGE_ADDITIONAL - Add additional assets to the compilation. PROCESS_ASSETS_STAGE_PRE_PROCESS - Basic preprocessing of the assets. PROCESS_ASSETS_STAGE_DERIVED - Derive new assets from the existing assets. PROCESS_ASSETS_STAGE_ADDITIONS - Add additional sections to the existing assets e.g. banner or initialization code. PROCESS_ASSETS_STAGE_OPTIMIZE - Optimize existing assets in a general way. PROCESS_ASSETS_STAGE_OPTIMIZE_COUNT - Optimize the count of existing assets, e.g. by merging them. PROCESS_ASSETS_STAGE_OPTIMIZE_COMPATIBILITY - Optimize the compatibility of existing assets, e.g. add polyfills or vendor prefixes. PROCESS_ASSETS_STAGE_OPTIMIZE_SIZE - Optimize the size of existing assets, e.g. by minimizing or omitting whitespace. PROCESS_ASSETS_STAGE_DEV_TOOLING - Add development tooling to the assets, e.g. by extracting a source map. PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE 5.8.0+ - Optimize the numbers of existing assets, e.g. by inlining assets into other assets. PROCESS_ASSETS_STAGE_SUMMARIZE - Summarize the list of existing assets. PROCESS_ASSETS_STAGE_OPTIMIZE_HASH - Optimize the hashes of the assets, e.g. by generating real hashes of the asset content. PROCESS_ASSETS_STAGE_OPTIMIZE_TRANSFER - Optimize the transfer of existing assets, e.g. by preparing a compressed (gzip) file as separate asset. PROCESS_ASSETS_STAGE_ANALYSE - Analyze the existing assets. PROCESS_ASSETS_STAGE_REPORT - Creating assets for the reporting purposes. afterProcessAssets SyncHook Called after the `processAssets`在新窗口打开 hook had finished without error. needAdditionalSeal SyncBailHook 调用来决定 compilation 是否需要解除 seal 以引入其他文件。 afterSeal AsyncSeriesHook 在 needAdditionalSeal 之后立即执行。 chunkHash SyncHook 触发来为每个 chunk 生成 hash。 回调参数:chunk chunkHash moduleAsset SyncHook 一个模块中的一个 asset 被添加到 compilation 时调用。 回调参数:module filename chunkAsset SyncHook 一个 chunk 中的一个 asset 被添加到 compilation 时调用。 回调参数:chunk filename assetPath SyncWaterfallHook 调用以决定 asset 的路径。 回调参数:path options needAdditionalPass SyncBailHook 调用以决定 asset 在输出后是否需要进一步处理。 childCompiler SyncHook 子 compiler 设置之后执行。 回调参数:childCompiler compilerName compilerIndex normalModuleLoader 从 webpack v5 开始,normalModuleLoader 钩子已经删除。现在要访问loader 请改用 NormalModule.getCompilationHooks(compilation).loader。 #JavascriptParser Hooks parser 实例,在 compiler 中被发现,是用来解析由 webpack 处理过的每个模块。parser 也是扩展自 tapable 的 webpack 类 并且提供多种 tapable 钩子, 以下示例中,parser 位于 module factories在新窗口打开 中,因此需要调用额外钩子 来进行获取: js复制 compiler.hooks.normalModuleFactory.tap('MyPlugin', factory => { factory.hooks.parser.for('javascript/auto').tap('MyPlugin', (parser, options) => { parser.hooks.someHook.tap(/* ... */); }); }); 12345 和 compiler 用法相同,取决于不同的钩子类型, 也可以在某些钩子上访问 tapAsync 和 tapPromise。 #钩子 以下生命周期钩子函数,是由 parser 暴露,可以通过 如下方式访问: evaluateTypeof SyncBailHook Triggered when evaluating an expression consisting in a typeof of a free variable Hook Parameters: identifier Callback Parameters: expression js复制 parser.hooks.evaluateTypeof.for('myIdentifier').tap('MyPlugin', expression => { /* ... */ return expressionResult; }); 1234 这会触发 evaluateTypeof 钩子的调用: js复制 const a = typeof myIdentifier; 1 This won't trigger: js复制 const myIdentifier = 0; const b = typeof myIdentifier; 12 evaluate SyncBailHook Called when evaluating an expression. Hook parameters: expressionType Callback parameters: expression For example: index.js js复制 const a = new String(); 1 MyPlugin.js js复制 parser.hooks.evaluate.for('NewExpression').tap('MyPlugin', expression => { /* ... */ return expressionResult; }); 1234 Where the expressions types are: 'ArrowFunctionExpression' 'AssignmentExpression' 'AwaitExpression' 'BinaryExpression' 'CallExpression' 'ClassExpression' 'ConditionalExpression' 'FunctionExpression' 'Identifier' 'LogicalExpression' 'MemberExpression' 'NewExpression' 'ObjectExpression' 'SequenceExpression' 'SpreadElement' 'TaggedTemplateExpression' 'TemplateLiteral' 'ThisExpression' 'UnaryExpression' 'UpdateExpression' evaluateIdentifier SyncBailHook Called when evaluating an identifier that is a free variable. Hook Parameters: identifier Callback Parameters: expression evaluateDefinedIdentifier SyncBailHook Called when evaluating an identifier that is a defined variable. Hook Parameters: identifier Callback Parameters: expression evaluateCallExpressionMember SyncBailHook Called when evaluating a call to a member function of a successfully evaluated expression. Hook Parameters: identifier Callback Parameters: expression param This expression will trigger the hook: index.js js复制 const a = expression.myFunc(); 1 MyPlugin.js js复制 parser.hooks.evaluateCallExpressionMember.for('myFunc').tap('MyPlugin', (expression, param) => { /* ... */ return expressionResult; }); 1234 statement SyncBailHook General purpose hook that is called for every parsed statement in a code fragment. Callback Parameters: statement js复制 parser.hooks.statement.tap('MyPlugin', statement => { /* ... */ }); 1 Where the statement.type could be: 'BlockStatement' 'VariableDeclaration' 'FunctionDeclaration' 'ReturnStatement' 'ClassDeclaration' 'ExpressionStatement' 'ImportDeclaration' 'ExportAllDeclaration' 'ExportDefaultDeclaration' 'ExportNamedDeclaration' 'IfStatement' 'SwitchStatement' 'ForInStatement' 'ForOfStatement' 'ForStatement' 'WhileStatement' 'DoWhileStatement' 'ThrowStatement' 'TryStatement' 'LabeledStatement' 'WithStatement' statementIf SyncBailHook Called when parsing an if statement. Same as the statement hook, but triggered only when statement.type == 'IfStatement'. Callback Parameters: statement label SyncBailHook Called when parsing statements with a label在新窗口打开. Those statements have statement.type === 'LabeledStatement'. Hook Parameters: labelName Callback Parameters: statement import SyncBailHook Called for every import statement in a code fragment. The source parameter contains the name of the imported file. Callback Parameters: statement source The following import statement will trigger the hook once: index.js js复制 import _ from 'lodash'; 1 MyPlugin.js js复制 parser.hooks.import.tap('MyPlugin', (statement, source) => { // source == 'lodash' }); 123 importSpecifier SyncBailHook Called for every specifier of every import statement. Callback Parameters: statement source exportName identifierName The following import statement will trigger the hook twice: index.js js复制 import _, { has } from 'lodash'; 1 MyPlugin.js js复制 parser.hooks.importSpecifier.tap('MyPlugin', (statement, source, exportName, identifierName) => { /* First call source == 'lodash' exportName == 'default' identifierName == '_' */ /* Second call source == 'lodash' exportName == 'has' identifierName == 'has' */ }); 123456789101112 export SyncBailHook Called for every export statement in a code fragment. Callback Parameters: statement exportImport SyncBailHook Called for every export-import statement eg: export * from 'otherModule';. Callback Parameters: statement source exportDeclaration SyncBailHook Called for every export statement exporting a declaration. Callback Parameters: statement declaration Those exports will trigger this hook: js复制 export const myVar = 'hello'; // also var, let export function FunctionName(){} export class ClassName {} 123 exportExpression SyncBailHook Called for every export statement exporting an expression e.g.export default expression;. Callback Parameters: statement declaration exportSpecifier SyncBailHook Called for every specifier of every export statement. Callback Parameters: statement identifierName exportName index exportImportSpecifier SyncBailHook Called for every specifier of every export-import statement. Callback Parameters: statement source identifierName exportName index varDeclaration SyncBailHook Called when parsing a variable declaration. Callback Parameters: declaration varDeclarationLet SyncBailHook Called when parsing a variable declaration defined using let Callback Parameters: declaration varDeclarationConst SyncBailHook Called when parsing a variable declaration defined using const Callback Parameters: declaration varDeclarationVar SyncBailHook Called when parsing a variable declaration defined using var Callback Parameters: declaration canRename SyncBailHook Triggered before renaming an identifier to determine if the renaming is allowed. This is usually used together with the rename hook. Hook Parameters: identifier Callback Parameters: expression js复制 var a = b; parser.hooks.canRename.for('b').tap('MyPlugin', expression => { // returning true allows renaming return true; }); 123456 rename SyncBailHook Triggered when renaming to get the new identifier. This hook will be called only if canRename returns true. Hook Parameters: identifier Callback Parameters: expression js复制 var a = b; parser.hooks.rename.for('b').tap('MyPlugin', expression => {}); 123 assigned SyncBailHook Called when parsing an AssignmentExpression before parsing the assigned expression. Hook Parameters: identifier Callback Parameters: expression js复制 a += b; parser.hooks.assigned.for('a').tap('MyPlugin', expression => { // this is called before parsing b }); 12345 assign SyncBailHook Called when parsing an AssignmentExpression before parsing the assign expression. Hook Parameters: identifier Callback Parameters: expression js复制 a += b; parser.hooks.assigned.for('a').tap('MyPlugin', expression => { // this is called before parsing a }); 12345 typeof SyncBailHook Triggered when parsing the typeof of an identifier Hook Parameters: identifier Callback Parameters: expression call SyncBailHook Called when parsing a function call. Hook Parameters: identifier Callback Parameters: expression js复制 eval(/* something */); parser.hooks.call.for('eval').tap('MyPlugin', expression => {}); 123 callAnyMember SyncBailHook Triggered when parsing a call to a member function of an object. Hook Parameters: objectIdentifier Callback Parameters: expression js复制 myObj.anyFunc(); parser.hooks.callAnyMember.for('myObj').tap('MyPlugin', expression => {}); 123 new SyncBailHook Invoked when parsing a new expression. Hook Parameters: identifier Callback Parameters: expression js复制 new MyClass(); parser.hooks.new.for('MyClass').tap('MyPlugin', expression => {}); 123 expression SyncBailHook Called when parsing an expression. Hook Parameters: identifier Callback Parameters: expression js复制 const a = this; parser.hooks.expression.for('this').tap('MyPlugin', expression => {}); 123 expressionConditionalOperator SyncBailHook Called when parsing a ConditionalExpression e.g. condition ? a : b Callback Parameters: expression program SyncBailHook Get access to the abstract syntax tree (AST) of a code fragment Parameters: ast comments #Plugin API 插件是 webpack 生态的关键部分, 它为社区用户提供了一种强有力的方式来直接触及 webpack 的编译过程(compilation process)。 插件能够 hook在新窗口打开 到每一个编译(compilation)中发出的关键事件中。 在编译的每个阶段中,插件都拥有对 compiler 对象的完全访问能力, 并且在合适的时机,还可以访问当前的 compilation 对象。 #Tapable 这个小型库是 webpack 的一个核心工具,但也可用于其他地方, 以提供类似的插件接口。 在 webpack 中的许多对象都扩展自 Tapable 类。 它对外暴露了 tap,tapAsync 和 tapPromise 等方法, 插件可以使用这些方法向 webpack 中注入自定义构建的步骤,这些步骤将在构建过程中触发。 请查阅文档在新窗口打开了解更多知识。 理解上面的的三种 tap 方法, 以及提供这些方法的钩子(hooks)对于编写插件来说是至关重要的。 那些扩展自 Tapable 的对象(例如:compiler), 以及其提供的钩子(hooks)和每个钩子的类型(例如:同步钩子(SyncHook))值得关注。 #插件类型 根据使用不同的钩子(hooks)和 tap 方法, 插件可以以多种不同的方式运行。 这个工作方式与 Tapable 提供的钩子(hooks)在新窗口打开密切相关。 compiler hooks在新窗口打开 分别记录了 Tapable 内在的钩子, 并指出哪些 tap 方法可用。 所以,依赖于使用的 tap 方法的不同, 插件可能会以不同的方式运行。 例如:当你钩入到 编译(compile) 阶段时,只有同步的 tap 方法可以使用。 js复制 compiler.hooks.compile.tap('MyPlugin', params => { console.log('以同步方式触及 compile 钩子。'); }); 123 然而,对于可以使用 AsyncHook 的 run 阶段, 则需使用 tapAsync 或 tapPromise(以及 tap)方法。 js复制 compiler.hooks.run.tapAsync('MyPlugin', (source, target, routesList, callback) => { console.log('以异步方式触及 run 钩子。'); callback(); }); compiler.hooks.run.tapPromise('MyPlugin', (source, target, routesList) => { return new Promise(resolve => setTimeout(resolve, 1000)).then(() => { console.log('以具有延迟的异步方式触及 run 钩子。'); }); }); compiler.hooks.run.tapPromise('MyPlugin', async (source, target, routesList) => { await new Promise(resolve => setTimeout(resolve, 1000)); console.log('以具有延迟的异步方式触及 run 钩子。'); }); 123456789101112131415 这些需求(story)的含义在于, 我们可以有多种方式 hook 到 compiler 中,可以让各种插件都以合适的方式去运行。 #自定义钩子 为了便于其他插件的编译过程中可以 tap 到,则需要创建一个新的 hook, 我们只需要简单的从 tapable 中 require 所需的 hook 类,并创建: js复制 const SyncHook = require('tapable').SyncHook; if (compiler.hooks.myCustomHook) throw new Error('已存在该钩子'); compiler.hooks.myCustomHook = new SyncHook(['a', 'b', 'c']); // 在你想要触发钩子的位置/时机下调用…… compiler.hooks.myCustomHook.call(a, b, c); 1234567 再次声明, 查看 tapable 文档在新窗口打开 来了解更多不同的钩子类(hook class),以及它们是如何工作的。 #进度报告 插件能够通过 `ProgressPlugin`在新窗口打开 这个在默认情况下将信息打印到标准错误输出(stderr)的插件来进行进度报告。如果想要使用这个功能,只需要在使用 webpack CLI在新窗口打开 的时候传入 --progress 参数。 如果想要自定义打印输出,只需要传递不同的参数到 `ProgressPlugin`在新窗口打开 的 reportProgress 方法。 如果想要报告进度,插件必须在 tap 到 hook 的时候使用 context: true 选项。 js复制 compiler.hooks.emit.tapAsync({ name: 'MyPlugin', context: true }, (context, compiler, callback) => { const reportProgress = context && context.reportProgress; if (reportProgress) reportProgress(0.95, 'Starting work'); setTimeout(() => { if (reportProgress) reportProgress(0.95, 'Done work'); callback(); }, 1000); }); 1234567891011 reportProgress 方法在被调用的时候会传入以下的参数: js复制 reportProgress(percentage, ...args); 1 percentage:此参数未使用。作为代替,`ProgressPlugin`在新窗口打开 插件会基于当前的钩子(hook)计算进度。 ...args:任意数量的字符串,这些字符串会传递给 ProgressPlugin 插件并报告给用户。 注意:只有 compiler 和 compilation 钩子的子集才支持 reportProgress 方法。请查看 `ProgressPlugin`在新窗口打开 了解更多信息 #日志 日志的 API 在 webpack 4.37 版本后提供支持。当 logging 在 `统计配置(stats configuration)`在新窗口打开中可用和(或)当 `infrastructure logging`在新窗口打开 可用的时候,插件会通过各自的记录格式(stats,infrastructure)打印信息。 插件可以使用 compilation.getLogger('PluginName') 来做记录。这种形式的记录保存在统计数据(Stats)中并做相应的格式化。它能够被用户过滤和导出。 插件也可以使用 compilation.getInfrastructureLogger('PluginName') 来做记录。使用 infrastructure 的形式并不会被保存在统计数据(Stats)中,因此也不会被格式化。它通常直接将记录载入到 console/dashboard/GUI 中。它能够被用户过滤。 插件也可以使用特殊的降级逻辑 compilation.getLogger ? compilation.getLogger('PluginName') : console 来检测是否支持记录,以此来在不支持 compilation.getLogger 方法的旧版本 webpack 中提供降级方法。 #解析器(Resolvers) 解析器是使用 enhanced-resolve 库创建的。Resolver 类 拓展了 tapable 类,并使用 tapable 来提供了一些钩子。 enhanced-resolve 可以直接用于创建新的解析器, 但是,任何 `compiler` 实例在新窗口打开 都有一些解析器实例,可以 被 tap 进去。 在继续阅读之前,请确保你已经读过 `enhanced-resolve`在新窗口打开 和 `tapable`在新窗口打开 文档。 #类型 在 compiler 类中,提供了三种类型的内置解析器: normal: 通过绝对或相对路径解析模块。 context: 在给定的上下文中解析模块。 loader: 解析 webpack loader在新窗口打开。 根据需要,任一个被使用在 compiler 中的内置解析器, 可以通过插件进行定制: js复制 compiler.resolverFactory.hooks.resolver.for('[type]').tap('name', resolver => { // you can tap into resolver.hooks now resolver.hooks.result.tap('MyPlugin', result => { return result; }); }); 123456 其中,[type] 是上述三个解析器之一。 请参阅 `enhanced-resolve` documentation在新窗口打开 以获得钩子的完整列表以及它们的介绍。 #配置选项 上述解析器也可以 利用 `resolve`在新窗口打开 or `resolveLoader`在新窗口打开 选项,通过配置文件进行定制。这些选项允许 用户可以通过多种选项来更改解析行为,包括 通过解析 plugins。 解析器插件,例如:`DirectoryNamedPlugin`在新窗口打开,可以直接引入 在 resolve.plugins,而不是直接在 `plugins` configuration option在新窗口打开 中使用。 请注意,resolve 配置会影响 normal 解析器和 context 解析器,而“ resolveLoader 用于修改 loader 解析器。
2021年01月07日
161 阅读
0 评论
0 点赞
webpack中使用typescript引入lodash报错
#webpack中使用typescript引入lodash方式 shell复制 npm install lodash --save npm install @types/lodash --save-dev 12 index.ts js复制 /** * 如果使用 import _ from 'lodash' 引入,会报以下提示: * TS1259: Module '"……/node_modules/@types/lodash/index"' can only be default-imported using the 'esModuleInterop' flag * 如果想要保留这种引入方式,需要在文件 tsconfig.json 中设置"allowSyntheticDefaultImports" : true 和 "esModuleInterop" : true */ import * as _ from 'lodash' _.join(['Hello', 'Typescript'], ' ') 12345678
2021年01月03日
313 阅读
0 评论
0 点赞