­

實現一個簡易版Webpack

原理

  • 1、解析一個文件及其依賴
  • 2、構建一個依賴關係圖
  • 3、將所有東西打包成一個單文件

程式碼實現

文件結構

1、解析文件及其依賴

通過babylon將文件解析成AST
在線解析器
image

程式碼實現:
bundle.js

const fs = require("fs");  const babylon = require("babylon");  const traverse = require("babel-traverse").default;    let ID = 0;    function createAsset(filename) {    const content = fs.readFileSync(filename, "utf-8");    // 解析文件成AST    const ast = babylon.parse(content, {      sourceType: "module",    });      const dependencies = [];    // 根據AST獲取相關依賴    traverse(ast, {      ImportDeclaration: ({ node }) => {        dependencies.push(node.source.value);      },    });      const id = ID++;      return {      id,      filename,      dependencies,    };  }    const mainAssets = createAsset("./example/entry.js");    console.log(mainAssets)    

輸出結果:
image

2、構建一個依賴關係圖

// 構建一個依賴關係圖  function createGraph(entry) {    const mainAssets = createAsset(entry);      const queue = [mainAssets];      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;  }    const graph = createGraph("./example/entry.js");  console.log(graph);  

輸出結果:
image

3、將所有東西打包成一個單文件

在解析文件時,使用babel對程式碼進行轉譯

// 解析一個文件及其依賴  function createAsset(filename) {    const content = fs.readFileSync(filename, "utf-8");    const ast = babylon.parse(content, {      sourceType: "module",    });      const dependencies = [];    traverse(ast, {      ImportDeclaration: ({ node }) => {        dependencies.push(node.source.value);      },    });      const id = ID++;    // 使用babel對程式碼進行轉譯    const { code } = babel.transformFromAst(ast, null, {      presets: ["env"],    });      return {      id,      filename,      dependencies,      code,    };  }  
// 將所有東西打包成一個單文件  function bundle(graph) {    let modules = "";      graph.forEach((mod) => {      modules += `${mod.id}:[        function(require,module,exports){          ${mod.code}        },        ${JSON.stringify(mod.mapping)}      ],`;    });    const result = `     (function(modules){       function require(id){         const [fn, mapping] = modules[id];            // 因為程式碼引入文件時根據相對路徑,所以需要把相對路徑跟id進行一個映射         function localRequire(relativePath){           return require(mapping[relativePath])         }           const module = {exports:{}};           fn(localRequire,module,module.exports)           return module.exports;       }       // 執行入口模組       require(0);     })({${modules}})     `;      return result;  }    const graph = createGraph("./example/entry.js");  const result = bundle(graph);  console.log(result);    

輸出結果:

(function(modules) {      function require(id) {          const [fn, mapping] = modules[id];            function localRequire(relativePath) {              return require(mapping[relativePath])          }            const module = {              exports: {}          };            fn(localRequire, module, module.exports)            return module.exports;      }      require(0);  })({      0: [          function(require, module, exports) {              "use strict";                var _message = require("./message.js");                var _message2 = _interopRequireDefault(_message);                function _interopRequireDefault(obj) {                  return obj && obj.__esModule ? obj : {                      default: obj                  };              }                console.log(_message2.default);          },          {              "./message.js": 1          }      ],      1: [          function(require, module, exports) {              "use strict";                Object.defineProperty(exports, "__esModule", {                  value: true              });                var _name = require("./name.js");                exports.default = "hello " + _name.name + "!";          },          {              "./name.js": 2          }      ],      2: [          function(require, module, exports) {              "use strict";                Object.defineProperty(exports, "__esModule", {                  value: true              });              var name = exports.name = 'Aaron';          },          {}      ],  })  

把程式碼複製到瀏覽器運行,執行成功!
image

一個簡易版的Webapck完成了。

相關鏈接

例子源碼
影片教程
babylon
babel-traverse docs