plugin
插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。创建插件比创建 loader 更加高级,因为你将需要理解一些 webpack 底层的内部特性来做相应的钩子
2. 创建插件 [#](#t12. 创建插件)
webpack 插件由以下组成:
- 一个 JavaScript 命名函数。
- 在插件函数的 prototype 上定义一个 apply 方法。
- 指定一个绑定到 webpack 自身的事件钩子。
- 处理 webpack 内部实例的特定数据。
- 功能完成后调用 webpack 提供的回调。
3. Compiler 和 Compilation [#](#t23. Compiler 和 Compilation)
在插件开发中最重要的两个资源就是compiler
和compilation
对象。理解它们的角色是扩展webpack引擎重要的第一步。
compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。
compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
4. 基本插件架构 [#](#t34. 基本插件架构)
- 插件是由「具有 apply 方法的 prototype 对象」所实例化出来的。
- 这个 apply 方法在安装插件时,会被 webpack compiler 调用一次。
- apply 方法可以接收一个 webpack compiler 对象的引用,从而可以在回调函数中访问到 compiler 对象。
webpack/lib/webpack.js:35
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', ()=> {
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:'zxmf'})
]
}
webpack/lib/Compiler.js:251
this.emitRecords(err => {
if (err) return finalCallback(err);
this.hooks.done.callAsync(stats, err => {});
});
5. 访问 compilation 对象 [#](#t45. 访问 compilation 对象)
使用 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;
webpack/lib/Compiler.js:496
newCompilation(params) {
const compilation = this.createCompilation();
this.hooks.compilation.call(compilation, params);
return compilation;
}
webpack/lib/Compilation.js:1183
seal(callback) {
this.hooks.seal.call();
this.hooks.optimize.call();
}
关于 compiler, compilation 的可用回调,和其它重要的对象的更多信息,请查看 插件 文档。
6. 异步编译插件 [#](#t56. 异步编译插件)
有一些编译插件中的步骤是异步的,这样就需要额外传入一个 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;
emit
事件在即将写入文件前触发 webpack/lib/Compiler.js:364
this.hooks.emit.callAsync(compilation, err => {
if (err) return callback(err);
outputPath = compilation.getPath(this.outputPath);
this.outputFileSystem.mkdirp(outputPath, emitFiles);
});
7. 输出文件列表 [#](#t67. 输出文件列表)
class FileListPlugin{
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.emit.tap('FileListPlugin', (compilation) =>{
let filelist='## 文件列表';
filelist = Object.keys(compilation.assets).reduce((filelist,filename)=>filelist+'\r\n- '+filename,filelist);
compilation.assets[this.options.name?this.options.name:'filelist.md']={
source() {
return filelist;
},
size() {
return filelist.length
}
}
});
}
}
module.exports=FileListPlugin;
8. InlineWebpackPlugin [#](#t78. InlineWebpackPlugin)
有些时候我们希望把脚本和样式单独内联在HTML页面里面
html-webpack-inline-source-plugin
class InlineWebpackPlugin{ constructor(options) { this.options=options; } processTags(compilation,inlineSource,htmlPluginData) { let head=[]; let body=[]; htmlPluginData.head.forEach(tag => head.push(this.processTag(compilation,inlineSource,tag))); htmlPluginData.body.forEach(tag=>body.push(this.processTag(compilation,inlineSource,tag))); return {...htmlPluginData,head,body}; } processTag(compilation,inlineSource,tag) { let assetUrl; debugger; if (tag.tagName == 'script' && inlineSource.test(tag.attributes.src)) { assetUrl=tag.attributes.src; tag={ tagName: 'script', closeTag: true, attributes:{type:'text/javascript'} } } else if (tag.tagName == 'link' && inlineSource.test(tag.attributes.href)) { assetUrl=tag.attributes.href; tag={ tagName: 'style', closeTag: true, attributes:{type:'text/css'} } } if (assetUrl) { let asset=compilation.assets[assetUrl]; tag.innerHTML=asset.source(); } return tag; } apply(compiler) { compiler.hooks.compilation.tap('InlineWebpackPlugin',compilation => { console.log('starting a new compilation'); debugger; compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync('InlineWebpackPlugin',(htmlPluginData,callback) => { if (!this.options.inlineSource) { return callback(null,htmlPluginData); } let inlineSource=new RegExp(this.options.inlineSource); let result=this.processTags(compilation,inlineSource,htmlPluginData); callback(null,result); }) }); } } module.exports=InlineWebpackPlugin;
webpack.config.js
new HtmlWebpackPlugin({
template: './src/index.html',
filename:'index.html'
}),
new InlineWebpackPlugin({inlineSource: '.(js|css)$' })
#
9.自动上传资源文件到CDN-
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 assets=compilation.assets; debugger; let promises=Object.keys(assets).map(this.upload.bind(this)); Promise.all(promises).then((err,data)=>console.log(err,data)) }); } upload(filename) { return new Promise((resolve,reject) => { let {bucket='video',domain="img.zhufenpeixun.cn",accessKey='tfi5imW04AkxJItuFbbRy1ffH1HIoo17HbWOXw5fVe',secretKey='mru__Na4qIor4-V7U4AOJyp2KBUYEw1NWduiJ4Pbyp'}=this.options; let mac=new qiniu.auth.digest.Mac(accessKey,secretKey); let options = { scope: bucket, }; let putPolicy = new qiniu.rs.PutPolicy(options); let uploadToken=putPolicy.uploadToken(mac); let config=new qiniu.conf.Config(); let localFile=path.join(__dirname,'../../dist',filename); let formUploader=new qiniu.form_up.FormUploader(config); let putExtra = new qiniu.form_up.PutExtra(); formUploader.putFile(uploadToken,filename,localFile,putExtra,(err,body,info)=>{ err? reject(err):resolve(body); }); }); } } module.exports=UploadPlugin;
Gitalking ...
Be the first guy leaving a comment!