2018-09-29

plugin


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

2. 创建插件 [#](#t12. 创建插件)

webpack 插件由以下组成:

  • 一个 JavaScript 命名函数。
  • 在插件函数的 prototype 上定义一个 apply 方法。
  • 指定一个绑定到 webpack 自身的事件钩子。
  • 处理 webpack 内部实例的特定数据。
  • 功能完成后调用 webpack 提供的回调。

3. Compiler 和 Compilation [#](#t23. Compiler 和 Compilation)

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

  • compiler 对象代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用。可以使用它来访问 webpack 的主环境。

  • compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。compilation 对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。

  • Compiler源码

  • 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-plugin

  • 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 #

  • qiniu

    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;
    
  • Node.js SDK

  • writing-a-plugin

  • api/plugins

  • 插件 API

Gitalking ...

Markdown is supported

Be the first guy leaving a comment!