VS Code 内置扩展开发指南:目录结构、esbuild 构建管线与浏览器端支持
本文基于 VS Code 仓库的 extensions/CONTRIBUTING.md,系统讲解内置扩展(Built-In Extensions)的标准目录结构、TypeScript 与 esbuild 双构建管线,以及如何把一个扩展从"仅桌面端"扩展为"桌面 + 浏览器"双目标。读完本文,你可以照着仓库内真实扩展的骨架新建一个内置扩展,理解 out/dist 输出目录的分工,并独立完成 esbuild.browser.mts 与 tsconfig.browser.json 的接入。
一、extensions/ 目录:随 VS Code 一起发布的内置扩展仓库
仓库根目录下的 extensions/ 存放随 VS Code 一起发布的内置扩展(built-in extensions),包括语言包(如 json/、python/)、语言服务(如 typescript-language-features/、css-language-features/)、主题(如 theme-monokai/)等。
extensions/package.json 并不是某个扩展的清单,而是"所有扩展共享的依赖"声明(其 description 为 Dependencies shared by all extensions),其中可以看到构建工具链的三个关键依赖:
typescript:用于开发构建(type-check + 编译到out/);esbuild:用于生产构建(打包压缩到dist/);@parcel/watcher:用于 watch 模式下低 CPU 占用的文件监听(在 extensions/esbuild-common.mts 中被使用,源码注释说明其空闲 CPU 占用远低于 esbuild 自带的 watch 模式)。
二、一个典型 TypeScript 内置扩展的目录结构
按照 extensions/CONTRIBUTING.md 的规范,一个典型的 TypeScript 内置扩展包含以下文件:
| 文件 | 作用 |
|---|---|
package.json |
扩展清单(manifest),声明 main/browser 入口、激活事件、贡献点等 |
src/ |
TypeScript 源码主目录 |
tsconfig.json |
主 TypeScript 配置,必须继承自 tsconfig.base.json |
esbuild.mts |
生产构建使用的 esbuild 构建脚本 |
.vscodeignore |
打包忽略清单,可直接从现有扩展复制 |
以仓库中较简单的 extensions/debug-auto-launch/ 为例,其实际目录正是这套骨架:package.json、src/、tsconfig.json、esbuild.mts、.vscodeignore、media/(图标)。
2.1 package.json 清单的要点
对照 extensions/debug-auto-launch/package.json,内置扩展清单的几个关键字段:
{
"publisher": "vscode",
"engines": { "vscode": "^1.5.0" },
"activationEvents": ["onStartupFinished"],
"main": "./out/extension",
"scripts": {
"compile": "gulp compile-extension:debug-auto-launch",
"watch": "gulp watch-extension:debug-auto-launch"
}
}
publisher固定为vscode;main指向开发构建产物./out/extension(见下文 2.3 节,out是开发构建目录);- 每个扩展的
scripts中都提供compile与watch两个 npm 脚本,分别转调gulp compile-extension:<扩展名>与gulp watch-extension:<扩展名>任务,这是仓库统一的单扩展构建/监听入口。
2.2 .vscodeignore:打包时忽略什么
extensions/debug-auto-launch/.vscodeignore 的真实内容展示了应该从发布包中排除的文件:
src/**
tsconfig*.json
**/*.tsbuildinfo
out/**
esbuild*.mts
package-lock.json
即源码、TypeScript 配置、构建脚本与锁文件都不随扩展发布,只保留清单声明的入口产物。CONTRIBUTING 文档建议新扩展的 .vscodeignore 直接"从现有扩展复制",上述文件即为可直接参照的模板。
2.3 输出目录约定:out(开发)与 dist(生产)
文档明确规定 TypeScript 扩展有两种输出结构:
out:开发构建(development builds)输出目录;dist:生产构建(production builds)输出目录。
两者对应两条构建管线:
-
开发管线(tsc →
out/):tsconfig.json编译源码到out/,产物按模块结构展开、不压缩,适合断点调试。对照 extensions/debug-auto-launch/tsconfig.json:{ "extends": "../tsconfig.base.json", "compilerOptions": { "rootDir": "./src", "outDir": "./out", "types": ["node"], "typeRoots": ["./node_modules/@types"] }, "include": [ "src/**/*", "../../src/vscode-dts/vscode.d.ts" ] }注意两点:一是
extends指向上一级的tsconfig.base.json(相对路径../tsconfig.base.json),二是include中显式引用了src/vscode-dts/vscode.d.ts,这是扩展 API 类型的来源。 -
生产管线(esbuild →
dist/):esbuild.mts将入口打包压缩为单个 bundle 输出到dist/。以 extensions/debug-auto-launch/esbuild.mts 为例:const srcDir = path.join(import.meta.dirname, 'src'); const outDir = path.join(import.meta.dirname, 'dist'); run({ platform: 'node', entryPoints: { 'extension': path.join(srcDir, 'extension.ts'), }, srcDir, outdir: outDir, }, process.argv);入口
src/extension.ts被打包为dist/extension.js。生产打包时,package.json的入口会被指向dist/产物;main指向out/的形式服务于开发调试。
三、tsconfig 继承体系:tsconfig.base.json 定了什么
CONTRIBUTING 要求 tsconfig.json 继承 tsconfig.base.json。查看 extensions/tsconfig.base.json,所有内置扩展共享以下编译约束:
{
"compilerOptions": {
"esModuleInterop": true,
"target": "ES2024",
"lib": ["ES2024"],
"module": "commonjs",
"strict": true,
"exactOptionalPropertyTypes": false,
"useUnknownInCatchVariables": false,
"alwaysStrict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"forceConsistentCasingInFileNames": true,
"experimentalDecorators": true
}
}
关键含义:
target: ES2024/module: commonjs:与 esbuild 生产管线的target: ['es2024']、format: 'cjs'保持一致(见下节),开发产物与生产产物在语言特性层面行为一致;strict: true叠加noUnusedLocals、noUnusedParameters、noImplicitReturns、noImplicitOverride:内置扩展的源码必须通过更严格的质量门槛,未使用变量和隐式返回都会报错;- 扩展侧的
tsconfig.json只需关心rootDir、outDir、types、include等项目级配置,语言标准由基类统一约束——这是"必须继承"的实质原因。
四、esbuild 构建管线深度解析:两个公共脚本
所有扩展的 esbuild.mts 都不直接调用 esbuild API,而是委托给两层公共脚本,这也是新扩展写构建脚本时应遵循的方式。
4.1 esbuild-extension-common.mts:扩展层公共选项
extensions/esbuild-extension-common.mts 中的 run() 负责把每个扩展的少量配置与公共基线合并。resolveBaseOptions() 为所有扩展统一设置:
const options: esbuild.BuildOptions = {
platform: config.platform, // 'node' | 'browser'
bundle: true,
minify: true,
treeShaking: true,
sourcemap: true,
target: ['es2024'],
external: ['vscode'], // 扩展 API 由宿主提供,必须 external
format: config.format ?? 'cjs',
logOverride: { 'import-is-undefined': 'error' },
};
其中 external: ['vscode'] 体现了 VS Code 扩展的本质:import * as vscode from 'vscode' 在运行时由宿主注入,打包时不可解析也不应打入 bundle。
平台差异同样在此处理——当 platform === 'browser' 时:
options.mainFields = ['browser', 'module', 'main'];
options.alias = { 'path': 'path-browserify' };
options.define = {
'process.platform': JSON.stringify('web'),
'process.env': JSON.stringify({}),
'process.env.BROWSER_ENV': JSON.stringify('true'),
};
即浏览器构建会优先解析依赖包的 browser 字段、用 path-browserify 替换 Node 的 path 模块、并把 process.platform 静态替换为 'web'。而 platform === 'node' 时 mainFields 为 ['module', 'main']。这段实现是后文"浏览器构建只能用 browser-safe API"这一约束的底层落地方式。
4.2 esbuild-common.mts:构建与 watch 的共享执行器
extensions/esbuild-common.mts 的 runBuild() 提供了所有扩展共用的执行语义:
--outputRoot <dir>:命令行参数,把输出目录重定向到<root>/<原 outdir 名>,供上层构建任务统一收集产物;--watch:进入监听模式。源码注释说明此处选择@parcel/watcher是因为其空闲时 CPU 占用远低于 esbuild watch;watch 回调带 100ms 防抖,且每次变更都完整重跑构建(而不是保留 esbuild context),源码注释解释这是为了降低内存占用;监听时自动忽略**/node_modules/**、**/dist/**、**/out/**,避免产物回弹触发死循环;- esbuild 服务生命周期:
esbuild.stop()会关掉所有并发构建共享的单一 esbuild 服务,因此buildOnce()用pendingBuilds计数,确保只有在没有构建在途时才stop(),防止兄弟构建(例如同一Promise.all中的其他构建)被误拆服务。
对照第二节示例,node ./esbuild.mts --watch 即可获得开发监听;而扩展 package.json 中的 gulp watch-extension:<扩展名> 脚本是仓库层面统一封装的等价入口。
五、让扩展在浏览器中运行:三步接入法
CONTRIBUTING 文档的 "Enabling an Extension in the Browser" 一节给出了完整方法:默认情况下内置扩展只面向桌面端,要同时支持浏览器版 VS Code,需要做三件事。
5.1 三步清单
- 在
package.json中添加"browser"条目,指向浏览器 bundle(例如"./dist/browser/extension"); - 添加
tsconfig.browser.json,仅做浏览器安全源码的类型检查; - 添加
esbuild.browser.mts,其中platform必须设为'browser'。
文档同时强调:浏览器构建只能使用 browser-safe API。若扩展在桌面与 Web 端需要不同行为,可以为每个目标建立独立入口:
src/extension.ts:桌面端入口;src/extension.browser.ts:浏览器端入口,并确认esbuild.browser.mts构建的是这个入口、tsconfig.browser.json也指向它。
5.2 真实案例:configuration-editing
extensions/configuration-editing/ 是这套流程的完整参照。
package.json 双入口声明(见 extensions/configuration-editing/package.json):
"main": "./out/configurationEditingMain",
"browser": "./dist/browser/configurationEditingMain",
"scripts": {
"compile": "gulp compile-extension:configuration-editing",
"watch": "gulp watch-extension:configuration-editing",
"compile-web": "npm-run-all2 -lp bundle-web typecheck-web",
"bundle-web": "node ./esbuild.browser.mts",
"typecheck-web": "node ../../node_modules/@typescript/native/lib/tsc.js --project ./tsconfig.browser.json --noEmit",
"watch-web": "npm-run-all2 -lp watch-bundle-web watch-typecheck-web",
"watch-bundle-web": "node ./esbuild.browser.mts --watch",
"watch-typecheck-web": "node ../../node_modules/@typescript/native/lib/tsc.js --project ./tsconfig.browser.json --noEmit --watch"
}
main 指向桌面端开发产物,browser 指向 dist/browser/ 下的生产 bundle,与文档给出的示例路径形态完全一致。compile-web 把"打包 + 类型检查"串成一条命令,watch-web 则并行跑监听打包与监听类型检查。
tsconfig.browser.json 只检查浏览器安全源码(extensions/configuration-editing/tsconfig.browser.json):
{
"extends": "./tsconfig.json",
"compilerOptions": {},
"exclude": ["./src/test/**"],
"files": ["./src/configurationEditingMain.ts"]
}
做法是继承主 tsconfig.json(间接继承 tsconfig.base.json),再用 files 把检查范围收敛到浏览器入口,配合 exclude 排除测试目录——这样 Node-only 的源码只要不被浏览器入口引用链触及,就不会进入浏览器类型检查的视野。
esbuild.browser.mts:platform 为 browser,且入口产物落在 dist/browser(extensions/configuration-editing/esbuild.browser.mts):
const srcDir = path.join(import.meta.dirname, 'src');
const outDir = path.join(import.meta.dirname, 'dist', 'browser');
run({
platform: 'browser',
entryPoints: {
'configurationEditingMain': path.join(srcDir, 'configurationEditingMain.ts'),
},
srcDir,
outdir: outDir,
additionalOptions: {
plugins: [browserNetPlugin],
tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'),
},
}, process.argv);
值得注意的是其中的 browserNetPlugin——一个用 esbuild onResolve 钩子把 ./node/net 的导入重定向到 ./browser/net.ts 的插件。这正是"同一份共享代码、按平台切换实现"的典型手段:桌面端走 src/node/,浏览器端由插件强制替换为 src/browser/ 下的安全实现,比单纯维护两个入口文件更细粒度。
从该扩展的目录结构看,src/ 下同时存在 node/ 与 browser/ 两组实现目录,tsconfig.browser.json 的 files 收敛 + esbuild 插件重定向两者配合,就构成了"browser-safe 约束"的完整闭环:静态层面由 platform: 'browser' 的 mainFields/alias/define 处理依赖替换,入口层面由类型检查保证不引用 Node-only 模块。
六、新扩展落地的核对清单
综合 CONTRIBUTING 文档与仓库内实例,新建一个内置扩展时可按以下清单核对:
- 目录骨架:
package.json+src/+tsconfig.json+esbuild.mts+.vscodeignore(.vscodeignore可复制 extensions/debug-auto-launch/.vscodeignore); tsconfig.json以"extends": "../tsconfig.base.json"继承基类,配置rootDir: ./src、outDir: ./out,并在include中加入../../src/vscode-dts/vscode.d.ts;esbuild.mts调用 extensions/esbuild-extension-common.mts 的run(),platform: 'node',outdir指向dist/,入口指向src/extension.ts;package.json中main指向./out/...(开发),提供compile/watch两个 gulp 转调脚本;- 如需支持浏览器:加
"browser": "./dist/browser/<入口>"字段、tsconfig.browser.json(files收敛到浏览器入口)、esbuild.browser.mts(platform: 'browser',outdir为dist/browser,必要时用插件重定向 Node-only 模块),并确认浏览器入口只使用 browser-safe API。
以上每条都能在仓库中找到现成范本:纯桌面端参考 extensions/debug-auto-launch/,桌面 + 浏览器双目标参考 extensions/configuration-editing/。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00