插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。创建插件比创建 loader 更加高级,因为你将需要理解一些 webpack 底层的内部特性来做相应的钩子
webpack 插件由以下组成:
class CompilationPlugin{
constructor(options) {
this.options=options;
}
apply(compiler) {
compiler.hooks.compilation.tap('CompilationPlugin',function (compilation) {
compilation.hooks.optimize.tap('optimize',function () {
console.log('资源正在被优化');
});
});
}
}
module.exports=CompilationPlugin;
在插件开发中最重要的两个资源就是compiler和compilation对象。理解它们的角色是扩展webpack引擎重要的第一步。
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
插件是由「具有 apply 方法的 prototype 对象」所实例化出来的。这个 apply 方法在安装插件时,会被 webpack compiler 调用一次。apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象。一个简单的插件结构如下:
class DonePlugin{
constructor(options) {
this.options=options;
}
apply(compiler) {
compiler.hooks.done.tap('DonePlugin', ()=> {
console.log('Hello ',this.options.name);
});
}
}
module.exports=DonePlugin;
然后,要安装这个插件,只需要在你的 webpack 配置的 plugin 数组中添加一个实例:
const DonePlugin=require('./plugins/DonePlugin');
module.exports={
entry: './src/index.js',
output: {
path: path.resolve('build'),
filename:'bundle.js'
},
plugins: [
new DonePlugin({name:'zfpx'})
]
}
使用 compiler 对象时,你可以绑定提供了编译 compilation 引用的回调函数,然后拿到每次新的 compilation 对象。这些 compilation 对象提供了一些钩子函数,来钩入到构建流程的很多步骤中。
class CompilationPlugin{
constructor(options) {
this.options=options;
}
apply(compiler) {
compiler.hooks.compilation.tap('CompilationPlugin',function (compilation) {
compilation.hooks.optimize.tap('optimize',function () {
console.log('资源正在被优化');
});
});
}
}
module.exports=CompilationPlugin;
关于 compiler, compilation 的可用回调,和其它重要的对象的更多信息,请查看 插件 文档。
有一些编译插件中的步骤是异步的,这样就需要额外传入一个 callback 回调函数,并且在插件运行结束时,必须调用这个回调函数。
class CompilationAsyncPlugin{
constructor(options) {
this.options=options;
}
apply(compiler) {
compiler.hooks.emit.tapAsync('EmitPlugin',function (compilation,callback) {
setTimeout(function () {
console.log('异步任务完成');
callback();
},500);
});
}
}
module.exports=CompilationAsyncPlugin;
class FileListPlugin{
constructor() {
}
apply(compiler) {
compiler.hooks.emit.tap('FileListPlugin',function (compilation) {
let fileList='filelist:\n\n';
for (let filename in compilation.assets) {
fileList+=('- '+filename+'\n');
}
compilation.assets['filelist.md']={
source() {
return fileList;
},
size() {
return fileList.length
}
}
});
}
}
module.exports=FileListPlugin;
const path=require('path');
function InlineWebpackPlugin() {
}
InlineWebpackPlugin.prototype.apply=function (compiler) {
let self=this;
compiler.hooks.compilation.tap('compilation',function (compilation) {
compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('InlineWebpackPlugin',function (htmlPluginData,callback) {
if (!htmlPluginData.plugin.options.inlineSource) {
return callback(null,htmlPluginData);
}
let regextStr=htmlPluginData.plugin.options.inlineSource;
let result=self.processTags(compilation,regextStr,htmlPluginData);
console.log(result);
callback(null,result);
});
});
}
InlineWebpackPlugin.prototype.processTags=function (compilation,regextStr,htmlPluginData) {
let self=this;
let head=[];
let body=[];
let regex=new RegExp(regextStr);
htmlPluginData.head.forEach(tag => head.push(self.processTag(compilation,regex,tag)));
htmlPluginData.body.forEach(tag => body.push(self.processTag(compilation,regex,tag)));
return {...htmlPluginData,head,body}
}
InlineWebpackPlugin.prototype.processTag=function (compilation,regex,tag) {
console.log(tag);
let assetUrl;
if (tag.tagName == 'script' && regex.test(tag.attributes.src)) {
assetUrl=tag.attributes.src;
tag={
tagName: 'script',
closeTag: true,
attributes: {
type:'text/javascript'
}
}
} else if(tag.tagName == 'link' && regex.test(tag.attributes.href)) {
assetUrl=tag.atttributes.href;
tag={
tagName: 'style',
closeTag: true,
attributes: {
type:'text/css'
}
}
}
if (assetUrl) {
let asset=compilation.assets[assetUrl];
tag.innerHTML=asset.source();
}
return tag;
}
module.exports=InlineWebpackPlugin;
class CompilationAsyncPlugin{
constructor(options) {
this.options=options;
}
apply(compiler) {
compiler.hooks.emit.tapAsync('EmitPlugin',function (compilation,callback) {
setTimeout(function () {
console.log('异步任务完成');
callback();
},5000);
});
}
}
module.exports=CompilationAsyncPlugin;
const qiniu=require('qiniu');
const path=require('path');
const fs=require('fs');
class UploadPlugin{
constructor(options) {
this.options=options||{};
}
apply(compiler) {
compiler.hooks.afterEmit.tap('UploadPlugin', (compilation) => {
let {bucket='video',domain="img.zhufengpeixun.cn",accessKey='Vfi5imW04AkxJItuFbbRy1ffH1HIoo17HbWOXw5f',secretKey='yru__Na4qIor4-V7U4AOJyp2KBUYEw1NWduiJ4Pb'}=this.options;
var mac=new qiniu.auth.digest.Mac(accessKey,secretKey);
var options = {
scope: bucket,
};
var putPolicy = new qiniu.rs.PutPolicy(options);
var uploadToken=putPolicy.uploadToken(mac);
var config = new qiniu.conf.Config();
console.log(compilation.assets);
let promises = Object.keys(compilation.assets).map(asset => upload(asset));
Promise.all(promises).then(function (data) {
console.log('发布CDN成功!');
});
function upload(filename) {
return new Promise(function (resolve,reject) {
var localFile=path.join(__dirname,'../build',filename);
var formUploader = new qiniu.form_up.FormUploader(config);
var putExtra = new qiniu.form_up.PutExtra();
var key=filename;
formUploader.putFile(uploadToken,key,localFile,putExtra,function (err,body,info) {
if (err)
reject(err);
else
resolve(body)
});
});
class FileListPlugin{
constructor() {
}
apply(compiler) {
compiler.hooks.emit.tap('FileListPlugin',function (compilation) {
let fileList='filelist:\n\n';
for (let filename in compilation.assets) {
fileList+=('- '+filename+'\n');
}
compilation.assets['filelist.md']={
source() {
return fileList;
},
size() {
return fileList.length
}
}
});
}
}
module.exports=UploadPlugin;
module.exports=FileListPlugin;