現已釋出!閱讀關於 11 月新增功能和修復的內容。

Web 擴充套件

Visual Studio Code 可以在瀏覽器中作為編輯器執行。一個例子是 `github.dev` 使用者介面,透過在 GitHub 的倉庫或 Pull Request 中按 `.`(句點鍵)來訪問。當 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"
  }
}

注意:如果您的擴充套件目標是 1.74 之前的 VS Code 版本,則必須在 `activationEvents` 中明確列出 `onCommand:helloworld-web-sample.helloWorld`。

僅具有 `main` 入口點但沒有 `browser` 的擴充套件不是 Web 擴充套件。Web 擴充套件宿主會忽略它們,並且在擴充套件檢視中不可下載。

Extensions view

僅具有宣告性貢獻的擴充套件(只有 `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 執行環境中執行的普通擴充套件相比存在限制。

  • 不支援匯入或要求其他模組。`importScripts` 也不可用。因此,程式碼必須打包成單個檔案。
  • 可以透過 `require('vscode')` 模式載入 VS Code API。這將起作用,因為有一個 `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 擴充套件中的語言伺服器協議部分所述。
  • 與常規擴充套件一樣,需要透過 `exports.activate = ...` 模式匯出擴充套件的 `activate/deactivate` 函式。

開發 Web 擴充套件

幸運的是,TypeScript 和 webpack 等工具可以隱藏許多瀏覽器執行環境的限制,並允許您像編寫常規擴充套件一樣編寫 Web 擴充套件。Web 擴充套件和常規擴充套件通常都可以從相同的原始碼生成。

例如,由 `yo code` 生成器建立的 `Hello Web Extension` 僅在構建指令碼上有所不同。您可以使用Debug: Select and Start Debugging 命令訪問的提供的啟動配置,像傳統 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` 包含在 VS Code 桌面版中執行 Web 擴充套件和測試的啟動配置,該配置使用 Web 擴充套件宿主(設定 `extensions.webWorker` 不再需要)。
  • `.vscode/task.json` 包含啟動配置使用的構建任務。它使用 `npm run watch-web` 並依賴於 webpack 特定的 `ts-webpack-watch` problem matcher。
  • `.vscode/extensions.json` 包含提供 problem matcher 的擴充套件。這些擴充套件需要安裝才能使啟動配置正常工作。
  • `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.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 欄位包含新增別名和回退以處理在瀏覽器中無法使用的 node 庫的功能。
    • 如果您使用 `path` 等庫,可以指定如何在 Web 編譯上下文中解析 `path`。例如,您可以指向一個定義 `path` 的檔案,使用 `path: path.resolve(__dirname, 'src/my-path-implementation-for-web.js')`。或者,您可以使用名為 `path-browserify` 的 Browserify node 打包版本,並指定 `path: require.resolve('path-browserify')`。
    • 請參閱 webpack resolve.fallback 獲取 Node.js 核心模組 polyfills 列表。
  • plugins 部分使用 DefinePlugin 外掛來 polyfill `process` Node.js 全域性變數等。

測試您的 Web 擴充套件

在釋出到 Marketplace 之前,目前有三種方法可以測試 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 支援執行一個 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 是一個 problem matcher,可以解析 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 擴充套件

在您將擴充套件釋出到 Marketplace 供所有人使用之前,您可以驗證您的擴充套件在實際 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://: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 擴充套件測試得到支援,並且可以像常規擴充套件測試一樣實現。請參閱 Testing Extensions 文章以瞭解擴充套件測試的基本結構。

@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 config 有一個測試部分。它期望在 `./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) 桌面版中執行(和除錯)擴充套件測試,請使用 `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 檔案,則可以為其新增一個新部分。請參考 vscode-css-formatter 作為示例。
  • 新增 `launch.json` 和 `tasks.json` 檔案,如 測試您的 Web 擴充套件部分所示。
  • 在 webpack 配置檔案中,將輸入檔案設定為現有的 Node.js 主檔案,或為 Web 擴充套件建立一個新的主檔案。
  • 在 `package.json` 中,新增 `browser` 和 `scripts` 屬性,如 Web 擴充套件結構部分所示。
  • 執行 `npm run compile-web` 來呼叫 webpack,並檢視需要工作才能使您的擴充套件在 Web 中執行的地方。

為了儘可能多地重用原始碼,這裡有一些技術

  • 要為 `path` 等 Node.js 核心模組提供 polyfill,請向 resolve.fallback 新增一個條目。
  • 要提供 `process` 等 Node.js 全域性變數,請使用 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` 的用法。替換為使用 vscode `workspace.fs`。

在 Web 中執行擴充套件時,提供較少的功能是可以的。使用 when clause contexts 來控制哪些命令、檢視和任務可用或在 Web 上的虛擬工作區中執行時隱藏。

  • 使用 `virtualWorkspace` 上下文變數來查詢當前工作區是否為非檔案系統工作區。
  • 使用 `resourceScheme` 來檢查當前資源是否為 `file` 資源。
  • 如果存在平臺 shell,請使用 `shellExecutionSupported`。
  • 實現備用命令處理程式,顯示一個對話方塊來解釋為什麼該命令不適用。

WebWorker 可用作派生程序的替代方案。我們已將幾個語言伺服器更新為作為 Web 擴充套件執行,包括內建的 JSONCSSHTML 語言伺服器。下面的 Language Server Protocol 部分提供了更多詳細資訊。

瀏覽器執行時環境僅支援 JavaScript 和 WebAssembly 的執行。用其他程式語言編寫的庫需要交叉編譯,例如,有工具可以 C/C++Rust 編譯到 WebAssembly。例如,vscode-anycode 擴充套件使用 tree-sitter,它是編譯到 WebAssembly 的 C/C++ 程式碼。

Web 擴充套件中的語言伺服器協議

vscode-languageserver-nodeLanguage Server Protocol (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` 入口點,也沒有以下貢獻點:`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` 標誌,否則生成源對映。
    • 從包中排除 'vscode' 模組(因為它由 VS Code 執行時提供)。
    • 為 `process` 和 `buffer` 建立 polyfills
    • 使用 esbuildProblemMatcherPlugin 外掛來報告導致打包器無法完成的錯誤。此外掛以 `esbuild` problem matcher 可以檢測的格式發出錯誤,該 problem matcher 也需要作為擴充套件安裝。
    • 使用 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);
    }
  });
}

示例

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