Git Product home page Git Product logo

minipack-demo's People

Contributors

jesuslove avatar

Watchers

 avatar  avatar

minipack-demo's Issues

代码解析打包原理(源码阅读)

前言

仓库地址:minipack

一个 JS 编写的现代模块打包器的简化示例。

打包器如何将代码打包成 bundler 呢?下面一起来看看打包逻辑。

创建资源 Asset

每一个 module 都会生成一个 Asset 对象,对象中保存资源的 id,filename, 依赖关系,以及 babel 转码后的 code 。

实现如下:

/ 创建 Asset
// 接收文件名
function createAsset(filename) {
  // 1. 读取文件内容
  const content = fs.readFileSync(filename, 'utf-8');
  // 2. 生成 AST
  const ast = babylon.parse(content, {
    sourceType: 'module',
  });
  // 3. 获取依赖,读取 AST 中 ImportDeclaration
  const dependencies = [];
  traverse(ast, {
    ImportDeclaration: ({node}) => {
      dependencies.push(node.source.value);
    },
  });
  // 4. 唯一标识
  const id = ID++;
  // 5. 转义代码,适配不同版本浏览器
  const {code} = transformFromAst(ast, null, {
    presets: ['env'],
  });
  return {
    id,
    filename,
    dependencies,
    code,
  };
}
  1. 通过 fs 读取绝对路径 filename 的内容。
  2. 使用 babylon 工具将文件内容转为 AST。 AST 的结构可以查看 AST Explorer
// import aa from 'xxx.js' 的 AST JSON 结构。

{
  "type": "Program",
  "start": 0,
  "end": 23,
  "body": [
    {
      "type": "ImportDeclaration",
      "start": 0,
      "end": 23,
      "specifiers": [
        {
          "type": "ImportDefaultSpecifier",
          "start": 7,
          "end": 9,
          "local": {
            "type": "Identifier",
            "start": 7,
            "end": 9,
            "name": "aa"
          }
        }
      ],
      "source": {
        "type": "Literal",
        "start": 15,
        "end": 23,
        "value": "xxx.js",  <------
        "raw": "'xxx.js'"
      }
    }
  ],
  "sourceType": "module"
}
  1. 获取依赖,通过读取 AST 中的 ImportDeclaration 得到值。这里读取到的是文件中所有的依赖并存放在 dependencies 数组中。
  2. Asset 的唯一标识,用来识别身份。
  3. 使用 Babel 把 AST 转义成所有浏览器都可以支持的格式。
  4. 返回 Asset 对象,包含 id, filename, dependencies, code。

生成依赖图谱

从主 mainAsset 开始,遍历所有 module 的依赖创建 childAsset 并将其推入队列中。

// 创建依赖图谱
function createGraph(entry) {
  // 1. 入口元素生成 asset
  const mainAsset = createAsset(entry);
  // 2. 队列
  const queue = [mainAsset];
  // 3. 遍历队列
  for (const asset of queue) {
    // 3.1 保存子依赖和ID之间的映射,例如:{xxx.js : 1}
    asset.mapping = {};
    // 3.2 所在目录
    const dirname = path.dirname(asset.filename);
    // 3.3 处理依赖
    asset.dependencies.forEach(relativePath => {
      // 3.3.1 依赖绝对路径
      const absolutePath = path.join(dirname, relativePath);
      // 3.3.2 生成 childAsset
      const child = createAsset(absolutePath);
      
      asset.mapping[relativePath] = child.id;
      // 3.3.3 推入 queue 中。
      queue.push(child);
    });
  }
  // 4. 返回 queue 队列,保存的是所有的 Asset 对象。
  return queue;
}
  1. 入口文件生成主 mainAsset 对象。
  2. 创建队列并把 mainAsset 推入到 queue 中。
  3. for...of 队列内容,根据 dependencies 数组创建依赖的 Asset 边推入到 queue 中。

处理依赖图谱,返回自调用函数

返回一个自调用函数,浏览器可以直接执行。 自调用函数如下结构:

(function() {})()

下面看看具体的实现代码:

// 打包函数,返回一个自调用函数
// (function(){})()
function bundle(graph) {
  // 1. 所有的依赖
  let modules = '';
  // 2. 创建映射关系
  /*
    id: [ 
         function(require, module, exports) { code },
        {xxxx.js: id}
        ],
  */
  graph.forEach(mod => {
    modules += `${mod.id}: [
      function(require, module, exports) {
        ${mod.code}
      },
      ${JSON.stringify(mod.mapping)}
    ]`;
  });
  // 创建自调用函数
  // 1. 接收 modules 对象作为参数。上面的 modules 字符串包转为 modules 对象。
  // 2. 创建 require 方法接收资源 ID 作为参数
  // 3. 根据 id 读取 modules 中对应的 fn  和 mapping
  const result = `
  (function(modules) {
    function require(id) {
      const [fn, mapping] = modules[id];
      function localRequire(name) {
        return require(mapping[name]);
      }
      const module = {exports: {}};
      fn(localRequire, module, module.exports)
      return module.exports
    }
    require(0)
  })({${modules}})
  `;
  return result;
}

完整代码

const fs = require('fs');
const path = require('path');
const babylon = require('babylon');
const traverse = require('babel-traverse').default;
const {transformFromAst} = require('babel-core');

let ID = 0;

// 创建 Asset
// 接收文件名
function createAsset(filename) {
  // 1. 读取文件内容
  const content = fs.readFileSync(filename, 'utf-8');
  // 2. 生成 AST
  const ast = babylon.parse(content, {
    sourceType: 'module',
  });
  // 3. 获取依赖,读取 AST 中 ImportDeclaration
  const dependencies = [];
  traverse(ast, {
    ImportDeclaration: ({node}) => {
      dependencies.push(node.source.value);
    },
  });
  const id = ID++;
  // 4. 转义代码,适配不同版本浏览器
  const {code} = transformFromAst(ast, null, {
    presets: ['env'],
  });
  return {
    id,
    filename,
    dependencies,
    code,
  };
}
// 创建依赖图谱
function createGraph(entry) {
  // 1. 入口元素生成 asset
  const mainAsset = createAsset(entry);
  // 2. 队列
  const queue = [mainAsset];
  // 3. 遍历队列
  for (const asset of queue) {
    // 3.1 保存子依赖和ID之间的映射
    asset.mapping = {};
    // 3.2 所在目录
    const dirname = path.dirname(asset.filename);
    // 3.3 处理依赖
    asset.dependencies.forEach(relativePath => {
      // 3.3.1 依赖绝对路径
      const absolutePath = path.join(dirname, relativePath);
      // 3.3.2 生成 childAsset
      const child = createAsset(absolutePath);
      asset.mapping[relativePath] = child.id;
      queue.push(child);
    });
  }
  return queue;
}
// 打包函数,返回一个自调用函数
// (function(){})()
function bundle(graph) {
  // 自执行函数的参数
  // key : value 映射 key 为模块 id,value 是一个 function 和依赖 mapping。
  let modules = '';
  graph.forEach(mod => {
    modules += `${mod.id}: [
      function(require, module, exports) {
        ${mod.code}
      },
      ${JSON.stringify(mod.mapping)}
    ]`;
  });
  // 创建一个 自调用方法,接收 modules 对象
  // 1. 内部创建 require 方法接收 id 作为参数
  // 2. 结构 modules 对应 id 的 fn 和 mapping
  // 3. localRequire 以对应路径文件的 id为参数获取module 内容。
  const result = `
  (function(modules) {
    function require(id) {
      const [fn, mapping] = modules[id];
      function localRequire(name) {
        return require(mapping[name]);
      }
      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);

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.