插件将Webpack引擎的全部潜能暴露给第三方开发人员。使用分段构建回调,开发人员可以将自己的行为引入Webpack构建过程。插件的开发比起加载器(loaders)的开发更近了一步,因为你需要用一些webpack底层的内部构件来钩住他们。准备去阅读一下webpack的源码吧。
编译器(compiler)和编译(compilation)
在开发插件时,两个最重要的对象是:compiler和compilation。了解他们的角色是扩展Webpack引擎的重要的第一步。
- compiler对象表示完全配置的webpack环境,这个对象在webpack启动时,就会创建,并配置了所有操作设置包括options,loaders,plugins等。当一个插件应用于Webpack环境时,插件将接收对此compiler的一个引用, 使用compiler去访问Webpack主环境。
- 一个compilation对象表示一个单一构建的版本花资产(asset)。在运行Webpack开发中间件时,每次检测到文件更改时都将创建一个新的编译,从而生成一组新的已编译资产。一个compilation表达有compilation还提供了许多回调点,插件可以选择执行自定义操作。
这两个组件是任何Webpack插件(特别是一个编译)的一个组成部分,因此开发人员要熟悉这些源文件:
基本结构
插件是在它们的原型上具有apply方法的可实例化对象。这个apply方法在安装插件时由Webpack编译器调用一次。 apply方法被引用到底层的Webpack compiler,它允许访问compiler回调。 一个简单的插件的结构如下:
function HelloWorldPlugin(options) {
// Setup the plugin instance with options...
}
HelloWorldPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', function() {
console.log('Hello World!');
});
};
module.exports = HelloWorldPlugin;
然后安装插件,只需在你的Webpack config plugins数组中包含一个实例:
var HelloWorldPlugin = require('hello-world');
var webpackConfig = {
// ... config settings here ...
plugins: [
new HelloWorldPlugin({options: true})
]
};
访问编译对象
使用编译器对象,您可以绑定回调,为每个新编译提供引用。 这些编译提供了用于在构建过程中挂钩到许多步骤的回调。
function HelloCompilationPlugin(options) {}
HelloCompilationPlugin.prototype.apply = function(compiler) {
// Setup callback for accessing a compilation:
compiler.plugin("compilation", function(compilation) {
// Now setup callbacks for accessing compilation steps:
compilation.plugin("optimize", function() {
console.log("Assets are being optimized.");
});
});
};
module.exports = HelloCompilationPlugin;
有关编译器,编译和其他重要对象可用的回调的更多信息,请参阅插件API文档。
异步编译插件
一些编译插件步骤是异步的,并且传递一个回调函数,当插件完成运行时必须调用它。
function HelloAsyncPlugin(options) {}
HelloAsyncPlugin.prototype.apply = function(compiler) {
compiler.plugin("emit", function(compilation, callback) {
// Do something async...
setTimeout(function() {
console.log("Done with async work...");
callback();
}, 1000);
});
};
module.exports = HelloAsyncPlugin;
一个简单的示例
一旦我们可以锁定到Webpack编译器和每个单独的编译,,我们可以用webpack引擎做我们想做的事情。我们可以重新格式化现有文件,创建衍生文件或制作全新的资产。
让我们编写一个简单的示例插件,生成一个名为filelist.md的新构建文件; 其内容将列出我们构建中的所有资产文件。 这个插件可能看起来像这样:
function FileListPlugin(options) {}
FileListPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
// Create a header string for the generated file:
var filelist = 'In this build:\n\n';
// Loop through all compiled assets,
// adding a new line item for each filename.
for (var filename in compilation.assets) {
filelist += ('- '+ filename +'\n');
}
// Insert this list into the Webpack build as a new file asset:
compilation.assets['filelist.md'] = {
source: function() {
return filelist;
},
size: function() {
return filelist.length;
}
};
callback();
});
};
module.exports = FileListPlugin;
有用的插件模式
插件给予了无限的机会在Webpack构建系统中执行自定义。这允许您创建自定义资产类型,执行唯一的构建修改,甚至在使用中间件时增强Webpack运行时。以下是在编写插件时非常有用的Webpack的一些功能。
浏览资产,块,模块和依赖关系
在编译被密封之后,编译中的所有结构可以被遍历。
function MyPlugin() {}
MyPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
// Explore each chunk (build output):
compilation.chunks.forEach(function(chunk) {
// Explore each module within the chunk (built inputs):
chunk.modules.forEach(function(module) {
// Explore each source file path that was included into the module:
module.fileDependencies.forEach(function(filepath) {
// we've learned a lot about the source structure now...
});
});
// Explore each asset filename generated by the chunk:
chunk.files.forEach(function(filename) {
// Get the asset source for each file generated by the chunk:
var source = compilation.assets[filename].source();
});
});
callback();
});
};
module.exports = MyPlugin;
- compilation.modules:一个编译中的模块数组(构建输入)。 每个模块管理一个源库中的原文件的构建。
- module.fileDependencies:包含在模块中的源文件路径数组。这包括源JavaScript文件本身(例如:index.js)和所需的所有依赖关系资产文件(样式表,图像等)。 查看依赖关系对于查看哪些源文件属于模块很有用。
- compilation.chunks: 编译中的块数组(构建输出)。 每个块管理最终渲染资产的组成。
- chunk.modules: 包含在块中的模块数组。通过扩展,您可以查看每个模块的依赖关系来查看馈入到一个块中的原始源文件。
- chunk.files:由块生成的输出文件名的数组。 您可以从compilation.assets表中访问这些资产来源。
监控文件监视图
在运行Webpack中间件时,每个编译包括一个fileDependencies数组(什么文件被监视)和一个fileTimestamps哈希,将观察到的文件路径映射到时间戳。这些对于检测编译期间已更改的文件非常有用:
function MyPlugin() {
this.startTime = Date.now();
this.prevTimestamps = {};
}
MyPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
var changedFiles = Object.keys(compilation.fileTimestamps).filter(function(watchfile) {
return (this.prevTimestamps[watchfile] || this.startTime) < (compilation.fileTimestamps[watchfile] || Infinity);
}.bind(this));
this.prevTimestamps = compilation.fileTimestamps;
callback();
}.bind(this));
};
module.exports = MyPlugin;
您还可以将新的文件路径馈送到监视图中,以在这些文件更改时接收编译触发器。 只需将有效的文件路径推送到compilation.fileDependencies数组中,即可将它们添加到watch中。 注意:fileDependencies数组在每个编译中重建,因此您的插件必须将自己的监视依赖项推送到每个编译中,以保持它们被观察。
更改块
与监视图类似,通过跟踪它们的哈希来监视编译中的改变块(或模块)是相当简单的。
function MyPlugin() {
this.chunkVersions = {};
}
MyPlugin.prototype.apply = function(compiler) {
compiler.plugin('emit', function(compilation, callback) {
var changedChunks = compilation.chunks.filter(function(chunk) {
var oldVersion = this.chunkVersions[chunk.name];
this.chunkVersions[chunk.name] = chunk.hash;
return chunk.hash !== oldVersion;
}.bind(this));
callback();
}.bind(this));
};
module.exports = MyPlugin;