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

posted @ 2020-04-06 22:28  linjiajun  阅读(...)  评论(...编辑  收藏