1. plugin #

插件向第三方开发者提供了 webpack 引擎中完整的能力。使用阶段式的构建回调,开发者可以引入它们自己的行为到 webpack 构建流程中。创建插件比创建 loader 更加高级,因为你将需要理解一些 webpack 底层的内部特性来做相应的钩子

2. 创建插件 #

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;

3. Compiler 和 Compilation #

在插件开发中最重要的两个资源就是compilercompilation对象。理解它们的角色是扩展webpack引擎重要的第一步。

4. 基本插件架构 #

插件是由「具有 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'})
    ]
}

5. 访问 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;

关于 compiler, compilation 的可用回调,和其它重要的对象的更多信息,请查看 插件 文档。

6. 异步编译插件 #

有一些编译插件中的步骤是异步的,这样就需要额外传入一个 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;

7. filelist.md #

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;

8. InlineWebpackPlugin #


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;

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 {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;