衔接上文,既然添加了loader,那么就干脆朝着webpack方向完善其他主要功能,此文主要是提供Plugin
实现部分
参考webpack官网,发现其对插件的解释为:
plugins 选项用于以各种方式自定义 webpack 构建过程
可以理解为插件可以参与整个 webpack 构建过程,那么由此可以推断如下几点:
- 可自定义
- webpack构建过程中会有多个生命周期埋点
- plugin在对应生命周期处执行
再深入了解,我们发现自定义插件有如下规则:
- 插件为实例对象
- 插件对象需要包含apply方法,即定义该实例的类需要绑定apply方法
- apply方法传入webpack构建上下文
- 可以在对应构建上下文提供的周期函数钩子内执行我们需要的操作
思考
- 启动时生成构建上下文
- 构建过程埋点
- 注册插件,执行插件实例的apply方法,在构建上下文提供的生命周期中执行内容
实现
构建上下文,应包含配置的入口,出口,loaders等,此处只写插件相关
const ctx = {
beforeRun: {
pluginMap: {},
tap(pluginName, callback) {
this.pluginMap[pluginName] = callback
},
},
run: {
pluginMap: {},
tap(pluginName, callback) {
this.pluginMap[pluginName] = callback
},
},
beforeCompile: {
pluginMap: {},
tap(pluginName, callback) {
this.pluginMap[pluginName] = callback
},
},
compile: {
pluginMap: {},
tap(pluginName, callback) {
this.pluginMap[pluginName] = callback
},
},
make: {
pluginMap: {},
tap(pluginName, callback) {
this.pluginMap[pluginName] = callback
},
},
seal: {
pluginMap: {},
tap(pluginName, callback) {
this.pluginMap[pluginName] = callback
},
},
}
埋点函数
// 生命周期事件埋点
const execHook = (hook) => {
//批量执行该此生命周期注册的函数列表
Object.values(ctx[hook].pluginMap).forEach(pluginFunc => {
pluginFunc()
})
}
browserify.js
const fs = require("fs");
const { resolve } = require("path");
const { useLoader } = require("./loader.js");
const { registerPlugin, execHook } = require("./plugin")
//注册插件
registerPlugin()
execHook('beforeRun')
//开始初始化参数
const moduleFuncCache = [];
let curIndex = 0;
//记录path和数组对应资源数组位置
const pathIndexMap = {};
const codeSplicing = (path) => {
// 获取绝对路径
const wholePath = resolve(path);
if (pathIndexMap[wholePath] !== undefined) return;
moduleFuncCache.push(`
function(){
${useLoader(wholePath, codeSplicing, pathIndexMap)}
}
`);
pathIndexMap[wholePath] = curIndex++;
};
const getCode = () => {
// eval方式转函数
return `
// 自执行函数,避免全局污染
(function(){
const moduleCache = []
const _require = function(index){
// 第一次引用该模块则执行,后续从缓存中取
if(!moduleCache[index]) moduleCache[index] = formatModuleFuncCache[index]()
return moduleCache[index]
}
//数组收集,省去了json序列化的过程,从而省去转码的过程
const formatModuleFuncCache = [${moduleFuncCache}]
//执行入口文件代码
formatModuleFuncCache[${moduleFuncCache.length - 1}]()
})()
`;
};
//主函数,传入文件路径,返回最终打包完成的代码块
const browserify = (path) => {
execHook('run')
// 为每个require的模块拼接代码,为其提供module实例,并返回module.exports
codeSplicing(path);
// 阻止代码,使其能解析代码cache对象,并依照引入顺序来执行代码块
execHook('beforeCompile')
const code = getCode();
execHook('compile')
return code
};
// 执行命令行传入打包源文件 node ./browserify.js index.js,此时path即index.js
const [path] = process.argv.splice(2);
// 写目标文件;
execHook('make')
try {
fs.mkdirSync("./dist");
} catch (error) { }
fs.writeFileSync("./dist/chunk.js", browserify(path));
execHook('seal')
插件列表
class ConsoleLogOnBuildPlugin {
apply(compiler) {
const start = Date.now()
compiler.run.tap('ConsoleLogOnBuildPlugin', (compilation) => {
console.log(`${moment(start).format("YYYY-MM-DD HH:mm:ss")}: mini-webpack 构建正在启动!`);
});
compiler.seal.tap('ConsoleLogOnBuildPlugin', (compilation) => {
console.log(`${moment().format("YYYY-MM-DD HH:mm:ss")}: mini-webpack 构建完成!`);
console.log(`构建时长: ${Date.now() - start}`);
});
}
}
const pluginList = [
new ConsoleLogOnBuildPlugin()
]
测试效果:
2022-02-14 18:09:07: mini-webpack 构建正在启动!
2022-02-14 18:09:10: mini-webpack 构建完成!
构建时长: 3443
总结
插件通过构建工具的埋点,以及构建上下文,可以执行各种自定义功能!
项目地址:https://github.com/a793816354/myBrowserify/tree/mini-webpack