如何实现一个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);
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。