1、loader和plugin的区别
loader的应用场景是“转换(transform)”,简单的来说就是将内容从一种格式转换为另一种,其中包含es6->es5、将资源从bundle中解耦出来、scss|less等高级语言的css化,文件的加载等。
plugin的应用场景是针对webpack各个事件的钩子处理函数,简单的来说就是在各个时间点做一些“扩展”,比如:报告打印、import组件的次数记录、模块加载时间分析、压缩代码、模块大小分析等。
由此可见loader侧重于文件的转换和转化,而plugin侧重全局的把控和扩展。
2、loader例子:
// loader-utils是专门用于自定义loader时的一些工具函数
module.exports = function(source) {
const upperCaseContent = source.toUpperCase();
const content = `module.exports = ${JSON.stringify(upperCaseContent)}`;
// console.log("########content:",content);
return content;
};
这是一个典型的loader转化场景,将小写英文转化为大写。结果如下:
module.exports = "NO ONE’S BORN BEING GOOD AT ALL THINGS. YOU BECOME GOOD AT THINGS THROUGH HARD WORK. YOU’RE NOT A VARSITY ATHLETE THE FIRST TIME YOU PLAY A NEW SPORT. YOU DON’T HIT EVERY NOTE THE FIRST TIME YOU SING A SONG.YOU’VE GOT TO PRACTICE. THE SAME PRINCIPLE APPLIES TO YOUR SCHOOLWORK. YOU MIGHT HAVE TO DO A MATH PROBLEM A FEW TIMES BEFORE YOU GET IT RIGHT. YOU MIGHT HAVE TO READ SOMETHING A FEW TIMES BEFORE YOU UNDERSTAND IT.YOU DEFINITELY HAVE TO DO A FEW DRAFTS OF A PAPER BEFORE IT’S GOOD ENOUGH TO HAND IN."
加入module.exports是为了在将文本嵌入bundle.js后能以模块的形式读取它,当然一般都是配合file-loader将文件提取出来:
module: {
rules: [
{
test: /\.txt$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[hash].[ext]', // 配置文件的输出名称
},
},
{
// 本地引用loader
loader: path.resolve('./src/my-loader'),
options: {
// 通过配置传入words来替换NAME为wei
words: 'wei'
}
}]
}
],
},
在 Webpack 中,loader
的顺序非常重要,因为它们的执行顺序是从 右到左 或者 下到上 的,这意味着最后一个定义的 loader 会最先执行。
3、plugin例子:
class ComponentReferenceCounterPlugin {
constructor(options) {
this.options = options;
this.total = { len: 0, components: {} };
}
apply(compiler) {
const self = this;
const parser = (factory) => {
factory.hooks.parser.for('javascript/auto').tap('count-webpack-plugin', (parser) => {
parser.hooks.importSpecifier.tap('count-webpack-plugin', (statement, source, exportName, identifierName) => {
console.log("@@@@@@@@@@@@@@@@@@@@@",source,identifierName,exportName);
if (source.includes(self.options.pathname)) {
self.total.len += 1;
const key = identifierName;
self.total.components[key] = self.total.components[key]? self.total.components[key] + 1 : 1;
}
});
});
};
compiler.hooks.normalModuleFactory.tap('count-webpack-plugin', parser);
compiler.hooks.done.tap('count-webpack-plugin-done', () => {
console.log('Component reference count:', self.total);
});
}
}
module.exports = ComponentReferenceCounterPlugin;
基本上这个plugin做了:
【1】factory.hooks.parser.for('javascript/auto').tap(...)
: 这里使用了 Webpack 的 parser
hook 来监听 JavaScript 文件的解析过程。javascript/auto
表示监听所有 JavaScript 类型的模块。
【2】parser.hooks.importSpecifier.tap(...)
: 这个 hook 在解析 import
语句时触发,可以获取到导入的模块路径(source
)、导出的名称(exportName
)以及标识符名称(identifierName
)。
【3】compiler.hooks.normalModuleFactory.tap(...)
: 这个 hook 用于注册 parser
函数到 Webpack 的 normalModuleFactory
中。normalModuleFactory
负责生成模块实例,因此在这个阶段挂载解析器来统计模块引用是合适的。
得到结果是:Component reference count: { len: 1, components: { child: 1 } }