实现一个webpack模块解析器

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

代码不多,github地址:手写webpack模块解析器webpack

总体流程分析

一、读取入口文件。git

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

三、深度遍历语法树,找到全部的依赖,并加入到一个数组中。web

四、将 ast 代码转换回可执行的 js 代码。数组

五、编写 require 函数,根据入口文件,自动执行完全部的依赖。浏览器

六、输出运行结果。babel

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);

欢迎你们提issue,一块儿交流。app

相关文章
相关标签/搜索