Web 延伸模組

Visual Studio Code 可以在瀏覽器中作為編輯器執行。一個例子是當您在 GitHub 上瀏覽儲存庫 (repository) 或 Pull Request 時,按下 . (句號鍵) 所開啟的 github.dev 使用者介面。當 VS Code 在網頁環境中使用時,已安裝的擴充功能會在瀏覽器中的擴充功能主機內執行,稱為「網頁擴充功能主機」(web extension host)。能夠在網頁擴充功能主機中執行的擴充功能稱為「網頁擴充功能」(web extension)。

網頁擴充功能與一般擴充功能具有相同的結構,但由於執行環境不同,它們無法執行與針對 Node.js 執行環境編寫的擴充功能相同的程式碼。網頁擴充功能仍然可以存取完整的 VS Code API,但無法存取 Node.js API 和模組載入功能。相反地,網頁擴充功能受到瀏覽器沙盒的限制,因此與一般擴充功能相比有一些限制

網頁擴充功能執行環境在 VS Code 桌面版上也受到支援。如果您決定將您的擴充功能建立為網頁擴充功能,它將在 VS Code for the Web(包括 vscode.devgithub.dev)以及桌面版和 GitHub Codespaces 等服務上獲得支援。

網頁擴充功能結構

網頁擴充功能的結構與一般擴充功能相同。擴充功能資訊清單 (package.json) 定義了擴充功能原始碼的進入點檔案並宣告擴充功能貢獻 (contributions)。

對於網頁擴充功能,主進入點檔案是由 browser 屬性定義的,而不是像一般擴充功能那樣由 main 屬性定義。

contributes 屬性在網頁擴充功能和一般擴充功能中的運作方式相同。

