目录
Webpack 2.2 中文文档
插件 API(Plugin API)
关于编写插件的高阶介绍,可以从阅读 如何编写插件 开始。
很多 webpack 中的对象都继承了 Tapable 类,暴露了一个 plugin 方法。插件可以使用 plugin 方法注入自定义的构建步骤。你可以看到 compiler.plugin 和 compilation.plugin 被频繁使用。基本上,每个插件的调用都在构建流程中绑定了回调来触发特定的步骤。
每个插件会在 webpack 启动时被安装一次,webpack 通过调用插件的 apply 方法来安装它们,并且传递一个 webpack compiler 对象的引用。然后你可以调用 compiler.plugin 来访问资源的编译和它们独立的构建步骤。下面就是一个示例:
// MyPlugin.js function MyPlugin(options) { // 根据 options 配置你的插件 } MyPlugin.prototype.apply = function(compiler) { compiler.plugin("compile", function(params) { console.log("The compiler is starting to compile..."); }); compiler.plugin("compilation", function(compilation) { console.log("The compiler is starting a new compilation..."); compilation.plugin("optimize", function() { console.log("The compilation is starting to optimize files..."); }); }); compiler.plugin("emit", function(compilation, callback) { console.log("The compilation is going to emit files..."); callback(); }); }; module.exports = MyPlugin;
Then in webpack.config.js
plugins: [ new MyPlugin({options: 'nada'}) ]
插件接口
插件接口有下面两种不同区别:
- 基于时间:
1、同步(默认):就像上面看到的,使用 return 。
2、异步:最后的参数是一个回调。规范为:function(err, result)
3、并行:处理函数(handlers)被并行地调用(异步地)。 -
返回值:
1、没有委托(bailing)(默认):没有返回值。
2、委托:处理函数被按顺序地调用,直到某一个处理函数有返回任何值。
3、并行委托:处理函数被并行地调用(异步地)。产生的第一个返回值(按顺序地)最后会被使用。
4、瀑布流:每个处理函数取得并使用上一个处理函数的结果作为参数。
(译注:这里的处理函数指插件通过 plugin 方法注册的函数)
Compiler 实例
插件需要有 apply 方法(在原型链或绑定原型)来访问 compiler 实例。
MyPlugin.js
function MyPlugin() {}; MyPlugin.prototype.apply = function (compiler) { //现在你可以访问所有的 compiler 实例方法 } module.exports = MyPlugin;
下面这样也可以:
MyFunction.js
function apply(options, compiler) { //现在你可以访问所有的 compiler 实例方法 //和配置 } //这里的小技巧可以简单的向插件传递和检查配置项 module.exports = function(options) { if (options instanceof Array) { options = { include: options }; } if (!Array.isArray(options.include)) { options.include = [ options.include ]; } return { apply: apply.bind(this, options) }; };
run(compiler: Compiler) 异步
编译器的 run 方法用来开始一个 compilation。在跟踪模式(watch mode)将不会被调用。
watch-run(watching: Watching) 异步
编译器的 watch 方法用来开始一个 watching compilation。在普通模式将不会被调用。 The watch method of the Compiler is used to start a watching compilation. This is not called in normal mode.
compilation(c: Compilation, params: Object)
一个 Compilation 被创建了。插件可以使用它来得到 Compilation 的引用。params 对象包含一些有用的引用。
normal-module-factory(nmf: NormalModuleFactory)
一个 NormalModuleFactory 被创建了。插件可以使用它来得到 NormalModuleFactory 对象的引用。
compiler.plugin("normal-module-factory", function(nmf) { nmf.plugin("after-resolve", function(data) { data.loaders.unshift(path.join(__dirname, "postloader.js")); }); });
context-module-factory(cmf: ContextModuleFactory)
一个 ContextModuleFactory 被创建了。插件可以使用它来得到 ContextModuleFactory 对象的引用。
compile(params)
编译器开始编译。在普通模式和跟踪模式都能使用。插件可以在这个时间点来修改 params 对象(比如修饰工厂函数)。
compiler.plugin("compile", function(params) { //你现在处于 "compile" 阶段 });
make(c: Compilation) 并行
插件可以在这个时间点,通过 Compilation 的 addEntry(context, entry, name, callback) 和 prefetch(context, dependency, callback) 方法,向编译添加入口(entries)或者预拉取模块。
after-compile(c: Compilation) 异步
编译过程已经结束,模块已经被封闭(sealed)。下一步是发起输出(emit)生成资源。在这里可以通过一些炫酷的方法使用模块的结果。
处理函数将不会被复制到子编译器中。
emit(c: Compilation) 异步
编译器开始输出生成资源。这里是插件向 c.assets 数组添加生成资源的最后机会。
after-emit(c: Compilation) 异步
编译器已经输出所有生成资源。
done(stats: Stats)
所有任务已经完成。
failed(err: Error)
编译器处在跟踪模式并且一个编译已经失败。
invalid()
编译器处在跟踪模式并且一个文件变化被检测到,编译短暂延迟后马上开始(options.watchDelay)。
after-plugins()
所有从配置对象中提取的插件已经被加到编译器上。
after-resolvers()
所有从配置对象中提取的插件已经被加到解析器上。
Compilation 实例
编译实例继承于编译器。例如,compiler.compilation 是对所有 require 图表中对象的字面上的编译。这个对象可以访问所有的模块和它们的依赖(大部分是循环依赖)。在编译阶段,模块被加载,封闭,优化,分块,哈希和重建等等。这将是编译中任何操作主要的生命周期。
compiler.plugin("compilation", function(compilation) { //主要的编译实例 //随后所有的方法都从 compilation.plugin 上得来 });
normal-module-loader
普通模块加载器,真实地一个一个加载模块图表中所有的模块的函数。 is the function that actually loads all the modules in the module graph (one-by-one).
compilation.plugin('normal-module-loader', function(loaderContext, module) { //这里是所以模块被加载的地方 //一个接一个,此时还没有依赖被创建 });
seal
编译的封闭已经开始。
compilation.plugin('seal', function() { //你已经不能再接收到任何模块 //没有参数 });
optimize
优化编译。
compilation.plugin('optimize', function() { //webpack 已经进入优化阶段 //没有参数 });
optimize-tree(chunks, modules) 异步
树的异步优化。
compilation.plugin('optimize-tree', function(chunks, modules) { });
optimize-modules(modules: Module[])
模块的优化。
compilation.plugin('optimize-modules', function(modules) { //树优化期间处理模块数组 });
after-optimize-modules(modules: Module[])
模块优化已经结束。
optimize-chunks(chunks: Chunk[])
块的优化。
//块的优化在编译中可能运行很长时间 compilation.plugin('optimize-chunks', function(chunks) { //这里一般只有一个块,除非你在配置中指定了多个入口 chunks.forEach(function (chunk) { //块含有模块的循环引用 chunk.modules.forEach(function (module){ //module.loaders, module.rawRequest, module.dependencies, etc. }); }); });
after-optimize-chunks(chunks: Chunk[])
块的优化已经结束。
revive-modules(modules: Module[], records)
从记录中重建模块信息。
optimize-module-order(modules: Module[])
按模块重要性重新排序,第一个模块是最重要的模块,将得到最小的 id。
optimize-module-ids(modules: Module[])
优化模块的 id。
after-optimize-module-ids(modules: Module[])
模块 id 优化已经结束。
record-modules(modules: Module[], records)
存储模块信息到记录。
revive-chunks(chunks: Chunk[], records)
从记录中重建块信息。 Restore chunk info from records.
optimize-chunk-order(chunks: Chunk[])
按块重要性重新排序,第一个块是最重要的模块,将得到最小的 id。
optimize-chunk-ids(chunks: Chunk[])
优化块的 id。
after-optimize-chunk-ids(chunks: Chunk[])
块 id 优化已经结束。
record-chunks(chunks: Chunk[], records)
存储块信息到记录。
before-hash
编译开始哈希前。
after-hash
编译哈希后。
before-chunk-assets
创建块生成资源前。
additional-chunk-assets(chunks: Chunk[])
为块创建附加的生成资源。
record(compilation, records)
存储编译信息到记录。
optimize-chunk-assets(chunks: Chunk[]) 异步
优化块的生成资源。
生成资源被存储在 this.assets,但是它们并不都是块的生成资源。一个 Chunk 有一个 files 属性指出这个块创建的所有文件。附加的生成资源被存储在 this.additionalChunkAssets 中。
这是一个为每个块添加 banner 的例子。
compilation.plugin("optimize-chunk-assets", function(chunks, callback) { chunks.forEach(function(chunk) { chunk.files.forEach(function(file) { compilation.assets[file] = new ConcatSource("\/**Sweet Banner**\/", "\n", compilation.assets[file]); }); }); callback(); });
after-optimize-chunk-assets(chunks: Chunk[])
块生成资源已经被优化。这里是一个来自 @boopathi 的示例插件,详细的输出每个块里有什么。
var PrintChunksPlugin = function() {}; PrintChunksPlugin.prototype.apply = function(compiler) { compiler.plugin('compilation', function(compilation, params) { compilation.plugin('after-optimize-chunk-assets', function(chunks) { console.log(chunks.map(function(c) { return { id: c.id, name: c.name, includes: c.modules.map(function(m) { return m.request; }) }; })); }); }); };
optimize-assets(assets: Object{name: Source}) 异步
优化所有生成资源。
生成资源被存放在 this.assets.
after-optimize-assets(assets: Object{name: Source})
生成资源优化已经结束。
build-module(module)
一个模块构建开始前。
compilation.plugin('build-module', function(module){ console.log('build module'); console.log(module); });
succeed-module(module)
一个模块已经被成功构建。
compilation.plugin('succeed-module', function(module){ console.log('succeed module'); console.log(module); });
failed-module(module)
一个模块构建失败。
compilation.plugin('failed-module', function(module){ console.log('failed module'); console.log(module); });
module-asset(module, filename)
一个模块中的一个生成资源被加到编译中。
chunk-asset(chunk, filename)
一个块中的一个生成资源被加到编译中。
MainTemplate 实例
startup(source, module, hash)
compilation.mainTemplate.plugin('startup', function(source, module, hash) { if (!module.chunks.length && source.indexOf('__ReactStyle__') === -1) { var originName = module.origins && module.origins.length ? module.origins[0].name : 'main'; return ['if (typeof window !== "undefined") {', ' window.__ReactStyle__ = ' + JSON.stringify(classNames[originName]) + ';', '}' ].join('\n') + source; } return source; });
Parser 实例 (compiler.parser)
分析器实例接收一个字符串和回调,当字符串能被匹配到时返回一个表达式。
compiler.plugin('compilation', function(compilation, data) { data.normalModuleFactory.plugin('parser', function(parser, options) { parser.plugin('call require', function(expr) { // you now have a reference to the call expression 现在你可以获取到调用表达式(call expression)对象的引用 }); }); });
program(ast) 委托
获取代码片段的 AST 的通用插件接口。
statement(statement: Statement) 委托
获取代码片段的语句的通用插件接口。
call (expr: Expression) 委托
abc(1) => call abc
a.b.c(1) => call a.b.c
expression (expr: Expression) 委托
abc => expression abc
a.b.c => expression a.b.c
expression ?:(expr: Expression) 委托
(abc ? 1 : 2) => expression ?!
返回一个布尔值来忽略错误路径的分析。
typeof (expr: Expression) 委托
typeof a.b.c => typeof a.b.c
statement if(statement: Statement) 委托
if(abc) {} => statement if
返回一个布尔值来忽略错误路径的分析。
label (statement: Statement) 委托
xyz: abc => label xyz
var (statement: Statement) 委托
var abc, def => var abc + var def
返回 false 对已知的定义不加入变量。
evaluate (expr: Expression) 委托
计算一个表达式。
evaluate typeof (expr: Expression) 委托
计算一个标识符的类型。
evaluate Identifier (expr: Expression) 委托
计算一个标识符是否是未定义的。
evaluate defined Identifier (expr: Expression) 委托
计算一个标识符是否是已定义的。
evaluate CallExpression .(expr: Expression) 委托
计算一个成员函数的调用是否是一个成功的表达式。
NormalModuleFactory
before-resolve(data) 异步 瀑布流
工厂函数开始解析前。 data 对象含有这些属性:
- context 解析目录的绝对路径。
- request 表达式的请求。
插件可以修改这个对象或者传递一个新的对象给回调。
after-resolve(data) 异步 瀑布流
工厂函数解析后。 data 对象含有这些属性:
- request 解析请求。这充当了 NormalModule 的标识符。
- userRequest 用户输入的请求。已被解析,但是不包含预加载器或 post 加载器。
- rawRequest 未解析的请求。
- loaders 解析出的加载器的数组。将传递给 NormalModule 并被执行。
- resource 原始资源。将被 NormalModule 读取。
- parser 将被 NormalModule 使用的分析器。
ContextModuleFactory
before-resolve(data) 异步 瀑布流
after-resolve(data) 异步 瀑布流
alternatives(options: Array) 异步 瀑布流
解析器
- compiler.resolvers.normal 普通模块的解析器
- compiler.resolvers.context 上下文模块的解析器
- compiler.resolvers.loader 加载器的解析器
任何插件都应该使用 this.fileSystem 作为文件系统,因为它具有缓存。它只有异步名称函数,但是如果用户使用同步的文件系统接口,它可以变为同步的表现(比如 enhanced-require)。
拼接路径时应该使用 this.join。它标准化(normalizes)了路径。这里也有一个 this.normalizes 方法。
还有一个可用的委托异步的 forEach 接口 this.forEachBail(array, iterator, callback)。
要传递请求到其他解析插件,使用 this.doResolve(types: String|String[], request: Request, callback) 方法(或者this.doResolve(types, request, message, callback)方法)。types 根据优先级的识别会有多种请求类型的可能。
interface Request { path: String // 请求的当前路径 request: String // 当前的请求字符串 query: String // 当前请求的查询字符串,如果有 module: boolean // 请求是否以模块开始 directory: boolean // 请求是否指向一个目录 file: boolean // 请求是否指向一个文件 resolved: boolean // 请求是否已经被解析 // 在布尔值属性上,undefined 意味着 false } // 示例 // 在 /home/user/project/file.js 文件中:require("../test?charset=ascii") { path: "/home/user/project", request: "../test", query: "?charset=ascii" } // 在 /home/user/project/file.js 文件中:require("test/test/") { path: "/home/user/project", request: "test/test/", module: true, directory: true }
resolve(context: String, request: String)
解析流程开始前。
resolve-step(types: String[], request: Request)
解析流程中一个单独的步骤开始前。
module(request: Request) 异步 瀑布流
一个模块请求被找到并且要被解析。
directory(request: Request) 异步 瀑布流
一个目录请求被找到并且要被解析。
file(request: Request) 异步 瀑布流
一个文件请求被找到并且要被解析。
插件也许可以提供更多扩展点
这里是一个 webpack 提供的默认插件列表。它们都有 (request: Request)(是异步和瀑布流的)。
普通模块的流程和上下文是 module -> module-module -> directory -> file。
加载器的流程是 module -> module-loader-module -> module-module -> directory -> file。
module-module
一个模块应该被在一个特定的目录下被找到。path 包含了这个目录。
module-loader-module(仅加载器)
在模块模板被应用到模块名之前使用。流程将从 module-module 继续。
原文:https://webpack.js.org/api/plugins/
上一篇: 加载器 API(Loader API) 下一篇: 第三部分:文档-配置