实现一个简易版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