以下範例展示了一個簡單的 Hello World 擴充功能的 package.json,它僅在網頁擴充功能主機中執行 (它只有一個 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 進入點的擴充功能不是網頁擴充功能。它們會被網頁擴充功能主機忽略,且無法在「擴充功能」檢視中下載。

Extensions view

僅具有宣告式貢獻 (只有 contributes,沒有 mainbrowser) 的擴充功能可以是網頁擴充功能。它們無需擴充功能作者進行任何修改,即可在 VS Code for the Web 中安裝並執行。具有宣告式貢獻的擴充功能範例包括佈景主題、語法和程式碼片段 (snippets)。

擴充功能可以同時擁有 browsermain 進入點,以便在瀏覽器和 Node.js 執行環境中執行。將現有擴充功能更新為網頁擴充功能一節展示了如何遷移擴充功能以使其在兩種執行環境中皆能運作。

網頁擴充功能啟用一節列出了用於決定擴充功能是否能在網頁擴充功能主機中載入的規則。

網頁擴充功能主檔案

網頁擴充功能的主檔案由 browser 屬性定義。該腳本會在網頁擴充功能主機中的 Browser WebWorker 環境中執行。它受到瀏覽器 Worker 沙盒的限制,與在 Node.js 執行環境中執行的一般擴充功能相比有一定限制。

  • 不支援匯入 (import) 或需求 (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 存取。
  • 若要存取網頁資源,必須使用 Fetch API。存取的資源需要支援 跨來源資源共享 (CORS)
  • 無法建立子處理序或執行可執行檔。不過,可以透過 Worker API 建立 Web Worker。這用於執行語言伺服器,詳情請參閱 網頁擴充功能中的語言伺服器協定一節。
  • 與一般擴充功能相同,擴充功能的 activate/deactivate 函式需要透過 exports.activate = ... 模式匯出。

開發網頁擴充功能

幸運的是,TypeScript 和 webpack 等工具可以隱藏許多瀏覽器執行環境的限制,讓您能以與一般擴充功能相同的方式編寫網頁擴充功能。網頁擴充功能和一般擴充功能通常可以由相同的原始碼產生。

例如,由 yo code 產生器建立的 Hello Web Extension 僅在建置腳本上有所不同。您可以透過使用提供的啟動設定 (可透過 Debug: Select and Start Debugging 命令存取) 來執行和偵錯產生的擴充功能,就像傳統的 Node.js 擴充功能一樣。

建立網頁擴充功能

若要建立新的網頁擴充功能,請使用 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 包含啟動設定,可在 VS Code 桌面版中以網頁擴充功能主機執行網頁擴充功能和測試 (不再需要設定 extensions.webWorker)。
  • .vscode/task.json 包含啟動設定所使用的建置工作。它使用 npm run watch-web 並依賴 webpack 特定的 ts-webpack-watch 問題比對器 (problem matcher)。
  • .vscode/extensions.json 包含提供問題比對器的擴充功能。必須安裝這些擴充功能,啟動設定才能運作。
  • tsconfig.json 定義了符合 webworker 執行環境的編譯選項。

helloworld-web-sample 中的原始碼與產生器所建立的內容類似。

Webpack 設定

webpack 設定檔由 yo code 自動產生。它將擴充功能的原始碼打包成一個單一的 JavaScript 檔案,以便載入到網頁擴充功能主機中。

稍後我們將解釋如何使用 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.nodejs.com.tw/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 檔案將執行的環境類型。對於網頁擴充功能,您應該將其設為 webworker
  • resolve 欄位包含為無法在瀏覽器中運作的 node 函式庫新增別名 (aliases) 和備援 (fallbacks) 的功能。
    • 如果您使用的是像 path 這樣的函式庫,您可以指定如何在網頁編譯環境中解析 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 外掛程式來為 Node.js 的全域變數 (如 process) 提供 polyfill。

測試您的網頁擴充功能

目前在將網頁擴充功能發佈到 Marketplace 之前,有三種測試方式。

  • 在桌面版 VS Code 上使用 --extensionDevelopmentKind=web 選項,在 VS Code 內部執行的網頁擴充功能主機中執行您的網頁擴充功能。
  • 使用 @vscode/test-web node 模組開啟一個包含 VS Code for the Web 的瀏覽器,其中包含由本機伺服器提供服務的您的擴充功能。
  • 將您的擴充功能 側載 (Sideload)vscode.dev,以在實際環境中檢視您的擴充功能。

在桌面版 VS Code 上測試您的網頁擴充功能

為了使用現有的 VS Code 擴充功能開發體驗,桌面版 VS Code 支援在常規 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 執行個體中,網頁擴充功能將可用並在網頁擴充功能主機中執行。執行 Hello World 命令以啟用該擴充功能。

開啟 Running Extensions 檢視 (命令:Developer: Show Running Extensions) 以查看哪些擴充功能正在網頁擴充功能主機中執行。

使用 @vscode/test-web 在瀏覽器中測試您的網頁擴充功能

@vscode/test-web node 模組提供了 CLI 和 API,用於在瀏覽器中測試網頁擴充功能。

該 node 模組提供了一個 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 作為開發相依性 (dev dependency) 新增到您的擴充功能中,並在指令碼中呼叫它。

  "devDependencies": {
    "@vscode/test-web": "*"
  },
  "scripts": {
    "open-in-browser": "vscode-test-web --extensionDevelopmentPath=. ."
  }

查看 @vscode/test-web README 以獲取更多 CLI 選項。

選項 引數 說明
--browserType 要啟動的瀏覽器:chromium (預設), firefoxwebkit
--extensionDevelopmentPath 指向開發中擴充功能的路徑。
--extensionTestsPath 要執行的測試模組路徑。
--permission 授予開啟的瀏覽器的權限:例如 clipboard-read, clipboard-write
請參閱 選項完整清單。引數可以多次提供。
--folder-uri 要開啟 VS Code 的工作區 URI。如果提供了 folderPath,則此項會被忽略。
--extensionPath 指向包含要包含的額外擴充功能的資料夾路徑。
引數可以多次提供。
folderPath 要開啟 VS Code 的本機資料夾。
資料夾內容將作為虛擬檔案系統提供,並作為工作區開啟。

VS Code 的網頁版檔案會被下載到 .vscode-test-web 資料夾中。建議將其加入到您的 .gitignore 檔案中。

在 vscode.dev 中測試您的網頁擴充功能

在向所有人發佈您的擴充功能以在 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,從命令面板執行 Developer: Install Extension From Location... (⇧⌘P (Windows, Linux Ctrl+Shift+P)),貼上上述 URL (例如 https://:5000),然後選擇 Install

檢查記錄

您可以檢查瀏覽器開發人員工具主控台中的記錄,以查看來自您擴充功能的任何錯誤、狀態和訊息。

您可能會看到來自 vscode.dev 本身的記錄。此外,您無法輕易設定中斷點或查看擴充功能的原始碼。這些限制使得在 vscode.dev 中進行偵錯不是最理想的體驗,因此我們建議在側載到 vscode.dev 之前,使用前兩種選項進行測試。側載是在發佈擴充功能之前進行最後一次健全性檢查的好方法。

網頁擴充功能測試

網頁擴充功能測試受到支援,並且可以以類似於一般擴充功能測試的方式實作。請參閱 測試擴充功能文章,以了解擴充功能測試的基本結構。

@vscode/test-web node 模組相當於 @vscode/test-electron (先前稱為 vscode-test)。它允許您在命令列上針對 Chromium, Firefox 和 Safari 執行擴充功能測試。

該工具執行以下步驟:

  1. 從本機網頁伺服器啟動 VS Code for the Web 編輯器。
  2. 開啟指定的瀏覽器。
  3. 執行提供的測試執行器 (test runner) 腳本。

您可以在持續整合 (CI) 建置中執行這些測試,以確保擴充功能在所有瀏覽器上都能運作。

測試執行器腳本是在網頁擴充功能主機中執行的,且具有與 網頁擴充功能主檔案相同的限制。

  • 所有檔案都被打包成單一檔案。它應包含測試執行器 (例如 Mocha) 和所有測試 (通常為 *.test.ts)。
  • 僅支援 require('vscode')

yo code 網頁擴充功能產生器建立的 webpack 設定中有一節專門針對測試。它預期測試執行器腳本位於 ./src/web/test/suite/index.ts。所提供的 測試執行器腳本使用 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);
    }
  });
}

