视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
如何实现一个webpack模块解析器
2020-11-27 22:05:48 责编:小采
文档


最近在学习 webpack源码,由于源码比较复杂,就先梳理了一下整体流程,就参考官网的例子,手写一个最基本的 webpack 模块解析器。

代码很少,github地址:手写webpack模块解析器

整体流程分析

1、读取入口文件。

2、将内容转换成 ast 语法树。

3、深度遍历语法树,找到所有的依赖,并加入到一个数组中。

4、将 ast 代码转换回可执行的 js 代码。

5、编写 require 函数,根据入口文件,自动执行完所有的依赖。

6、输出运行结果。

createAsset

// 读取内容并提取它的依赖关系
function createAsset(filename) {
 // 以字符串的形式读取文件
 const content = fs.readFileSync(filename, "utf-8");

 // 转换字符串为ast抽象语法树
 const ast = babylon.parse(content, {
 sourceType: "module"
 });

 const dependencies = [];

 // 遍历抽象语法树
 traverse(ast, {
 // 每当遍历到import语法的时候
 ImportDeclaration: ({ node }) => {
 // 把依赖的模块加入到数组中
 dependencies.push(node.source.value);
 }
 });

 const id = ID++;

 // 转换为浏览器可运行的代码
 const { code } = babel.transformFromAstSync(ast, null, {
 presets: ["@babel/preset-env"]
 });

 return {
 id,
 filename,
 dependencies,
 code
 };
}

createGraph

// 从入口开始,分析所有依赖项,形成依赖图,采用深度优先遍历
function createGraph(entry) {
 const mainAsset = createAsset(entry);

 // 定义一个保存依赖项的数组
 const queue = [mainAsset];

 for (const asset of queue) {
 const dirname = path.dirname(asset.filename);

 // 定义一个保存子依赖项的属性
 asset.mapping = {};

 asset.dependencies.forEach(relativePath => {
 const absolutePath = path.join(dirname, relativePath);

 const child = createAsset(absolutePath);

 // 给子依赖项赋值
 asset.mapping[relativePath] = child.id;

 // 将子依赖也加入队列中,循环处理
 queue.push(child);
 });
 }
 return queue;
}

bundle

// 根据生成的依赖关系图,生成浏览器可执行文件
function bundle(graph) {
 let modules = "";

 // 把每个模块中的代码放在一个function作用域内
 graph.forEach(mod => {
 modules += `${mod.id}:[
 function (require, module, exports){
 ${mod.code}
 },
 ${JSON.stringify(mod.mapping)},
 ],`;
 });

 // require, module, exports 不能直接在浏览器中使用,这里模拟了模块加载,执行,导出操作。
 const result = `
 (function(modules){
 // 创建一个require()函数: 它接受一个 模块ID 并在我们之前构建的模块对象查找它.
 function require(id){
 const [fn, mapping] = modules[id];

 function localRequire(relativePath){
 // 根据mapping的路径,找到对应的模块id
 return require(mapping[relativePath]);
 }

 const module = {exports:{}};

 // 执行转换后的代码,并
输出内容。 fn(localRequire,module,module.exports); return module.exports; } // 执行入口文件 require(0); })({${modules}}) `; return result; }

执行解析

const graph = createGraph("./entry.js");
const result = bundle(graph);

下载本文
显示全文
专题