Web 扩展
Visual Studio Code 可以在浏览器中作为编辑器运行。一个例子是在 GitHub 中浏览存储库或拉取请求时按下 .
(句点键)到达的 github.dev
用户界面。当 VS Code 在 Web 中使用时,已安装的扩展会在浏览器中的一个扩展主机中运行,该主机称为“Web 扩展主机”。可以在 Web 扩展主机中运行的扩展称为“Web 扩展”。
Web 扩展与常规扩展共享相同的结构,但由于运行时不同,它们运行的代码与为 Node.js 运行时编写的扩展不同。Web 扩展仍然可以访问完整的 VS Code API,但不再访问 Node.js API 和模块加载。相反,Web 扩展受浏览器沙盒的限制,因此与普通扩展相比存在限制。
Web 扩展运行时在 VS Code 桌面版上也受支持。如果你决定将扩展创建为 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"
}
}
注意:如果你的扩展针对的版本低于 VS Code 1.74,你必须在
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 扩展主机中的 Browser WebWorker 环境中运行。它受到浏览器 Worker 沙盒的限制,与在 Node.js 运行时中运行的普通扩展相比存在限制。
- 不支持导入或 require 其他模块。
importScripts
也不可用。因此,代码必须打包成一个单一文件。 - VS Code API 可以通过
require('vscode')
模式加载。这会奏效,因为存在一个用于require
的 shim,但这个 shim 不能用于加载其他扩展文件或额外的 Node 模块。它只适用于require('vscode')
。 - Node.js 全局变量和库(如
process
、os
、setImmediate
、path
、util
、url
)在运行时不可用。但是,可以使用 webpack 等工具添加它们。webpack 配置部分解释了如何实现这一点。 - 打开的工作区或文件夹位于虚拟文件系统上。访问工作区文件需要通过 VS Code 文件系统 API,该 API 可通过
vscode.workspace.fs
访问。 - 扩展上下文位置 (
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
仅在构建脚本上有所不同。你可以使用提供的启动配置(可通过调试: 选择并启动调试命令访问)运行和调试生成的扩展,就像调试传统 Node.js 扩展一样。
创建 Web 扩展
要搭建新的 Web 扩展,请使用 yo code
并选择 New Web Extension。确保安装了最新版本的 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.resolve(__dirname, 'src/my-path-implementation-for-web.js')
定义path
。或者你可以使用 Browserify 打包的 node 库版本,称为path-browserify
,并指定path: require.resolve('path-browserify')
。 - 有关 Node.js 核心模块 polyfill 的列表,请参阅 webpack resolve.fallback。
- 如果你使用
plugins
部分使用 DefinePlugin 插件来 polyfill 全局变量,例如process
Node.js 全局变量。
测试你的 Web 扩展
测试你的 Web 扩展
- 在将 Web 扩展发布到市场之前,目前有三种方法可以测试它:
- 使用桌面版 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 支持与常规 Node.js 扩展主机一起运行 Web 扩展主机。
{
"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"
}
]
}
使用 New Web Extension 生成器提供的 pwa-extensionhost
启动配置。
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "watch-web",
"group": "build",
"isBackground": true,
"problemMatcher": ["$ts-webpack-watch"]
}
]
}
它使用任务 npm: watch-web
通过调用 npm run watch-web
来编译扩展。该任务应位于 tasks.json
中。
$ts-webpack-watch
是一个问题匹配器,可以解析 webpack 工具的输出。它由 TypeScript + Webpack Problem Matchers 扩展提供。
在启动的 Extension Development Host 实例中,Web 扩展将可用并在 Web 扩展主机中运行。运行 Hello World
命令以激活扩展。
打开运行中的扩展视图(命令:开发者: 显示运行中的扩展)以查看哪些扩展正在 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 bits 下载到
.vscode-test-web
中。 - 在
localhost:3000
上启动一个本地服务器。
打开一个浏览器(Chromium、Firefox 或 Webkit)。
npx @vscode/test-web --extensionDevelopmentPath=$extensionFolderPath $testDataPath
你可以从命令行运行它。
"devDependencies": {
"@vscode/test-web": "*"
},
"scripts": {
"open-in-browser": "vscode-test-web --extensionDevelopmentPath=. ."
}
或者更好的是,将 @vscode/test-web
添加为扩展的开发依赖,并在脚本中调用它。
查看 @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 bits 会下载到 .vscode-test-web
文件夹中。你最好将其添加到 .gitignore
文件中。
在 vscode.dev 中测试你的 Web 扩展
在将你的扩展发布供所有人用于 VS Code for the Web 之前,你可以验证你的扩展在实际的 vscode.dev 环境中的行为。
要在 vscode.dev 上查看你的扩展,首先需要从你的机器上托管它,以便 vscode.dev 下载和运行。
首先,你需要安装 mkcert
。
$ mkdir -p $HOME/certs
$ cd $HOME/certs
$ mkcert -install
$ mkcert localhost
然后,将 localhost.pem
和 localhost-key.pem
文件生成到你不会丢失它们的位置(例如 $HOME/certs
)。
$ 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://localhost:5000 │
│ - On Your Network: https://172.19.255.26:5000 │
│ │
│ Copied local address to clipboard! │
│ │
└────────────────────────────────────────────────────┘
然后,从你的扩展路径中,运行 npx serve
启动一个 HTTP 服务器。
最后,打开 vscode.dev,从命令面板(⇧⌘P (Windows, Linux Ctrl+Shift+P))运行开发者: 从位置安装扩展... 命令,粘贴上面提到的 URL(例如 https://localhost:5000
),然后选择安装。
检查日志
你可以在浏览器开发者工具的控制台中检查日志,查看扩展的任何错误、状态和日志。
Web 扩展测试
你可能会看到来自 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('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);
}
});
}
只支持 require('vscode')
。
"devDependencies": {
"@vscode/test-web": "*"
},
"scripts": {
"test": "vscode-test-web --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js"
}
由 yo code
Web 扩展生成器创建的 webpack 配置包含一个用于测试的部分。它期望测试运行器脚本位于 ./src/web/test/suite/index.ts
。提供的测试运行器脚本使用 Mocha 的 Web 版本,并包含特定于 webpack 的语法来导入所有测试文件。
要从命令行运行 Web 测试,请将以下内容添加到你的 package.json
并使用 npm test
运行。
{
"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 扩展
要在包含测试数据的文件夹上打开 VS Code,请将本地文件夹路径 (folderPath
) 作为最后一个参数传递。
要在 VS Code (Insiders) 桌面版中运行(和调试)扩展测试,请使用 Extension Tests in VS Code
启动配置。
将现有扩展更新为 Web 扩展
发布 Web 扩展
Web 扩展与常规扩展一起托管在市场上。
确保使用最新版本的 vsce
发布你的扩展。vsce
会标记所有 Web 扩展。为此,vsce
使用Web 扩展启用部分中列出的规则。
无代码的扩展
没有代码,只有贡献点(例如主题、代码片段和基本语言扩展)的扩展无需进行任何修改。它们可以在 Web 扩展主机中运行,并可以从“扩展”视图安装。
无需重新发布,但在发布新版本时,请确保使用最新版本的 vsce
。
- 迁移含代码的扩展
- 具有源代码(由
main
属性定义)的扩展需要提供一个Web 扩展主文件,并在package.json
中设置browser
属性。 - 使用以下步骤为浏览器环境重新编译你的扩展代码:
- 添加一个 webpack 配置文件,如webpack 配置部分所示。如果你的 Node.js 扩展代码已经有一个 webpack 文件,可以为 Web 添加一个新部分。可以参考 vscode-css-formatter 作为示例。
- 添加
launch.json
和tasks.json
文件,如测试你的 Web 扩展部分所示。
在 webpack 配置文件中,将输入文件设置为现有的 Node.js 主文件,或者为 Web 扩展创建一个新的主文件。
- 在
package.json
中,添加browser
和scripts
属性,如Web 扩展剖析部分所示。 - 运行
npm run compile-web
以调用 webpack,并查看需要哪些工作才能使你的扩展在 Web 中运行。 - 为了确保尽可能多地重用源代码,这里有一些技巧:
- 要 polyfill Node.js 核心模块(如
path
),请在 resolve.fallback 中添加一个条目。 - 要提供 Node.js 全局变量(如
process
),请使用 DefinePlugin 插件。 - 使用在浏览器和 node 运行时中都能工作的 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
的使用。替换为使用 vscode 的workspace.fs
。 - 扩展在 Web 中运行时提供较少的功能是可以接受的。使用when 子句上下文来控制在 Web 上的虚拟工作区中运行时哪些命令、视图和任务可用或隐藏。
- 使用
virtualWorkspace
上下文变量来判断当前工作区是否为非文件系统工作区。
使用 resourceScheme
来检查当前资源是否为 file
资源。
使用 shellExecutionSupported
来检查是否存在平台 shell。
实现替代的命令处理器,显示一个对话框解释为什么该命令不适用。
WebWorkers 可以用作 fork 进程的替代方案。我们已经更新了几个语言服务器,使其作为 Web 扩展运行,包括内置的 JSON、CSS 和 HTML 语言服务器。下面的语言服务器协议部分提供了更多详细信息。
浏览器运行时环境仅支持执行 JavaScript 和 WebAssembly。用其他编程语言编写的库需要交叉编译,例如有工具可以将 C/C++ 和 Rust 编译到 WebAssembly。vscode-anycode 扩展例如使用了 tree-sitter,它是编译到 WebAssembly 的 C/C++ 代码。
Web 扩展中的语言服务器协议
import { LanguageClient } from `vscode-languageclient/browser`;
vscode-languageserver-node 是 语言服务器协议 (LSP) 的一个实现,用作 JSON、CSS 和 HTML 等语言服务器实现的底层基础。
从 3.16.0 版本开始,客户端和服务器现在也提供了浏览器实现。服务器可以在 Web worker 中运行,连接基于 webworkers 的 postMessage
协议。
Web 扩展启用
浏览器的客户端位于 'vscode-languageclient/browser'。
- 服务器位于
vscode-languageserver/browser
。 - lsp-web-extension-sample 示例展示了这是如何工作的。
Web 扩展启用
使用 ESBuild
如果满足以下条件,VS Code 会自动将扩展视为 Web 扩展:
扩展清单 (package.json
) 具有 browser
入口点。
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);
});
扩展清单没有 main
入口点,并且没有以下任何贡献点:localizations
、debuggers
、terminal
、typescriptServerPlugins
。
- 如果扩展想要提供一个在 Web 扩展主机中也能工作的调试器或终端,则需要定义一个
browser
入口点。- 使用 ESBuild
- 如果你想使用 esbuild 而不是 webpack,请执行以下操作:
- 添加一个
esbuild.js
构建脚本。 - 构建脚本执行以下操作:
- 使用 esbuild 创建一个构建上下文。该上下文配置为:
- 将
src/web/extension.ts
中的代码打包到一个单一文件dist/web/extension.js
中。 - 将所有测试,包括测试运行器 (mocha) 打包到一个单一文件
dist/web/test/suite/extensionTests.js
中。 - 如果传入了
--production
标志,则压缩代码。
- 除非传入了
--production
标志,否则生成源映射。
从打包中排除 'vscode' 模块(因为它由 VS Code 运行时提供)。
为 process
和 buffer
创建 polyfills。
使用 esbuildProblemMatcherPlugin 插件报告阻止打包器完成的错误。该插件以一种格式发出错误,该格式可以被 esbuild
问题匹配器检测到,后者也需要作为扩展安装。
"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=. ."
}
使用 testBundlePlugin 实现一个测试主文件 (extensionTests.js
),该文件引用所有测试文件和 mocha 测试运行器 mochaTestRunner.js
。
如果传入了 --watch
标志,则开始监视源文件的更改,并在检测到更改时重建打包。
{
"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 可以直接处理 TypeScript 文件。但是,esbuild 只是剥离所有类型声明,而不进行任何类型检查。只会报告语法错误,并可能导致 esbuild 失败。
// 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);
}
});
}