手搓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 实现与源码设计复盘
相关推荐
扶风ff3 小时前
新品知识更新太快?用练题簿在线刷题,安排企业培训的小测与复盘
前端·学习·小程序
对空六课3 小时前
支持注意力分析的热力图工具有哪些?
前端·数据库·数据分析
Csvn4 小时前
Vue3 响应式与编译:依赖收集如何升级为节点级靶向更新
前端
星栈5 小时前
pnpm 12 升级实测
前端·javascript
Sirens.6 小时前
Java并发锁详解:六类锁策略与 synchronized 底层原理
java·前端·算法
Ai-_Man6 小时前
您您这可以把Microsofat Copilot的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑·copilot
特创数字科技7 小时前
一个纯本地运行的图片处理工具:压缩 / 裁剪 / 九宫格 / 圆角 / 滤镜 / 拼图 / 水印,终生使用
前端
IT_陈寒8 小时前
Vue的v-if和v-for混用居然是个天坑
前端·人工智能·后端
天衍四九-8 小时前
【无标题】
前端·spring boot·mysql·nginx·docker
广州华水科技8 小时前
2026年单北斗GNSS变形监测系统推荐榜单,解锁GNSS位移监测新高度
前端