如何实现一个webpack模块解析器
最近在学习webpack源码,由于源码比较复杂,就先梳理了一下整体流程,就参考官网的例子,手写一个最基本的webpack模块解析器。
代码很少,github地址:手写webpack模块解析器
整体流程分析
1、读取入口文件。
2、将内容转换成ast语法树。
3、深度遍历语法树,找到所有的依赖,并加入到一个数组中。
4、将ast代码转换回可执行的js代码。
5、编写require函数,根据入口文件,自动执行完所有的依赖。
6、输出运行结果。
createAsset
//读取内容并提取它的依赖关系
functioncreateAsset(filename){
//以字符串的形式读取文件
constcontent=fs.readFileSync(filename,"utf-8");
//转换字符串为ast抽象语法树
constast=babylon.parse(content,{
sourceType:"module"
});
constdependencies=[];
//遍历抽象语法树
traverse(ast,{
//每当遍历到import语法的时候
ImportDeclaration:({node})=>{
//把依赖的模块加入到数组中
dependencies.push(node.source.value);
}
});
constid=ID++;
//转换为浏览器可运行的代码
const{code}=babel.transformFromAstSync(ast,null,{
presets:["@babel/preset-env"]
});
return{
id,
filename,
dependencies,
code
};
}
createGraph
//从入口开始,分析所有依赖项,形成依赖图,采用深度优先遍历
functioncreateGraph(entry){
constmainAsset=createAsset(entry);
//定义一个保存依赖项的数组
constqueue=[mainAsset];
for(constassetofqueue){
constdirname=path.dirname(asset.filename);
//定义一个保存子依赖项的属性
asset.mapping={};
asset.dependencies.forEach(relativePath=>{
constabsolutePath=path.join(dirname,relativePath);
constchild=createAsset(absolutePath);
//给子依赖项赋值
asset.mapping[relativePath]=child.id;
//将子依赖也加入队列中,循环处理
queue.push(child);
});
}
returnqueue;
}
bundle
//根据生成的依赖关系图,生成浏览器可执行文件
functionbundle(graph){
letmodules="";
//把每个模块中的代码放在一个function作用域内
graph.forEach(mod=>{
modules+=`${mod.id}:[
function(require,module,exports){
${mod.code}
},
${JSON.stringify(mod.mapping)},
],`;
});
//require,module,exports不能直接在浏览器中使用,这里模拟了模块加载,执行,导出操作。
constresult=`
(function(modules){
//创建一个require()函数:它接受一个模块ID并在我们之前构建的模块对象查找它.
functionrequire(id){
const[fn,mapping]=modules[id];
functionlocalRequire(relativePath){
//根据mapping的路径,找到对应的模块id
returnrequire(mapping[relativePath]);
}
constmodule={exports:{}};
//执行转换后的代码,并输出内容。
fn(localRequire,module,module.exports);
returnmodule.exports;
}
//执行入口文件
require(0);
})({${modules}})
`;
returnresult;
}
执行解析
constgraph=createGraph("./entry.js");
constresult=bundle(graph);
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。