现已发布!阅读关于 12 月份的新功能和修复。

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.devgithub.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 扩展主机将忽略它们,并且它们在扩展视图中不可下载。

Extensions view

仅具有声明性贡献(只有 contributes,没有 mainbrowser)的扩展可以是 Web 扩展。它们可以在 VS Code for the Web 中安装和运行,而无需扩展作者进行任何修改。具有声明性贡献的扩展示例包括主题、语法和代码片段。

扩展可以同时具有 browsermain 入口点,以便在浏览器和 Node.js 运行时中运行。更新现有扩展以支持 Web 扩展部分展示了如何迁移扩展以同时支持这两种运行时。

Web 扩展启用部分列出了用于决定扩展是否可以在 Web 扩展主机中加载的规则。

Web 扩展主文件

Web 扩展的主文件由 browser 属性定义。脚本在 Web 扩展主机中以浏览器 WebWorker 环境运行。它受到浏览器 worker 沙盒的限制,与在 Node.js 运行时中运行的普通扩展相比,存在一些局限性。

  • 不支持导入或要求其他模块。importScripts 也不可用。因此,代码必须打包成单个文件。
  • 可以通过 require('vscode') 模式加载 VS Code API。这会奏效,因为存在一个 require 的 shim,但此 shim 不能用于加载其他扩展文件或其他节点模块。它仅与 require('vscode') 一起使用。
  • Node.js 全局变量和库(如 processossetImmediatepathutilurl)在运行时不可用。但是,可以使用 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 扩展中的语言服务器协议部分所述。
  • 与常规扩展一样,需要通过 exports.activate = ... 模式导出扩展的 activate/deactivate 函数。

开发 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-webwatch-webpackage-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 中的源代码与生成器创建的源代码类似:helloworld-web-sample

Webpack 配置

webpack 配置文件由 yo code 自动生成。它将扩展的源代码捆绑到一个 JavaScript 文件中,以便在 Web 扩展主机中加载。

稍后我们将解释如何使用 esbuild 作为捆绑器,但现在我们从 webpack 开始。

webpack.config.js

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.jsonmain 的文件开始。
    • 如果您不想打包测试,可以省略测试套件字段。
  • output 字段指示编译文件的位置。
    • [name] 将被 entry 中使用的键替换。因此,在生成的配置文件中,它将生成 dist/web/extension.jsdist/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')
    • 请参阅 webpack resolve.fallback 以获取 Node.js 核心模块 polyfill 列表。
  • plugins 部分使用 DefinePlugin 插件来 polyfill 诸如 process Node.js 全局变量之类的全局变量。

测试您的 Web 扩展

目前有三种方法可以在发布扩展到 Marketplace 之前对其进行测试。

  • 使用在桌面版 VS Code 上运行的 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 扩展主机。

使用 **新建 Web 扩展** 生成器提供的 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 扩展提供。

在启动的 **扩展开发主机** 实例中,Web 扩展将在 Web 扩展主机中可用并运行。运行 Hello World 命令以激活扩展。

打开 **正在运行的扩展** 视图(命令: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(默认)、firefoxwebkit
--extensionDevelopmentPath 指向要包含的开发中扩展的路径。
--extensionTestsPath 指向要运行的测试模块的路径。
--permission 授予打开的浏览器的权限:例如 clipboard-readclipboard-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.pemlocalhost-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://: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 上运行扩展测试。

该实用程序执行以下步骤:

  1. 从本地 Web 服务器启动 VS Code for the Web 编辑器。
  2. 打开指定的浏览器。
  3. 运行提供的测试运行器脚本。

您可以运行连续构建中的测试,以确保扩展在所有浏览器上都能正常工作。

测试运行器脚本在 Web 扩展主机上运行,并具有与Web 扩展主文件相同的限制。

  • 所有文件都捆绑到一个文件中。它应该包含测试运行器(例如 Mocha)和所有测试(通常是 *.test.ts)。
  • 仅支持 require('vscode')

yo code Web 扩展生成器创建的webpack 配置有一个用于测试的部分。它期望测试运行器脚本位于 ./src/web/test/suite/index.ts。提供的测试运行器脚本使用 Web 版 Mocha,并包含 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) 桌面版中运行(和调试)扩展测试,请使用“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 作为示例。
  • 添加 launch.jsontasks.json 文件,如测试您的 Web 扩展部分所示。
  • 在 webpack 配置文件中,将输入文件设置为现有的 Node.js 主文件,或为 Web 扩展创建新的主文件。
  • package.json 中,添加 browserscripts 属性,如 Web 扩展的结构部分所示。
  • 运行 npm run compile-web 来调用 webpack,并查看需要进行哪些工作才能使您的扩展在 Web 中运行。

