☰
Positron 内置扩展目录结构、双输出构建与浏览器端启用实践指南
2026/10/14 21:00:51 网站建设 项目流程
  • 开发工具
  • 代码编辑器
  • 数据科学

【免费下载链接】positron

Positron, a next-generation data science IDE

项目地址:https://gitcode.com/gh_mirrors/po/positron
点击查看免费下载

本文围绕 Positron(基于 VS Code 架构的数据科学 IDE)仓库中extensions/目录的贡献规范 extensions/CONTRIBUTING.md 展开,系统讲解一个典型 TypeScript 内置扩展的标准文件结构(package.json、src/、tsconfig.json、esbuild.mts、.vscodeignore)、out/与dist/两套输出目录的分工,以及如何通过browser入口字段、tsconfig.browser.json和esbuild.browser.mts三件套让扩展同时运行在桌面端和浏览器端。读完本文,你可以按仓库既有模式从零搭建一个新的内置扩展,并理解底层 gulp/esbuild 构建链路如何工作。

一、目录与定位:内置扩展住在extensions/下

Positron 仓库的extensions/目录存放所有随产品分发的内置扩展(如 emmet、debug-auto-launch、typescript-language-features 等,以及 Positron 自研的 positron-python、positron-supervisor 等)。贡献规范的核心结论是:一个典型的 TypeScript 内置扩展具有以下结构:

文件职责
package.json扩展清单(manifest),声明main/browser入口、engines.vscode、contributes等
src/TypeScript 源码主目录
tsconfig.json主 TypeScript 配置,必须继承扩展根目录的 tsconfig.base.json
esbuild.mts生产构建用的 esbuild 构建脚本
.vscodeignore打包忽略清单,可从现有扩展复制

对应地,TypeScript 扩展有两类输出目录:

  • out/:开发构建(development build)的输出目录;
  • dist/:生产构建(production build)的输出目录。

