Web 扩展
Visual Studio Code 可以作为浏览器中的编辑器运行。一个例子是当您在 GitHub 中浏览存储库或拉取请求时,通过按 .(句点键)访问的 github.dev 用户界面。在 Web 环境中使用 VS Code 时,已安装的扩展会在浏览器中的扩展宿主中运行,该宿主称为“Web 扩展宿主”。能够在 Web 扩展宿主中运行的扩展称为“Web 扩展”。
Web 扩展与常规扩展具有相同的结构,但由于运行环境不同,它们无法运行为 Node.js 运行时编写的相同代码。Web 扩展仍然可以访问完整的 VS Code API,但无法再访问 Node.js API 和模块加载功能。相反,Web 扩展受到浏览器沙箱的限制,因此与普通扩展相比具有局限性。
VS Code 桌面版也支持 Web 扩展运行时。如果您决定将扩展创建为 Web 扩展,它将在 VS Code for the Web(包括 vscode.dev 和 github.dev)以及桌面版和 GitHub Codespaces 等服务中得到支持。
Web 扩展结构
Web 扩展的结构与常规扩展类似。扩展清单(package.json)定义了扩展源代码的入口文件并声明了扩展贡献。
对于 Web 扩展,主入口文件由 browser 属性定义,而不是像常规扩展那样由 main 属性定义。
contributes 属性对于 Web 扩展和常规扩展的工作方式相同。
下面的示例显示了一个简单的 hello world 扩展的 package.json,它仅在 Web 扩展宿主中运行(它只有一个 browser 入口点)。
{
"name": "helloworld-web-sample",
"displayName": "helloworld-web-sample",
"description": "HelloWorld example for VS Code in the browser",
"version": "0.0.1",
"publisher": "vscode-samples",
"repository": "https://github.com/microsoft/vscode-extension-samples/helloworld-web-sample",
"engines": {
"vscode": "^1.74.0"
},
"categories": ["Other"],
"activationEvents": [],
"browser": "./dist/web/extension.js",
"contributes": {
"commands": [
{
"command": "helloworld-web-sample.helloWorld",
"title": "Hello World"
}
]
},
"scripts": {
"vscode:prepublish": "npm run package-web",
"compile-web": "webpack",
"watch-web": "webpack --watch",
"package-web": "webpack --mode production --devtool hidden-source-map"
},
"devDependencies": {
"@types/vscode": "^1.59.0",
"ts-loader": "^9.2.2",
"webpack": "^5.38.1",
"webpack-cli": "^4.7.0",
"@types/webpack-env": "^1.16.0",
"process": "^0.11.10"
}
}
注意:如果您的扩展目标是 1.74 之前的 VS Code 版本,则必须在
activationEvents中显式列出onCommand:helloworld-web-sample.helloWorld。
只有 main 入口点但没有 browser 入口点的扩展不是 Web 扩展。它们会被 Web 扩展宿主忽略,并且无法在“扩展”视图中下载。