若要從命令列執行網頁測試,請將以下內容新增至您的 package.json,並使用 npm test 執行它。

  "devDependencies": {
    "@vscode/test-web": "*"
  },
  "scripts": {
    "test": "vscode-test-web --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js"
  }

若要開啟包含測試資料的工作區資料夾,請將本機資料夾路徑 (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"
    }
  ]
}

發佈網頁擴充功能

網頁擴充功能與其他擴充功能一樣託管在 Marketplace 上。

請確保使用最新版本的 vsce 來發佈您的擴充功能。vsce 會標記所有網頁擴充功能。為此,vsce 會使用 網頁擴充功能啟用一節中列出的規則。

將現有擴充功能更新為網頁擴充功能

無程式碼的擴充功能

沒有程式碼、只有貢獻點的擴充功能 (例如佈景主題、程式碼片段和基本的語言擴充功能) 不需要任何修改。它們可以在網頁擴充功能主機中執行,並可從「擴充功能」檢視中安裝。

重新發佈不是必要的,但在發佈新版本的擴充功能時,請確保使用最新版本的 vsce

遷移包含程式碼的擴充功能

具有原始碼 (由 main 屬性定義) 的擴充功能需要提供 網頁擴充功能主檔案,並在 package.json 中設定 browser 屬性。

請依照以下步驟重新編譯您的擴充功能程式碼以適應瀏覽器環境:

  • 新增 webpack 設定檔,如 webpack 設定一節所示。如果您已經有 Node.js 擴充功能程式碼的 webpack 檔案,可以新增一個網頁專用的區段。請參考 vscode-css-formatter 作為範例。
  • 依照 測試您的網頁擴充功能一節所示,新增 launch.jsontasks.json 檔案。
  • 在 webpack 設定檔中,將輸入檔案設定為現有的 Node.js 主檔案,或為網頁擴充功能建立一個新的主檔案。
  • package.json 中,新增 browserscripts 屬性,如 網頁擴充功能結構一節所示。
  • 執行 npm run compile-web 來呼叫 webpack,並查看還需要做哪些工作才能讓您的擴充功能在網路上執行。

為了確保盡可能多地重複使用原始碼,以下是一些技巧:

  • 若要為 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 和瀏覽器中具有不同實作的功能建立抽象。
  • 注意對 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

當您的擴充功能在網頁上執行時,提供較少的功能是可以接受的。請使用 when 子句內容 (when clause contexts) 來控制在網頁上虛擬工作區中執行時,哪些命令、檢視和工作是可用的或隱藏的。

  • 使用 virtualWorkspace 內容變數來找出目前工作區是否為非檔案系統工作區。
  • 使用 resourceScheme 來檢查目前資源是否為 file 資源。
  • 如果存在平台 shell,請使用 shellExecutionSupported
  • 實作替代命令處理器,顯示對話方塊以說明為什麼該命令不適用。

WebWorker 可以用作分叉處理序 (forking processes) 的替代方案。我們已經更新了多個語言伺服器以作為網頁擴充功能執行,包括內建的 JSON, CSSHTML 語言伺服器。下方的 語言伺服器協定一節提供了更多詳細資料。

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

網頁擴充功能中的語言伺服器協定

vscode-languageserver-node語言伺服器協定 (LSP) 的實作,用作 JSON, CSSHTML 等語言伺服器實作的基礎。

自 3.16.0 版起,客戶端和伺服器現在也提供了瀏覽器實作。伺服器可以在 Web Worker 中執行,連接基於 Web Worker 的 postMessage 協定。

瀏覽器的客戶端可在 'vscode-languageclient/browser' 找到。

import { LanguageClient } from `vscode-languageclient/browser`;

伺服器可在 vscode-languageserver/browser 找到。

lsp-web-extension-sample 展示了其運作方式。

網頁擴充功能啟用

如果滿足以下條件,VS Code 會自動將擴充功能視為網頁擴充功能:

  • 擴充功能資訊清單 (package.json) 具有 browser 進入點。
  • 擴充功能資訊清單沒有 main 進入點,且沒有以下任何貢獻點:localizations, debuggers, terminal, typescriptServerPlugins

如果擴充功能想要提供也能在網頁擴充功能主機中運作的偵錯程式或終端機,則需要定義 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 maps)。
    • 從打包中排除 'vscode' 模組 (因為它是由 VS Code 執行環境提供的)。
    • processbuffer 建立 polyfill。
    • 使用 esbuildProblemMatcherPlugin 外掛程式來報告導致打包工具無法完成的錯誤。此外掛程式以 esbuild 問題比對器可偵測的格式發出錯誤,該比對器也需要作為擴充功能安裝。
    • 使用 testBundlePlugin 來實作一個參考所有測試檔案和 mocha 測試執行器 mochaTestRunner.js 的測試主檔案 (extensionTests.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.