實現一個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

相關文章
相關標籤/搜索