插件向第三方开发者提供了 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'
+    })
]
externalscript能否检测代码中的import自动处理这个步骤?
external和script的问题,需要怎么实现,该从哪方面开始考虑依赖 当检测到有import该library时,将其设置为不打包类似exteral,并在指定模版中加入script,那么如何检测import?这里就用Parserexternal依赖
需要了解external是如何实现的,webpack的external是通过插件ExternalsPlugin实现的,ExternalsPlugin通过tap NormalModuleFactory 在每次创建Module的时候判断是否是ExternalModuleParser获取需要指定类型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;
