网页扩展
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扩展结构
一个网页扩展结构类似于常规扩展。扩展清单(package.json
)定义了扩展源代码的入口文件,并声明了扩展的贡献。
对于网页扩展,主入口文件由browser
属性定义,而不是像常规扩展那样由main
属性定义。
contributes
属性在网页扩展和常规扩展中的工作方式相同。
下面的示例展示了一个简单的“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 extension enablement 部分列出了用于决定是否可以在web扩展主机中加载扩展的规则。
Web 扩展主文件
网络扩展的主文件由browser
属性定义。该脚本在浏览器WebWorker环境中的网络扩展主机中运行。它受到浏览器工作沙箱的限制,与在Node.js运行时中运行的普通扩展相比,存在一些限制。
- 不支持导入或要求其他模块。
importScripts
也不可用。因此,代码必须打包到一个文件中。 - VS Code API 可以通过模式
require('vscode')
加载。这将有效,因为有一个用于require
的垫片,但这个垫片不能用于加载额外的扩展文件或额外的节点模块。它仅适用于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
。 - 为了访问网络资源,必须使用Fetch API。访问的资源需要支持跨域资源共享 (CORS)
- 无法创建子进程或运行可执行文件。但是,可以通过Worker API创建web workers。这用于运行语言服务器,如web扩展中的语言服务器协议部分所述。
- 与常规扩展一样,扩展的
activate/deactivate
函数需要通过模式exports.activate = ...
导出。
开发一个网页扩展
幸运的是,像TypeScript和webpack这样的工具可以隐藏许多浏览器运行时的限制,并允许你以与常规扩展相同的方式编写Web扩展。通常,Web扩展和常规扩展可以从相同的源代码生成。
例如,由yo code
generator创建的Hello Web Extension
仅在构建脚本上有所不同。您可以使用提供的启动配置来运行和调试生成的扩展,就像传统的Node.js扩展一样,这些配置可以通过调试:选择并开始调试命令访问。
创建一个网页扩展
要搭建一个新的网页扩展,请使用yo code
并选择新建网页扩展。确保安装了最新版本的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
包含在 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.org/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
字段包含为在浏览器中无法使用的节点库添加别名和回退的能力。- 如果您使用的是像
path
这样的库,您可以指定如何在 Web 编译环境中解析path
。例如,您可以指向项目中定义path
的文件,使用path: path.resolve(__dirname, 'src/my-path-implementation-for-web.js')
。或者您可以使用名为path-browserify
的 Browserify 节点打包版本库,并指定path: require.resolve('path-browserify')
。 - 有关 Node.js 核心模块 polyfills 的列表,请参阅 webpack resolve.fallback。
- 如果您使用的是像
plugins
部分使用 DefinePlugin 插件 来填充全局变量,例如 Node.js 的process
全局变量。
测试你的网页扩展
目前有三种方法可以在将Web扩展发布到市场之前对其进行测试。
- 使用在桌面上运行的VS Code,并带有
--extensionDevelopmentKind=web
选项,以在VS Code中运行的Web扩展主机中运行您的Web扩展。 - 使用 @vscode/test-web 节点模块打开一个包含 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 问题匹配器 扩展提供。
在启动的扩展开发主机实例中,Web扩展将在Web扩展主机中可用并运行。运行Hello World
命令以激活扩展。
打开正在运行的扩展视图(命令:开发者:显示正在运行的扩展)以查看哪些扩展在Web扩展主机中运行。
使用@vscode/test-web在浏览器中测试您的Web扩展
@vscode/test-web 节点模块提供了一个CLI和API来在浏览器中测试Web扩展。
节点模块提供了一个npm二进制文件vscode-test-web
,可以从命令行打开VS Code for the Web:
- 它将VS Code的网页部分下载到
.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选项:
Option | Argument Description |
---|---|
--browserType | The browser to launch: chromium (default), firefox or webkit |
--extensionDevelopmentPath | A path pointing to an extension under development to include. |
--extensionTestsPath | A path to a test module to run. |
--permission | Permission granted to the opened browser: e.g. clipboard-read , clipboard-write .See full list of options. Argument can be provided multiple times. |
--folder-uri | URI of the workspace to open VS Code on. Ignored when folderPath is provided |
--extensionPath | A path pointing to a folder containing additional extensions to include. Argument can be provided multiple times. |
folderPath | A local folder to open VS Code on. The folder content will be available as a virtual file system and opened as workspace. |
VS Code 的网页部分被下载到一个文件夹 .vscode-test-web
。您希望将其添加到您的 .gitignore
文件中。
在 vscode.dev 中测试你的网页扩展
在您发布扩展以供每个人在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
然后,从您的扩展路径中,通过运行 npx serve
启动一个HTTP服务器:
$ 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! │
│ │
└────────────────────────────────────────────────────┘
最后,打开vscode.dev,从命令面板运行开发者:从位置安装扩展...(⇧⌘P (Windows, Linux Ctrl+Shift+P)),粘贴上面的URL,例如https://localhost:5000
,然后选择安装。
检查日志
您可以在浏览器的开发者工具的控制台中检查日志,以查看来自您的扩展程序的任何错误、状态和日志。
你可能会看到来自vscode.dev本身的其他日志。此外,你无法轻松设置断点,也无法查看扩展的源代码。这些限制使得在vscode.dev中调试并不是最愉快的体验,因此我们建议在侧载到vscode.dev之前使用前两个选项进行测试。侧载是在发布扩展之前进行最终健全性检查的好方法。
Web扩展测试
支持Web扩展测试,并且可以类似于常规扩展测试来实现。请参阅测试扩展文章,了解扩展测试的基本结构。
@vscode/test-web 节点模块相当于 @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);
}
});
}
要从命令行运行网络测试,请将以下内容添加到您的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扩展与其他扩展一起托管在Marketplace上。
确保使用最新版本的vsce
来发布您的扩展。vsce
会标记所有作为Web扩展的扩展。为此,vsce
使用了Web扩展启用部分中列出的规则。
将现有扩展更新为Web扩展
无代码扩展
没有代码但只有贡献点(例如主题、片段和基本语言扩展)的扩展不需要任何修改。它们可以在Web扩展主机中运行,并且可以从扩展视图中安装。
重新发布不是必须的,但在发布扩展的新版本时,请确保使用最新版本的vsce
。
使用代码迁移扩展
带有源代码的扩展(由main
属性定义)需要提供一个web扩展主文件并在package.json
中设置browser
属性。
按照以下步骤为浏览器环境重新编译您的扩展代码:
- 添加一个webpack配置文件,如webpack配置部分所示。如果您已经为您的Node.js扩展代码有一个webpack文件,您可以为web添加一个新部分。查看vscode-css-formatter作为示例。
- 添加
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运行时都能工作的node模块。Node模块可以通过定义
browser
和main
入口点来实现这一点。Webpack会自动使用与其目标匹配的入口点。实现这一点的node模块示例包括request-light和@vscode/l10n。 - 要为节点模块或源文件提供替代实现,请使用 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节点模块提供了joinPath
、dirName
、baseName
、extName
、resolvePath
。 - 注意
fs
的使用。通过使用vscode的workspace.fs
来替换。
当您的扩展在网络上运行时,提供较少的功能是可以的。使用when子句上下文来控制哪些命令、视图和任务在网络的虚拟工作空间中可用或隐藏。
- 使用
virtualWorkspace
上下文变量来检查当前工作区是否是非文件系统工作区。 - 使用
resourceScheme
来检查当前资源是否为file
资源。 - 如果存在平台 shell,请使用
shellExecutionSupported
。 - 实现替代命令处理程序,显示对话框以解释为什么该命令不适用。
WebWorkers 可以作为分叉进程的替代方案。我们已经更新了几个语言服务器以作为 Web 扩展运行,包括内置的 JSON、CSS 和 HTML 语言服务器。下面的 语言服务器协议 部分提供了更多详细信息。
浏览器运行时环境仅支持执行JavaScript和WebAssembly。用其他编程语言编写的库需要进行交叉编译,例如有工具可以将C/C++和Rust编译为WebAssembly。例如,vscode-anycode扩展使用了tree-sitter,这是将C/C++代码编译为WebAssembly的示例。
Web扩展中的语言服务器协议
vscode-languageserver-node 是 语言服务器协议 (LSP) 的一个实现,它被用作语言服务器实现的基础,例如 JSON, CSS, 和 HTML。
自3.16.0版本以来,客户端和服务器现在也提供了浏览器实现。服务器可以在web worker中运行,连接基于webworkers的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: 'silent',
// 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}`);
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
标志,否则生成源映射。 - 从捆绑包中排除'vscode'模块(因为它由VS Code运行时提供)。
- 为
process
和buffer
创建polyfills。 - 使用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
是一个节点模块,它并行运行名称匹配给定前缀的脚本。对于我们来说,它运行 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);
}
});
}