上一版做了什么
- 实现了一个
js打包器
这一版做了什么
- 增加
loader支持 - 增加
plugin支持
为什么要增加loader和plugin
在上一篇文章中提到,webpack 就是一个 打包 js代码的打包器。至于webpack能打包 图片、css、less、scss 等其他文件, 那都是loader或者plugin的功能。所以,为了打包器更强大,需要增加loader和plugin的支持
不足的点
我只是写了一个简单的实现,真正的webpack比我这个强大太多,但是基本原理是一样的。
先来一发整体工作流程图吧

webpack工作原理.png
再来一个架构图

webpack架构图.png
工作流程
通过上述两个图,可以大概描述出webpack的工作原理了。通过loader和plugin的加持,webpack可以完成各种各样的工作。
loader支持
编译器在获取源码的时候,即可对源码进行操作,此时,loader便可以排上用场了,在loader中,对源码可以做一些操作,然后讲源码返回。 这也是webpack的loader的处理方式。具体修改为,在上一版的编译器的构建模块函数中增加代码。
getSource(modulePath) {
const rules = this.config.module.rules;
let content = fs.readFileSync(modulePath, 'utf8');
for(let i = 0; i < rules.length; i++){
const rule = rules[i];
const { test, use } = rule;
let len = use.length - 1;
if (test.test(modulePath)) {
const normalLoader = () => {
// loader 获取对应的loader函数
const loader = require(use[len--]);
content = loader(content);
if (len >= 0) {
normalLoader();
}
}
normalLoader();
}
}
return content;
}
plugin支持
针对plugin的支持,采用了和
webpack一样的模式,使用tapable这个库
简单介绍一下tapable这个库。这是库提供了一些观察者处理方法,有同步hooks和异步hooks. 具体使用方法,请看 tapable
所以在编译器的run方法执行之前,我们需要注册所有的观察者. 则有了代码:
constructor(config) {
this.config = config;
// 需要保存入口文件的路径
this.entryId;
// 需要保存所有模块的依赖
this.modules = {};
// 入口路径
this.entry = config.entry;
// 工作路径
this.root = process.cwd();
this.hooks = {
entryOption: new SyncHook(),
compile: new SyncHook(),
afterCompile: new SyncHook(),
afterPlugins: new SyncHook(),
run: new SyncHook(),
emit: new SyncHook(),
done: new SyncHook()
}
const { plugins = [] } = this.config;
// 注册所有的plugin
if (Array.isArray(plugins)) {
plugins.forEach(plugin => {
plugin.apply(this);
});
}
// 调用plugin注册完的钩子
this.hooks.afterPlugins.call();
}
剩下的,我们就是需要在编译器的各个步骤来触发钩子函数了.
例如, 在编译开始之前、编译进行时、编译完成后、文件发射前、文件发射后....
这个版本里面我只做了几个简单的触发
run() {
this.hooks.run.call();
this.hooks.compile.call();
// 执行并且创建模块的依赖关系
this.buildModule(path.resolve(this.root, this.entry), true);
this.hooks.afterCompile.call();
// 发射一个打包后的文件
this.emitFile();
this.hooks.emit.call();
this.hooks.done.call();
}
完整源码
待上传到github