网易乐得技术团队

震惊!webpack插件如何开发

webpack是什么:

1

Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader 的转换,任何形式的资源都可以视作模块,比如 CommonJs 模块、 AMD 模块、 ES6 模块、CSS、图片、 JSON、Coffeescript、 LESS 等。

webpack概念

Entry Points 就是入口文件
Output 输出文件的位置
loader 是对应用程序中资源文件进行转换。它们是(运行在 Node.js 中的)函数,可以将资源文件作为参数的来源,然后返回新的资源文件。
plugin是 wepback 的支柱功能。能直接参与到webpack编译的过程,并通过使用各种webpack内部特性来达到神奇的效果。插件可以于解决 loader 无法实现的其他事。

webpack 编译流程

2

如何编写一个插件?(How to write a plugin?)

由于官方文档也写的非常简单,直接要求看compilationcompiler源码,在我开发插件的过程中遇到了很多困难,主要参考了html-webpack-pluginfile-loader官方文档才明白了一些。
我理解插件开发有俩个重要的点,一个是assets是webpack编译中负责生成的文件。另外一个是chunks, entrys入口文件的各类信息。
同时插件可以使用 plugin 方法注入自定义的构建步骤。
emit(c: Compilation) 异步编译器开始输出生成资源。这里是插件向 Compilation.assets
数组添加生成资源的最后机会。

1
2
3
compiler.plugin('emit', function(compilation, callback) {
//在这里可以做生成文件操作处理
});

done(stats: Stats)所有任务已经完成。
failed(err: Error)编译器处在跟踪模式并且一个编译已经失败。

参考官网,这段代码意思就是,生成一个filelist.md文件,里边的内容就是filelist。可以在emit阶段,生成图片,js,都是没问题的。

1
2
3
4
5
6
7
8
compilation.assets['filelist.md'] = {
source: function() {
return filelist;
},
size: function() {
return filelist.length;
}
};

基本插件架构

插件都是被实例化的带有 apply 原型方法的对象。这个 apply 方法在安装插件时将被 webpack 编译器调用一次。apply 方法提供了一个对应的编译器对象的引用,从而可以访问到相关的编译器回调。一个简单的插件结构如下:

1
2
3
4
5
6
7
8
9
10
11
function HelloWorldPlugin(options) {
// 使用配置(options)设置插件实例
}

HelloWorldPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', function() {
console.log('well done');
});
};

module.exports = HelloWorldPlugin;

compilation.getStats()函数能获取到生产文件的一些hash等关键信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  "chunks": [
{
"id": 0,
"rendered": true,
"initial": true,
"entry": true,
"extraAsync": false,
"size": 0,
"names": [
"desktop/40x/index"
],
"files": [
"desktop/40x/index.9ecaa2b40f.js"
],
"hash": "9ecaa2b40f26e670b18b",
"parents": [],
"origins": [
{
"moduleId": 2,
"module": "E:\\miaow\\ftl-webpack-plugin\\src\\desktop\\40x\\index.js"
,
"moduleIdentifier": "E:\\miaow\\ftl-webpack-plugin\\src\\desktop\\40x\
\index.js",
"moduleName": "./src/desktop/40x/index.js",
"loc": "",
"name": "desktop/40x/index",
"reasons": []
}
]
}

现在我们要开发一个生成ftl的webpack插件,并且注入一些全局变量功能,js的插入。

定义了一个function 在这里我们处理传入的变量和默认值的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function ftlPlugin(options) {
this.options = Object.assign({}, options, {
define: options.define || {},
entries: options.entries || [],
commons: options.commons || [],
favicon: options.favicon || 'favicon.ico',
publicPath: options.publicPath || '',
context: options.context || path.resolve(__dirname, 'src'),
commonPath: options.commonPath || 'common'
});

this.checkRequiredOptions(options);
//webpack存储变量
this.webpackOptions = {};
this.filesRegex = {};
this.files = [];
//公共js
this.commonScripts = '';
this.scripts = {};
}

调用了一个子编译器来分析ftl的依赖,img图片,css丢给相应的loader进行处理,我们只关心入口文件的生成。缓存等都是交给webpack自己处理。

1
compilationPromise = childCompiler.compileTemplate(self.options.templateLoaderName, compiler.context, fileName, compilation);

compilation.chunks 就是入口文件的js,会有生成文件等信息。此段代码就是要讲获取编译后的js路径,而后插入到ftl当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ftlPlugin.prototype.getCommonJS = function (compilation)  {
let commonJS = '',
//获取webpack 配置信息
publicPath = this.webpackOptions.output.publicPath || this.options.publicPath;

compilation.chunks.map((chunk) => {
for (let i = 0; i < commons.length; i++) {
if(commons[i] === chunk.entryModule.rawRequest){
commonJS += `<script src="${publicPath}${chunk.files}"></script>`;
}
}
});

}

最后按照官方文档讲入口ftl文件生成插件就完成开发了。欢迎大家提出讨论意见,在2017年7月1日前回复的同志讲roll一位,楼主有小礼品赠送,