手搓JSVM第 11 篇:打包、模块与 CLI:把 VM 产物变成可运行文件

手搓JSVM第 11 篇:打包、模块与 CLI:把 VM 产物变成可运行文件

源码仓库

1. 本文目标

前面几篇已经把编译器和 runtime 的核心部件搭起来了。接下来要解决的是工程化闭环:

如何把 bytecode、constantPool、函数元信息和 runtime 打包成一个可运行 JS 文件?

本篇覆盖:

  1. ProgramArtifact

  2. Runtime Source

  3. Pack

  4. IIFE / CJS / ESM 包装

  5. Module Bundling

  6. CLI

  7. Debug Info

2. 为什么需要打包

如果只生成 bytecode:

javascript 复制代码
[4, 0, 0, 19, 0]

这还只是编译数据,用户不能把它当作完整程序直接运行。

要真正执行,还需要 runtime:

javascript 复制代码
function __jsvmpRun(metadata, globalObject) { ... }

同时还要把 runtime 所需的 metadata 一并带上:

javascript 复制代码
{
  bytecode,
  constantPool,
  functions
}

形象化比喻:游戏卡带

bytecode 像游戏关卡数据,runtime 像游戏机,metadata 像说明书。三者单独存在都不构成最终交付物;打包就是把它们装进一张卡带,让用户拿到后可以直接运行。

3. 前置知识

概念 说明
Artifact 编译产物数据
Runtime 执行 bytecode 的解释器
Pack 把 runtime 与 artifact 拼成 JS
IIFE 立即执行函数表达式
CJS CommonJS 输出
ESM ES Module 输出
CLI 命令行入口

4. 源码中的对应位置

概念 源码位置 说明
compile() src/compiler/pipeline.ts 主编译流程
ProgramArtifact src/compiler/types.ts bytecode 与元数据结构
generateRuntimeSource() src/compiler/runtime-gen.ts 生成运行时源码
packArtifact() src/compiler/pack.ts 打包 runtime + metadata
bundle() src/compiler/bundler.ts 模块打包
CLI src/cli.ts 命令行参数与入口

5. 核心数据结构

ProgramArtifact

它解决什么问题

把 runtime 执行程序时需要的 bytecode、常量和函数信息集中保存。

教学版简化实现
javascript 复制代码
const artifact = {
  format: 'iife',
  bytecode: [],
  constantPool: [],
  functions: [],
  entryFunctionId: 0,
};

Runtime Source

它解决什么问题

提供真正读取并解释 bytecode 的执行函数。

教学版简化实现
javascript 复制代码
function generateRuntimeSource() {
  return `function __run(metadata){ return 0 }`;
}

Packed Output

它解决什么问题

把 runtime 与 artifact 合到一起,让用户得到一个可直接运行的 JS 文件。

教学版简化实现
javascript 复制代码
const output = `
(function(){
  const metadata = ${JSON.stringify(artifact)};
  ${runtimeSource}
  return __run(metadata);
})()
`;

6. Mermaid 图解

flowchart LR A["Source File"] --> B["compile()"] B --> C["ProgramArtifact"] C --> D["Runtime Source"] D --> E["packArtifact()"] C --> E E --> F["Output JS"]
flowchart TD A["CLI input"] --> B["resolve format"] B --> C{"bundle?"} C -->|yes| D["bundle modules"] C -->|no| E["compile single file"] D --> F["parse/lower/emit"] E --> F F --> G["pack"] G --> H["write output"]

7. 伪代码

plaintext 复制代码
function compile(inputFile, outputFile, options):
    source = read inputFile
    format = resolveFormat(source)

    if bundle enabled:
        source = bundle(source)

    ast = parse(source)
    ir = lower(ast)
    bytecode = emit(ir)
    runtime = generateRuntimeSource()
    code = pack(runtime, bytecode)

    write outputFile
    return code

8. 教学版实现代码

javascript 复制代码
function packArtifact(artifact) {
  const metadata = JSON.stringify(artifact);

  return `
(function () {
  function __miniRun(metadata) {
    const code = metadata.bytecode;
    const pool = metadata.constantPool;
    const regs = [];
    let pc = 0;

    while (pc < code.length) {
      const op = code[pc++];
      if (op === 4) regs[code[pc++]] = pool[code[pc++]];
      else if (op === 19) return regs[code[pc++]];
      else throw new Error('unknown opcode: ' + op);
    }
  }

  return __miniRun(${metadata});
})()
`.trim();
}

9. 示例输入与输出

Artifact

javascript 复制代码
const artifact = {
  bytecode: [4, 0, 0, 19, 0],
  constantPool: [42],
};

Packed Output

javascript 复制代码
const code = packArtifact(artifact);
console.log(eval(code)); // 42

10. 执行过程拆解

Step 阶段 产物
1 parse/lower/emit ProgramArtifact
2 generate runtime __jsvmpRun 源码
3 pack 自包含 JS 文件
4 execute output VM runtime 执行 bytecode
5 return 输出结果

11. 与原始源码的差异

主题 教学版 正式源码
Runtime 极简同步执行 支持 sync/async/generator
Format 只演示 IIFE 支持 IIFE/ESM/CJS
Module 不支持 支持 ESM/CJS bundling
Global 简化 注入 __vmp_global、require/module/exports
Debug 不支持 可包含 debugInfo

12. 常见问题

Q1:为什么 output 仍然是 JS?

因为这个项目做的是 JS virtualization:源码先变成 bytecode,再由自定义 JS runtime 解释,所以最终产物仍然需要交给 JS 引擎运行。

Q2:打包是不是混淆?

不是。打包只是把 runtime 和 metadata 放到一起。混淆属于额外阶段。

13. 本文小结

到这里,从"编译数据"到"可运行产物"的工程闭环就完整了。

下一篇将继续讲:

plaintext 复制代码
第 12 篇:完整最小 JSVM 实现与源码设计复盘
相关推荐
泯泷35 分钟前
手搓JSVM第 10 篇:对象、数组与属性访问
前端·javascript·前端框架
泯泷1 小时前
手搓JSVM第 9 篇:闭包:函数如何记住外部变量
前端·javascript·前端框架
晴天161 小时前
前端 SSR、BFF 原理介绍
前端·javascript·网络·html
全栈技术负责人2 小时前
AI 效能工具建设:Agent 可观测性技能技术方案与实践
前端·ai·ai编程
梦想平凡2 小时前
百游棋牌源代码开发搭建教程(一):全端工程结构、开发环境配置与首次启动验证
前端·javascript·源代码管理
妙码生花2 小时前
两个月 59 篇 AI 开发日志 + Golang 商业级实战项目收工后,得来的 AI 使用心法-上
前端·后端·gin
vx-Biye_Design2 小时前
springboot中国传统节日宣传平台49078-计算机课程设计、毕业设计
java·前端·vue.js·spring boot·后端·课程设计·idea
妙码生花2 小时前
两个月 59 篇 AI 开发日志 + Golang 商业级实战项目收工后,得来的 AI 使用心法-下
前端·后端·go
Highcharts2 小时前
常见报错排雷指南3:兼容性问题的官方解法
javascript·数据可视化