插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。创建插件比创建 loader 更加高级,因为你将需要理解一些 webpack 底层的内部特性来做相应的钩子
对象 | 钩子 |
---|---|
Compiler | run,compile,compilation,make,emit,done |
Compilation | buildModule,normalModuleLoader,succeedModule,finishModules,seal,optimize,after-seal |
Module Factory | beforeResolver,afterResolver,module,parser |
Module | |
Parser] | program,statement,call,expression |
Template | hash,bootstrap,localVars,render |
webpack 插件由以下组成:
在插件开发中最重要的两个资源就是compiler
和compilation
对象。理解它们的角色是扩展webpack引擎重要的第一步。
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
plugin.apply(compiler);
}
}
class DonePlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.done.tap('DonePlugin', (stats) => {
console.log('Hello ', this.options.name);
});
}
}
module.exports = DonePlugin;
class DonePlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.done.tapAsync('DonePlugin', (stats, callback) => {
console.log('Hello ', this.options.name);
callback();
});
}
}
module.exports = DonePlugin;
const DonePlugin=require('./plugins/DonePlugin');
module.exports={
entry: './src/index.js',
output: {
path: path.resolve('build'),
filename:'bundle.js'
},
plugins: [
new DonePlugin({name:'zfpx'})
]
}
if (this.hooks.shouldEmit.call(compilation) === false) {
const stats = new Stats(compilation);
stats.startTime = startTime;
stats.endTime = Date.now();
+ this.hooks.done.callAsync(stats, err => {
+ if (err) return finalCallback(err);
+ return finalCallback(null, stats);
+ });
return;
}
plugins\asset-plugin.js
class AssetPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.compilation.tap('AssetPlugin', function (compilation) {
compilation.hooks.chunkAsset.tap('AssetPlugin', function (chunk, filename) {
console.log('filename=', filename);
});
});
}
}
module.exports = AssetPlugin;
newCompilation(params) {
const compilation = this.createCompilation();
this.hooks.compilation.call(compilation, params);
return compilation;
}
chunk.files.push(file);
+this.hooks.chunkAsset.call(chunk, file);
关于 compiler, compilation 的可用回调,和其它重要的对象的更多信息,请查看 插件 文档。
const { RawSource } = require("webpack-sources");
const JSZip = require("jszip");
const path = require("path");
class ZipPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
let that = this;
compiler.hooks.emit.tapAsync("ZipPlugin", (compilation, callback) => {
var zip = new JSZip();
for (let filename in compilation.assets) {
const source = compilation.assets[filename].source();
zip.file(filename, source);
}
zip.generateAsync({ type: "nodebuffer" }).then(content => {
compilation.assets[that.options.filename] = new RawSource(content);
callback();
});
});
}
}
module.exports = ZipPlugin;
webpack.config.js
plugins: [
+ new zipPlugin({
+ filename: 'assets.zip'
+ })
]
external
script
能否检测代码中的import自动处理这个步骤?
external
和script
的问题,需要怎么实现,该从哪方面开始考虑依赖
当检测到有import
该library
时,将其设置为不打包类似exteral
,并在指定模版中加入script,那么如何检测import?这里就用Parser
external依赖
需要了解external是如何实现的,webpack的external是通过插件ExternalsPlugin
实现的,ExternalsPlugin通过tap
NormalModuleFactory
在每次创建Module的时候判断是否是ExternalModule
Parser
获取需要指定类型moduleType,一般使用javascript/auto
即可plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
filename:'index.html'
}),
new AutoExternalPlugin({
jquery: {
expose: '$',
url: 'https://cdn.bootcss.com/jquery/3.1.0/jquery.js'
}
})
]
const ExternalModule = require('webpack/lib/ExternalModule');
class AutoExternalPlugin {
constructor(options) {
this.options = options;
this.externalModules = {};
}
apply(compiler) {
//1.在解析语法树的过程中查找那些需要外部引入的模块名称
compiler.hooks.normalModuleFactory.tap('AutoExternalPlugin', normalModuleFactory => {
normalModuleFactory.hooks.parser
.for('javascript/auto')
.tap('AutoExternalPlugin', parser => {
parser.hooks.import.tap('AutoExternalPlugin', (statement, source) => {
if (this.options[source])
this.externalModules[source] = true;
});
});
//2.在生产模块的过程中发现如果是外部模块则返回外部模块
normalModuleFactory.hooks.factory.tap('AutoExternalPlugin', factory => (data, callback) => {
const dependency = data.dependencies[0];
let value = dependency.request;//jquery
if (this.externalModules[value]) {
let varName = this.options[value].expose;
callback(null, new ExternalModule(varName, 'window'));
} else {
factory(data, callback);
}
});
});
compiler.hooks.compilation.tap('AutoExternalPlugin', compilation => {
//3.向body底部插入全局变量的脚本
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('normalModuleFactory', (htmlPluginData, callback) => {
Object.keys(this.externalModules).forEach(name => {
let src = this.options[name].url;
htmlPluginData.body.unshift({
tagName: 'script',
closeTag: true,
attributes: { type: 'text/javascript', src }
});
});
callback(null, htmlPluginData);
});
});
}
}
module.exports = AutoExternalPlugin;