只有声明性贡献(仅有 contributes,没有 main 或 browser)的扩展可以是 Web 扩展。它们可以在无需扩展作者进行任何修改的情况下安装并在 VS Code for the Web 中运行。具有声明性贡献的扩展示例包括主题、语法和代码片段。
扩展可以同时具有 browser 和 main 入口点,以便在浏览器和 Node.js 运行时中运行。将现有扩展更新为 Web 扩展一节展示了如何迁移扩展以在两种运行时中工作。
Web 扩展启用一节列出了用于决定扩展是否可以在 Web 扩展宿主中加载的规则。
Web 扩展主文件
Web 扩展的主文件由 browser 属性定义。该脚本在 Web 扩展宿主中的 浏览器 WebWorker 环境中运行。它受到浏览器 Worker 沙箱的限制,与在 Node.js 运行时中运行的普通扩展相比具有局限性。
- 不支持导入或要求其他模块。
importScripts也不可用。因此,代码必须打包成单个文件。 - VS Code API 可以通过
require('vscode')模式加载。这是有效的,因为require有一个垫片(shim),但此垫片不能用于加载额外的扩展文件或额外的 node 模块。它仅适用于require('vscode')。 - Node.js 全局变量和库(如
process、os、setImmediate、path、util、url)在运行时不可用。不过,可以使用 webpack 等工具添加它们。Webpack 配置一节解释了如何执行此操作。 - 打开的工作区或文件夹位于虚拟文件系统中。对工作区文件的访问需要通过
vscode.workspace.fs提供的 VS Code 文件系统 API 进行。 - 扩展上下文位置(
ExtensionContext.extensionUri)和存储位置(ExtensionContext.storageUri、globalStorageUri)也位于虚拟文件系统上,需要通过vscode.workspace.fs进行访问。 - 要访问 Web 资源,必须使用 Fetch API。被访问的资源需要支持跨域资源共享 (CORS)。
- 无法创建子进程或运行可执行文件。但是,可以通过 Worker API 创建 Web Worker。这用于运行语言服务器,如 Web 扩展中的语言服务器协议一节所述。
- 与常规扩展一样,扩展的
activate/deactivate函数需要通过exports.activate = ...模式导出。
开发 Web 扩展
值得庆幸的是,TypeScript 和 webpack 等工具可以隐藏许多浏览器运行时限制,并允许您以与常规扩展相同的方式编写 Web 扩展。Web 扩展和常规扩展通常可以从相同的源代码生成。
例如,由 yo code 生成器创建的 Hello Web Extension 仅在构建脚本上有所不同。您可以使用通过 Debug: Select and Start Debugging(调试:选择并开始调试)命令访问的提供启动配置,像传统的 Node.js 扩展一样运行和调试生成的扩展。
创建 Web 扩展
要搭建新的 Web 扩展,请使用 yo code 并选择 New Web Extension(新建 Web 扩展)。请确保已安装最新版本的 generator-code(>= generator-code@1.6)。要更新生成器和 yo,请运行 npm i -g yo generator-code。
所创建的扩展由扩展的源代码(一个显示 hello world 通知的命令)、package.json 清单文件以及 webpack 或 esbuild 配置文件组成。
为了简化起见,我们假设您使用 webpack 作为打包工具。在本文末尾,我们还将解释选择 esbuild 时的区别。
src/web/extension.ts是扩展的入口源代码文件。它与常规的 hello 扩展完全相同。package.json是扩展清单。- 它通过
browser属性指向入口文件。 - 它提供脚本:
compile-web、watch-web和package-web,分别用于编译、监视和打包。
- 它通过
webpack.config.js是 webpack 配置文件,它将扩展源代码编译并打包成单个文件。.vscode/launch.json包含启动配置,这些配置在带有 Web 扩展宿主的 VS Code 桌面版中运行 Web 扩展和测试(不再需要设置extensions.webWorker)。.vscode/task.json包含启动配置使用的构建任务。它使用npm run watch-web并依赖于 webpack 特定的ts-webpack-watch问题匹配器。.vscode/extensions.json包含提供问题匹配器的扩展。需要安装这些扩展才能使启动配置正常工作。tsconfig.json定义了符合webworker运行时的编译选项。
helloworld-web-sample 中的源代码与生成器创建的内容类似。
Webpack 配置
Webpack 配置文件由 yo code 自动生成。它将您的扩展源代码打包成一个 JavaScript 文件,以便在 Web 扩展宿主中加载。
稍后我们将解释如何将 esbuild 用作打包工具,但目前我们先从 webpack 开始。
const path = require('path');
const webpack = require('webpack');
/** @typedef {import('webpack').Configuration} WebpackConfig **/
/** @type WebpackConfig */
const webExtensionConfig = {
mode: 'none', // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
target: 'webworker', // extensions run in a webworker context
entry: {
extension: './src/web/extension.ts', // source of the web extension main file
'test/suite/index': './src/web/test/suite/index.ts' // source of the web extension test runner
},
output: {
filename: '[name].js',
path: path.join(__dirname, './dist/web'),
libraryTarget: 'commonjs',
devtoolModuleFilenameTemplate: '../../[resource-path]'
},
resolve: {
mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules
extensions: ['.ts', '.js'], // support ts-files and js-files
alias: {
// provides alternate implementation for node module and source files
},
fallback: {
// Webpack 5 no longer polyfills Node.js core modules automatically.
// see https://webpack.js.cn/configuration/resolve/#resolvefallback
// for the list of Node.js core module polyfills.
assert: require.resolve('assert')
}
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
}
]
},
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser' // provide a shim for the global `process` variable
})
],
externals: {
vscode: 'commonjs vscode' // ignored because it doesn't exist
},
performance: {
hints: false
},
devtool: 'nosources-source-map' // create a source map that points to the original source file
};
module.exports = [webExtensionConfig];
webpack.config.js 的一些重要字段:
entry字段包含您的扩展和测试套件的主入口点。- 您可能需要调整此路径以正确指向您的扩展的入口点。
- 对于现有扩展,您可以首先将此路径指向您当前用于
package.json的main属性的文件。 - 如果您不想打包测试,可以省略测试套件字段。
output字段指示编译后的文件将位于何处。[name]将被entry中使用的键替换。因此在生成的配置文件中,它将生成dist/web/extension.js和dist/web/test/suite/index.js。
target字段指示编译后的 JavaScript 文件将在哪种类型的环境中运行。对于 Web 扩展,您希望此项为webworker。resolve字段包含了为在浏览器中无法工作的 node 库添加别名和回退功能的能力。- 如果您使用的是像
path这样的库,您可以指定如何在 Web 编译上下文中解析path。例如,您可以指向项目中定义path的文件,使用path: path.resolve(__dirname, 'src/my-path-implementation-for-web.js')。或者您可以使用 Browserify 提供的库的 node 打包版本path-browserify,并指定path: require.resolve('path-browserify')。 - 有关 Node.js 核心模块 polyfill 的列表,请参阅 webpack resolve.fallback。
- 如果您使用的是像
plugins部分使用 DefinePlugin 插件来为 Node.js 全局变量(如process)提供 polyfill。
测试您的 Web 扩展
在将 Web 扩展发布到 Marketplace 之前,目前有三种测试方法。
- 使用在桌面上运行的 VS Code,带有
--extensionDevelopmentKind=web选项,以便在 VS Code 中运行的 Web 扩展宿主中运行您的 Web 扩展。 - 使用 @vscode/test-web node 模块打开一个包含 VS Code for the Web(包括您的扩展)的浏览器,该浏览器由本地服务器提供。
- 将您的扩展侧加载到 vscode.dev,以在实际环境中查看您的扩展。
在桌面上运行的 VS Code 中测试您的 Web 扩展
为了使用现有的 VS Code 扩展开发体验,在桌面上运行的 VS Code 支持同时运行 Web 扩展宿主和常规 Node.js 扩展宿主。
使用 New Web Extension 生成器提供的 pwa-extensionhost 启动配置。
{
"version": "0.2.0",
"configurations": [
{
"name": "Run Web Extension in VS Code",
"type": "pwa-extensionHost",
"debugWebWorkerHost": true,
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentKind=web"
],
"outFiles": ["${workspaceFolder}/dist/web/**/*.js"],
"preLaunchTask": "npm: watch-web"
}
]
}
它使用 npm: watch-web 任务通过调用 npm run watch-web 来编译扩展。该任务在 tasks.json 中定义。
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch-web",
"group": "build",
"isBackground": true,
"problemMatcher": ["$ts-webpack-watch"]
}
]
}
$ts-webpack-watch 是一个可以解析 webpack 工具输出的问题匹配器。它由 TypeScript + Webpack Problem Matchers 扩展提供。
在启动的 Extension Development Host(扩展开发宿主)实例中,Web 扩展将可用并在 Web 扩展宿主中运行。运行 Hello World 命令以激活扩展。
打开 Running Extensions(运行中的扩展)视图(命令:Developer: Show Running Extensions,即:开发者:显示运行中的扩展)以查看哪些扩展正在 Web 扩展宿主中运行。
使用 @vscode/test-web 在浏览器中测试您的 Web 扩展
@vscode/test-web node 模块提供了一个 CLI 和 API,用于在浏览器中测试 Web 扩展。
该 node 模块贡献了一个 npm 二进制文件 vscode-test-web,它可以从命令行打开 VS Code for the Web。
- 它将 VS Code 的 Web 模块下载到
.vscode-test-web中。 - 在
localhost:3000上启动本地服务器。 - 打开浏览器(Chromium、Firefox 或 Webkit)。
您可以从命令行运行它。
npx @vscode/test-web --extensionDevelopmentPath=$extensionFolderPath $testDataPath
或者更好的方法是,将 @vscode/test-web 添加为扩展的开发依赖项,并在脚本中调用它。
"devDependencies": {
"@vscode/test-web": "*"
},
"scripts": {
"open-in-browser": "vscode-test-web --extensionDevelopmentPath=. ."
}
查看 @vscode/test-web README 以获取更多 CLI 选项。
| 选项 | 参数 说明 |
|---|---|
| --browserType | 要启动的浏览器:chromium(默认)、firefox 或 webkit。 |
| --extensionDevelopmentPath | 指向要包含的正在开发的扩展的路径。 |
| --extensionTestsPath | 要运行的测试模块的路径。 |
| --permission | 授予打开的浏览器的权限:例如 clipboard-read、clipboard-write。参见完整选项列表。参数可以提供多次。 |
| --folder-uri | 在其中打开 VS Code 的工作区 URI。当提供 folderPath 时将被忽略。 |
| --extensionPath | 指向包含要包含的附加扩展的文件夹的路径。 参数可以提供多次。 |
| folderPath | 在其中打开 VS Code 的本地文件夹。 文件夹内容将作为虚拟文件系统提供,并作为工作区打开。 |
VS Code 的 Web 模块下载到 .vscode-test-web 文件夹中。您应该将其添加到 .gitignore 文件中。
在 vscode.dev 中测试您的 Web 扩展
在向所有人发布用于 VS Code for the Web 的扩展之前,您可以验证您的扩展在实际 vscode.dev 环境中的行为。
要在 vscode.dev 上查看您的扩展,您首先需要从您的机器上托管它,以便 vscode.dev 下载并运行它。
首先,您需要 安装 mkcert。
然后,将 localhost.pem 和 localhost-key.pem 文件生成到您不会丢失它们的位置(例如 $HOME/certs)。
$ mkdir -p $HOME/certs
$ cd $HOME/certs
$ mkcert -install
$ mkcert localhost
然后,从您的扩展路径启动一个 HTTP 服务器,运行 npx serve。
$ npx serve --cors -l 5000 --ssl-cert $HOME/certs/localhost.pem --ssl-key $HOME/certs/localhost-key.pem
npx: installed 78 in 2.196s
┌────────────────────────────────────────────────────┐
│ │
│ Serving! │
│ │
│ - Local: https://:5000 │
│ - On Your Network: https://172.19.255.26:5000 │
│ │
│ Copied local address to clipboard! │
│ │
└────────────────────────────────────────────────────┘
最后,打开 vscode.dev,从命令面板(⇧⌘P(Windows, Linux Ctrl+Shift+P))运行 Developer: Install Extension From Location...(开发者:从位置安装扩展...),粘贴上面的 URL(例如 https://:5000),然后选择 Install(安装)。
查看日志
您可以查看浏览器开发者工具控制台中的日志,以查看来自扩展的任何错误、状态和日志。
您可能会看到来自 vscode.dev 本身的其它日志。此外,您无法轻松设置断点,也无法查看扩展的源代码。这些限制使得在 vscode.dev 中调试体验不是很好,因此我们建议在侧加载到 vscode.dev 之前使用前两种选项进行测试。侧加载是发布扩展之前的最后一道检查工序。
Web 扩展测试
Web 扩展测试已受支持,其实现方式与常规扩展测试类似。请参阅测试扩展一文以了解扩展测试的基本结构。
@vscode/test-web node 模块等同于 @vscode/test-electron(以前称为 vscode-test)。它允许您从命令行在 Chromium、Firefox 和 Safari 上运行扩展测试。
该工具执行以下步骤:
- 从本地 Web 服务器启动 VS Code for the Web 编辑器。
- 打开指定的浏览器。
- 运行提供的测试运行器脚本。
您可以在持续集成构建中运行测试,以确保扩展在所有浏览器上都能正常工作。
测试运行器脚本在 Web 扩展宿主上运行,并具有与 Web 扩展主文件相同的限制。
- 所有文件都打包成一个文件。它应该包含测试运行器(例如 Mocha)和所有测试(通常是
*.test.ts)。 - 仅支持
require('vscode')。
由 yo code Web 扩展生成器创建的 webpack 配置有一个用于测试的部分。它期望测试运行器脚本位于 ./src/web/test/suite/index.ts。提供的测试运行器脚本使用 Mocha 的 Web 版本,并包含用于导入所有测试文件的 webpack 特定语法。
require('mocha/mocha'); // import the mocha web build
export function run(): Promise<void> {
return new Promise((c, e) => {
mocha.setup({
ui: 'tdd',
reporter: undefined
});
// bundles all files in the current directory matching `*.test`
const importAll = (r: __WebpackModuleApi.RequireContext) => r.keys().forEach(r);
importAll(require.context('.', true, /\.test$/));
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
console.error(err);
e(err);
}
});
}
要从命令行运行 Web 测试,请将以下内容添加到您的 package.json 中,并使用 npm test 运行它。
"devDependencies": {
"@vscode/test-web": "*"
},
"scripts": {
"test": "vscode-test-web --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js"
}
要在带有测试数据的文件夹上打开 VS Code,请将本地文件夹路径(folderPath)作为最后一个参数传递。
要在 VS Code (Insiders) 桌面版中运行(和调试)扩展测试,请使用 Extension Tests in VS Code 启动配置。
{
"version": "0.2.0",
"configurations": [
{
"name": "Extension Tests in VS Code",
"type": "extensionHost",
"debugWebWorkerHost": true,
"request": "launch",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentKind=web",
"--extensionTestsPath=${workspaceFolder}/dist/web/test/suite/index"
],
"outFiles": ["${workspaceFolder}/dist/web/**/*.js"],
"preLaunchTask": "npm: watch-web"
}
]
}
发布 Web 扩展
Web 扩展与其他扩展一样托管在 Marketplace 上。
请确保使用最新版本的 vsce 来发布您的扩展。vsce 会标记所有属于 Web 扩展的扩展。为此,vsce 使用 Web 扩展启用一节中列出的规则。
将现有扩展更新为 Web 扩展
无代码扩展
没有代码、只有贡献点(例如,主题、代码片段和基本语言扩展)的扩展不需要任何修改。它们可以在 Web 扩展宿主中运行,并可以从“扩展”视图中安装。
无需重新发布,但在发布扩展的新版本时,请确保使用最新版本的 vsce。
迁移带代码的扩展
带有源代码(由 main 属性定义)的扩展需要提供Web 扩展主文件,并在 package.json 中设置 browser 属性。
请使用以下步骤为浏览器环境重新编译您的扩展代码:
- 按照 Webpack 配置一节添加 webpack 配置文件。如果您已经有了用于 Node.js 扩展代码的 webpack 文件,则可以添加一个新的 Web 部分。查看 vscode-css-formatter 作为示例。
- 添加 测试您的 Web 扩展一节中所示的
launch.json和tasks.json文件。 - 在 webpack 配置文件中,将输入文件设置为现有的 Node.js 主文件,或为 Web 扩展创建一个新的主文件。
- 在
package.json中,添加browser和scripts属性,如 Web 扩展结构一节所示。 - 运行
npm run compile-web以调用 webpack,并查看需要哪些工作才能使您的扩展在 Web 中运行。
为了确保尽可能多地重用源代码,以下是一些技术:
- 要为 Node.js 核心模块(如
path)提供 polyfill,请在 resolve.fallback 中添加一个条目。 - 要提供 Node.js 全局变量(如
process),请使用 DefinePlugin 插件。 - 使用在浏览器和 Node.js 运行时中都能工作的 node 模块。Node 模块可以通过同时定义
browser和main入口点来实现这一点。Webpack 将自动使用与其目标相匹配的一个。执行此操作的 node 模块示例包括 request-light 和 @vscode/l10n。 - 要为 node 模块或源文件提供替代实现,请使用 resolve.alias。
- 将您的代码分为浏览器部分、Node.js 部分和公共部分。在公共部分,仅使用在浏览器和 Node.js 运行时中都能工作的代码。为在 Node.js 和浏览器中具有不同实现的功能创建抽象。
- 注意
path、URI.file、context.extensionPath、rootPath、uri.fsPath的使用。这些在使用 VS Code for the Web 时,在虚拟工作区(非文件系统)中将无法工作。相反,应使用带有URI.parse、context.extensionUri的 URI。vscode-uri node 模块提供了joinPath、dirName、baseName、extName、resolvePath。 - 注意
fs的使用。请通过使用 VS Code 的workspace.fs进行替换。
当您的扩展在 Web 中运行时提供较少的功能是可以接受的。使用 when 子句上下文来控制在 Web 上运行于虚拟工作区时哪些命令、视图和任务可用或隐藏。
- 使用
virtualWorkspace上下文变量来判断当前工作区是否为非文件系统工作区。 - 使用
resourceScheme检查当前资源是否为file资源。 - 如果存在平台 shell,请使用
shellExecutionSupported。 - 实现替代命令处理程序,显示一个对话框以解释为什么该命令不适用。
WebWorker 可以作为分叉进程(forking processes)的替代方案。我们已更新了多个语言服务器以作为 Web 扩展运行,包括内置的 JSON、CSS 和 HTML 语言服务器。下面的 语言服务器协议一节提供了更多详细信息。
浏览器运行时环境仅支持执行 JavaScript 和 WebAssembly。用其他编程语言编写的库需要进行交叉编译,例如有工具可以将 C/C++ 和 Rust 编译为 WebAssembly。例如,vscode-anycode 扩展使用了 tree-sitter,这是编译为 WebAssembly 的 C/C++ 代码。
Web 扩展中的语言服务器协议
vscode-languageserver-node 是 语言服务器协议 (LSP) 的实现,它是诸如 JSON、CSS 和 HTML 等语言服务器实现的基础。
从 3.16.0 版本开始,客户端和服务器现在也提供了浏览器实现。服务器可以在 Web Worker 中运行,连接基于 Web Worker 的 postMessage 协议。
用于浏览器的客户端可以在 'vscode-languageclient/browser' 中找到。
import { LanguageClient } from `vscode-languageclient/browser`;
服务器位于 vscode-languageserver/browser。
lsp-web-extension-sample 展示了它是如何工作的。
Web 扩展启用
VS Code 会自动将扩展视为 Web 扩展,如果:
- 扩展清单 (
package.json) 具有browser入口点。 - 扩展清单没有
main入口点,并且没有以下任何贡献点:localizations、debuggers、terminal、typescriptServerPlugins。
如果扩展想要提供也能在 Web 扩展宿主中工作的调试器或终端,则需要定义 browser 入口点。
使用 ESBuild
如果您想使用 esbuild 而不是 webpack,请执行以下操作:
添加一个 esbuild.js 构建脚本。
const esbuild = require('esbuild');
const glob = require('glob');
const path = require('path');
const polyfill = require('@esbuild-plugins/node-globals-polyfill');
const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');
async function main() {
const ctx = await esbuild.context({
entryPoints: ['src/web/extension.ts', 'src/web/test/suite/extensionTests.ts'],
bundle: true,
format: 'cjs',
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: 'browser',
outdir: 'dist/web',
external: ['vscode'],
logLevel: 'warning',
// Node.js global to browser globalThis
define: {
global: 'globalThis'
},
plugins: [
polyfill.NodeGlobalsPolyfillPlugin({
process: true,
buffer: true
}),
testBundlePlugin,
esbuildProblemMatcherPlugin /* add to the end of plugins array */
]
});
if (watch) {
await ctx.watch();
} else {
await ctx.rebuild();
await ctx.dispose();
}
}
/**
* For web extension, all tests, including the test runner, need to be bundled into
* a single module that has a exported `run` function .
* This plugin bundles implements a virtual file extensionTests.ts that bundles all these together.
* @type {import('esbuild').Plugin}
*/
const testBundlePlugin = {
name: 'testBundlePlugin',
setup(build) {
build.onResolve({ filter: /[\/\\]extensionTests\.ts$/ }, args => {
if (args.kind === 'entry-point') {
return { path: path.resolve(args.path) };
}
});
build.onLoad({ filter: /[\/\\]extensionTests\.ts$/ }, async args => {
const testsRoot = path.join(__dirname, 'src/web/test/suite');
const files = await glob.glob('*.test.{ts,tsx}', { cwd: testsRoot, posix: true });
return {
contents:
`export { run } from './mochaTestRunner.ts';` +
files.map(f => `import('./${f}');`).join(''),
watchDirs: files.map(f => path.dirname(path.resolve(testsRoot, f))),
watchFiles: files.map(f => path.resolve(testsRoot, f))
};
});
}
};
/**
* This plugin hooks into the build process to print errors in a format that the problem matcher in
* Visual Studio Code can understand.
* @type {import('esbuild').Plugin}
*/
const esbuildProblemMatcherPlugin = {
name: 'esbuild-problem-matcher',
setup(build) {
build.onStart(() => {
console.log('[watch] build started');
});
build.onEnd(result => {
result.errors.forEach(({ text, location }) => {
console.error(`✘ [ERROR] ${text}`);
if (location == null) return;
console.error(` ${location.file}:${location.line}:${location.column}:`);
});
console.log('[watch] build finished');
});
}
};
main().catch(e => {
console.error(e);
process.exit(1);
});
构建脚本执行以下操作:
- 它创建带有 esbuild 的构建上下文。该上下文配置为:
- 将
src/web/extension.ts中的代码打包成单个文件dist/web/extension.js。 - 将所有测试(包括测试运行器 (mocha))打包成单个文件
dist/web/test/suite/extensionTests.js。 - 如果传递了
--production标志,则压缩代码。 - 除非传递了
--production标志,否则生成 source map。 - 将 'vscode' 模块从打包中排除(因为它由 VS Code 运行时提供)。
- 为
process和buffer创建 polyfill。 - 使用 esbuildProblemMatcherPlugin 插件来报告阻止打包完成的错误。此插件以
esbuild问题匹配器可以检测到的格式发出错误,该匹配器也需要作为扩展安装。 - 使用 testBundlePlugin 来实现一个测试主文件 (
extensionTests.js),该文件引用所有测试文件和 mocha 测试运行器mochaTestRunner.js。
- 将
- 如果传递了
--watch标志,它会开始监视源文件是否有更改,并在检测到更改时重建打包文件。
esbuild 可以直接使用 TypeScript 文件。但是,esbuild 只是剥离了所有类型声明,而不进行任何类型检查。仅报告语法错误,并且可能会导致 esbuild 失败。
因此,我们单独运行 TypeScript 编译器 (tsc) 来检查类型,但不发出任何代码(标志 --noEmit)。
package.json 中的 scripts 部分现在看起来像这样:
"scripts": {
"vscode:prepublish": "npm run package-web",
"compile-web": "npm run check-types && node esbuild.js",
"watch-web": "npm-run-all -p watch-web:*",
"watch-web:esbuild": "node esbuild.js --watch",
"watch-web:tsc": "tsc --noEmit --watch --project tsconfig.json",
"package-web": "npm run check-types && node esbuild.js --production",
"check-types": "tsc --noEmit",
"pretest": "npm run compile-web",
"test": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/extensionTests.js",
"run-in-browser": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. ."
}
npm-run-all 是一个 node 模块,它并行运行名称与给定前缀匹配的脚本。对我们来说,它运行 watch-web:esbuild 和 watch-web:tsc 脚本。您需要将 npm-run-all 添加到 package.json 中的 devDependencies 部分。
以下 tasks.json 文件为您每个监视任务提供单独的终端:
{
"version": "2.0.0",
"tasks": [
{
"label": "watch-web",
"dependsOn": ["npm: watch-web:tsc", "npm: watch-web:esbuild"],
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
},
"runOptions": {
"runOn": "folderOpen"
}
},
{
"type": "npm",
"script": "watch-web:esbuild",
"group": "build",
"problemMatcher": "$esbuild-watch",
"isBackground": true,
"label": "npm: watch-web:esbuild",
"presentation": {
"group": "watch",
"reveal": "never"
}
},
{
"type": "npm",
"script": "watch-web:tsc",
"group": "build",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"label": "npm: watch-web:tsc",
"presentation": {
"group": "watch",
"reveal": "never"
}
},
{
"label": "compile",
"type": "npm",
"script": "compile-web",
"problemMatcher": ["$tsc", "$esbuild"]
}
]
}
这是 esbuild 构建脚本中引用的 mochaTestRunner.js:
// Imports mocha for the browser, defining the `mocha` global.
import 'mocha/mocha';
mocha.setup({
ui: 'tdd',
reporter: undefined
});
export function run(): Promise<void> {
return new Promise((c, e) => {
try {
// Run the mocha test
mocha.run(failures => {
if (failures > 0) {
e(new Error(`${failures} tests failed.`));
} else {
c();
}
});
} catch (err) {
console.error(err);
e(err);
}
});
}