为了尽可能多地重用源代码,以下是一些技术:

  • 要为 Node.js 核心模块(如 path)提供 polyfill,请在 resolve.fallback 中添加一个条目。
  • 要提供 Node.js 全局变量(如 process),请使用 DefinePlugin 插件
  • 使用可以在浏览器和 Node.js 运行时中运行的 node 模块。Node 模块可以通过定义 browsermain 入口点来实现这一点。Webpack 将自动使用匹配其目标的一个。执行此操作的 node 模块示例包括 request-light@vscode/l10n
  • 要为 node 模块或源文件提供替代实现,请使用 resolve.alias
  • 将代码分离到浏览器部分、Node.js 部分和公共部分。在公共部分,仅使用在浏览器和 Node.js 运行时中都可运行的代码。为 Node.js 和浏览器中具有不同实现的功能创建抽象。
  • 留意对 pathURI.filecontext.extensionPathrootPathuri.fsPath 的使用。这些在 VS Code for the Web 中不适用于虚拟工作区(非文件系统)。而是使用带有 URI.parsecontext.extensionUri 的 URI。vscode-uri node 模块提供了 joinPathdirNamebaseNameextNameresolvePath
  • 留意对 fs 的使用。将其替换为使用 vscode workspace.fs

在 Web 中运行扩展时,提供较少的功能是可以接受的。使用when 子句上下文来控制哪些命令、视图和任务在 Web 上的虚拟工作区中运行时可用或隐藏。

  • 使用 virtualWorkspace 上下文变量来确定当前工作区是否为非文件系统工作区。
  • 使用 resourceScheme 来检查当前资源是否为 file 资源。
  • 如果存在平台 shell,请使用 shellExecutionSupported
  • 实现替代命令处理程序,显示对话框解释为什么命令不适用。

WebWorker 可用作分叉进程的替代方案。我们已更新了多个语言服务器以作为 Web 扩展运行,包括内置的JSONCSSHTML 语言服务器。语言服务器协议部分将提供更多详细信息。

浏览器运行时环境仅支持 JavaScript 和WebAssembly 的执行。用其他编程语言编写的库需要进行交叉编译,例如,有工具可以将 C/C++Rust 编译为 WebAssembly。例如,vscode-anycode 扩展使用tree-sitter,它是编译为 WebAssembly 的 C/C++ 代码。

Web 扩展中的语言服务器协议

vscode-languageserver-node语言服务器协议 (LSP) 的一种实现,用作诸如JSONCSSHTML 等语言服务器实现的基石。

从 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 入口点,也没有以下任何贡献点:localizationsdebuggersterminaltypescriptServerPlugins

如果扩展希望提供也适用于 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 标志,否则生成源映射。
    • 从捆绑包中排除 'vscode' 模块(因为它由 VS Code 运行时提供)。
    • processbuffer 创建 polyfill。
    • 使用 esbuildProblemMatcherPlugin 插件报告导致捆绑器无法完成的错误。此插件以 esbuild 问题匹配器可以检测到的格式发出错误,而 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:esbuildwatch-web:tsc 脚本。您需要将 npm-run-all 添加到 package.jsondevDependencies 部分。

以下 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);
    }
  });
}

示例

© . This site is unofficial and not affiliated with Microsoft.