實現一個簡易版Webpack

原理

  • 一、解析一個文件及其依賴
  • 二、構建一個依賴關係圖
  • 三、將全部東西打包成一個單文件

代碼實現

文件結構

一、解析文件及其依賴

經過babylon將文件解析成AST
在線解析器
imagenode

代碼實現:
bundle.jsgit

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)

輸出結果:
imagegithub

二、構建一個依賴關係圖

// 構建一個依賴關係圖
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);

輸出結果:
imagenpm

三、將全部東西打包成一個單文件

在解析文件時,使用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);

輸出結果:babel

(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';
        },
        {}
    ],
})

把代碼複製到瀏覽器運行,執行成功!
imageapp

一個簡易版的Webapck完成了。ui

相關連接

例子源碼
視頻教程
babylon
babel-traverse docs.net

相關文章
相關標籤/搜索