这套out/与dist/的分工在 tsconfig.base.json 中有全局约束:它统一设置了strict: true、target: "ES2020"、module: "commonjs"、esModuleInterop、noUnusedLocals、experimentalDecorators等严格编译选项。Positron 还在此文件中额外注入了两个全局类型声明文件(源码中的// --- Start Positron ---标记段):

"files": [ "../src/typings/thenable.d.ts", "../src/typings/base-common.d.ts" ]

即每个扩展编译时都会自动引入 src/typings/thenable.d.ts 和 src/typings/base-common.d.ts。以 extensions/emmet/tsconfig.json 为例,它在继承基础上只声明了扩展特有的部分:rootDir: "./src"、outDir: "./out",并把../../src/vscode-dts/vscode.d.ts加入include,让扩展代码可以直接获得vscode命名空间的完整类型定义。

二、两套输出目录:out/由 gulp 转译,dist/由 esbuild 打包

理解贡献规范的关键,是分清out/和dist/分别由什么工具链产出。

开发构建(out/):package.json中的compile/watch脚本通常委托给 gulp,例如 emmet 的:

"compile": "gulp compile-extension:emmet", "watch": "gulp watch-extension:emmet"

在 build/gulpfile.extensions.ts 中,gulp 会为每个扩展动态注册clean-extension-${name}、transpile-extension:${name}、compile-extension:${name}和watch-extension:${name}任务(约 L230-L315)。其中compile-extension:${name}的实际行为是:先清理out/目录和.tsbuildinfo,然后把src/中的非 TS 文件原样拷贝到out/,同时用 tsgo(TypeScript 原生编译器)增量转译 TS 源码到out/。产物是逐文件转译的 JS(保留目录结构、带 source map 改写),适合日常开发调试,因此package.json的main字段一般指向开发产物。

生产构建(dist/):由扩展根目录的esbuild.mts驱动,产出的是经bundle + minify + treeShaking的产物。以 extensions/emmet/esbuild.mts 为例:

import * as path from 'node:path'; import { run } from '../esbuild-extension-common.mts'; const srcDir = path.join(import.meta.dirname, 'src'); const outDir = path.join(import.meta.dirname, 'dist', 'node'); run({ platform: 'node', entryPoints: { 'emmetNodeMain': path.join(srcDir, 'node', 'emmetNodeMain.ts'), }, srcDir, outdir: outDir, }, process.argv);

更简单的单入口扩展如 extensions/debug-auto-launch/esbuild.mts 则直接以src/extension.ts为入口、dist/为输出目录。

两个入口与输出目录的对应关系在 emmet 的package.json中体现得非常清楚(见 extensions/emmet/package.json):

"main": "./out/node/emmetNodeMain", "browser": "./dist/browser/emmetBrowserMain"

main指向 gulp 开发构建产物(out/),browser指向 esbuild 生产构建产物(dist/)。桌面端生产构建则由打包流程统一执行各扩展的esbuild.mts生成 bundle。

三、公共构建脚本:esbuild-extension-common.mts的默认选项

所有扩展的esbuild.mts并不直接调用 esbuild API,而是统一调用共享的 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'], // --- Start Positron --- // 把 'positron' 加入默认 externals,使扩展可以 // `import * as positron from 'positron'` 而无需打包 // (positron 命名空间由宿主在运行时注入) external: ['vscode', 'positron'], // --- End Positron --- format: config.format ?? 'cjs', logOverride: { 'import-is-undefined': 'error', }, };

这里有几个值得注意的实现细节:

  1. external: ['vscode', 'positron']:vscodeAPI 由宿主运行时注入,必须 external;positron是 Positron 在此 fork 中追加的 external(源码中的// --- Start Positron ---标记段),意味着任何内置扩展都可以通过import * as positron from 'positron'使用 Positron 专属 API,而不需要将其打包进 bundle。
  2. 平台差异化:当platform === 'browser'时,公共脚本会额外设置options.alias = { 'path': 'path-browserify' },并把process.platform静态替换为"web"、process.env替换为{ BROWSER_ENV: 'true' }。这保证了浏览器 bundle 不会误引 Node 的path模块,也让扩展代码可以用process.platform === 'web'判断运行环境。当platform === 'node'时则设置mainFields: ['module', 'main']。
  3. watch 模式:底层的 extensions/esbuild-common.mts 中runBuild()解析--watch和--outputRoot参数;watch 模式改用@parcel/watcher监听srcDir(忽略node_modules/**、dist/**、out/**),带 100ms 防抖,且每次变更都完整重跑构建而非维持 esbuild context,以降低常驻内存占用。因此各扩展脚本中的watch-web类命令形如node ./esbuild.browser.mts --watch。

四、在浏览器端启用扩展:三步配置 + 独立入口

贡献规范指出,扩展默认只面向桌面端(desktop)。要同时支持浏览器(web 端),需要完成三件事:

  1. package.json增加"browser"字段,指向浏览器 bundle(例如"./dist/browser/extension");
  2. 新增tsconfig.browser.json,只对浏览器安全的源码做类型检查;
  3. 新增esbuild.browser.mts,其中必须设置platform: 'browser'。

并有一条硬约束:浏览器构建只能使用浏览器安全的 API。若桌面端和 web 端行为需要分叉,应为每个目标创建独立入口:

  • src/extension.ts:桌面端入口;
  • src/extension.browser.ts:浏览器入口,且必须确保esbuild.browser.mts以此构建、tsconfig.browser.json以此为目标。

仓库中的 extensions/emmet 是这套模式最完整的范例,四个文件互相印证:

(1)package.json(见 extensions/emmet/package.json)同时声明两个入口,并配置了 web 侧脚本:

"main": "./out/node/emmetNodeMain", "browser": "./dist/browser/emmetBrowserMain", "scripts": { "compile": "gulp compile-extension:emmet", "watch": "gulp watch-extension:emmet", "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" }

注意compile-web是bundle-web(esbuild 打 bundle)与typecheck-web(tsc 对tsconfig.browser.json做--noEmit检查)的组合——这正是"浏览器构建必须通过类型检查"约束的落地方式。

(2)extensions/emmet/esbuild.browser.mts:与桌面版esbuild.mts结构相同,差异在于platform: 'browser'、输出目录为dist/browser,入口为src/browser/emmetBrowserMain.ts,并通过additionalOptions.tsconfig指回tsconfig.browser.json:

run({ platform: 'browser', entryPoints: { 'emmetBrowserMain': path.join(srcDir, 'browser', 'emmetBrowserMain.ts'), }, srcDir, outdir: outDir, additionalOptions: { tsconfig: path.join(import.meta.dirname, 'tsconfig.browser.json'), }, }, process.argv);

(3)extensions/emmet/tsconfig.browser.json:继承主tsconfig.json,再用files数组把类型检查范围收敛到浏览器入口这一个文件(并排除src/test/**):

{ "extends": "./tsconfig.json", "compilerOptions": {}, "exclude": ["./src/test/**"], "files": ["./src/browser/emmetBrowserMain.ts"] }

(4)双入口的"浏览器安全"实践:对比两个入口文件可以直观看到文档所说的 API 边界。桌面入口 src/node/emmetNodeMain.ts 使用了 Node 专属模块os来取用户主目录:

import * as vscode from 'vscode'; import { homedir } from 'os'; // Node-only API ... setHomeDir(vscode.Uri.file(homedir())); activateEmmetExtension(context);

而浏览器入口 src/browser/emmetBrowserMain.ts 则完全不碰 Node API,只注册共享逻辑:

import * as vscode from 'vscode'; import { activateEmmetExtension } from '../emmetCommon'; export function activate(context: vscode.ExtensionContext) { activateEmmetExtension(context); }

即"Node-only 的能力(如读写用户目录)只留在桌面入口,共享逻辑收敛在emmetCommon中",这正是贡献规范中"distinct entrypoints for each target"要求的典型实现。

五、打包与验证:gulp 如何发现并执行这些脚本

了解构建脚本后,再看仓库级的执行入口。build/gulpfile.extensions.ts 中的buildWebExtensions()(L455-L479)会 glob 扫描extensions/**/esbuild.browser.mts,对每一个命中的扩展同时执行 esbuild 打包(ext.esbuildExtensions)和类型检查(ext.typeCheckExtension),分别挂到compile-web与watch-web两个 gulp 任务上。也就是说:只要扩展根目录存在esbuild.browser.mts,产品级的 web 构建流程就会自动把它纳入——这与贡献规范"添加esbuild.browser.mts即可启用浏览器端"的说法完全对应。

桌面端同理,每个扩展的compile-extension:${name}/watch-extension:${name}任务由 gulp 按extensions/*/tsconfig.json自动注册(见 build/gulpfile.extensions.ts L235-L315)。Positron 在此还加了一处扩展:编译任务会把非 TS 文件(含.tsx之外的资源)原样拷贝到out/(源码中的// --- Start Positron ---段,L248-L257),并支持watch-extension-no-clean变体以配合有依赖顺序的扩展(positron-data-explorer-protocol、positron-data-explorer-formatting、positron-data-explorer-duckdb需先于依赖方编译,L325-L359)。

日常开发中可参考的验证命令(以 emmet 为例):

# 桌面端开发构建 + 监听(gulp 转译到 out/) npm --prefix extensions/emmet run compile npm --prefix extensions/emmet run watch # 浏览器端构建 + 类型检查 npm --prefix extensions/emmet run compile-web npm --prefix extensions/emmet run watch-web

六、.vscodeignore:打包忽略清单

贡献规范提到.vscodeignore可直接从现有扩展复制。以 extensions/emmet/.vscodeignore 为参考,其作用是让扩展打包时排除源码与开发产物,只保留运行时需要的out/dist等:

test/** test-workspace/** src/** out/** tsconfig*.json **/*.tsbuildinfo esbuild* CONTRIBUTING.md cgmanifest.json package-lock.json .vscode

注意它忽略了src/**与out/**而保留了dist/**,说明该扩展发布形态以 esbuild 生产 bundle 为准。每个扩展可根据自身main/browser指向的目录调整此清单,但"从成熟扩展复制再微调"是规范推荐的低错做法。

七、新建内置扩展核对清单

综合贡献规范与仓库实现,新建一个 TypeScript 内置扩展(以my-ext为例)的最小文件集:

  1. extensions/my-ext/package.json:name、publisher: "vscode"、engines.vscode、main: "./out/extension"、activationEvents、contributes;
  2. extensions/my-ext/src/extension.ts:实现activate(context)的桌面入口;
  3. extensions/my-ext/tsconfig.json:"extends": "../tsconfig.base.json",设rootDir: "./src"、outDir: "./out",include中加入../../src/vscode-dts/vscode.d.ts;
  4. extensions/my-ext/esbuild.mts:调用run({ platform: 'node', entryPoints: { extension: ... }, srcDir, outdir });
  5. extensions/my-ext/.vscodeignore:复制自 extensions/emmet/.vscodeignore 并按需调整。

如需浏览器支持,再补三件套:

  1. package.json增加"browser": "./dist/browser/extension",并把入口改为src/extension.ts(桌面)与src/extension.browser.ts(浏览器)双文件结构,浏览器入口只使用浏览器安全 API;
  2. tsconfig.browser.json:继承./tsconfig.json,用files收敛到src/extension.browser.ts;
  3. esbuild.browser.mts:platform: 'browser'、outdir指向dist/browser、additionalOptions.tsconfig指向tsconfig.browser.json。

完成后用gulp compile-extension:my-ext验证桌面构建,用node ./esbuild.browser.mts加tsc --project ./tsconfig.browser.json --noEmit验证浏览器构建;扩展根目录出现esbuild.browser.mts后,产品级compile-web任务会自动将其纳入。

总结

Positron 仓库extensions/目录的贡献规范本质上是一套"约定优于配置"的工程模板:标准五件套文件保证扩展结构一致;tsconfig.base.json提供统一且严格的编译基线;out/(gulp/tsgo 逐文件转译)与dist/(esbuild 单文件 bundle)双输出目录分别服务开发与发布;而browser字段 +tsconfig.browser.json+esbuild.browser.mts(platform: 'browser')+ 独立浏览器入口,四者齐备才能让扩展在 web 端安全运行。由于所有构建脚本共享 extensions/esbuild-extension-common.mts 中的默认选项(含 Positron 特有的positronexternal 注入),单个扩展脚本只需声明入口和输出目录,平台的打包与 watch 链路(build/gulpfile.extensions.ts)便会自动发现并编排它们。

  • 开发工具
  • 代码编辑器
  • 数据科学

【免费下载链接】positron

Positron, a next-generation data science IDE

项目地址:https://gitcode.com/gh_mirrors/po/positron
点击查看免费下载

相关推荐

上一篇:OmniRoute A2A Server 接入指南:基于 JSON-RPC 2.0 的智能路由 Agent 协议
下一篇:Qwen Code `/review` 的 Aone Code `pr-context` 支持:用平台读取层解除 Aone 评审的上下文不可用上限

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询