首页 > 编程 > JavaScript > 正文

如何实现一个webpack模块解析器

2019-11-19 12:38:16
字体:
来源:转载
供稿:网友

最近在学习 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);

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持武林网。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表