Web 擴充功能
Visual Studio Code 可以在瀏覽器中作為編輯器執行。一個例子是 github.dev
使用者介面,當在 GitHub 中瀏覽存放庫或提取請求時,按下 .
(句點鍵)即可到達。當 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 擴充功能主機忽略,並且無法在「擴充功能」檢視中下載。
只有宣告式貢獻(只有 contributes
,沒有 main
或 browser
)的擴充功能可以是 Web 擴充功能。它們可以安裝並在 VS Code for the Web 中執行,而無需擴充功能作者進行任何修改。具有宣告式貢獻的擴充功能範例包括佈景主題、語法和程式碼片段。
擴充功能可以同時具有 browser
和 main
進入點,以便在瀏覽器和 Node.js 執行階段中執行。「將現有擴充功能更新為 Web 擴充功能」章節說明了如何遷移擴充功能以在兩個執行階段中運作。
「Web 擴充功能啟用」章節列出了用於決定是否可以在 Web 擴充功能主機中載入擴充功能的規則。
Web 擴充功能主要檔案
Web 擴充功能的主要檔案由 browser
屬性定義。腳本在 Web 擴充功能主機的 瀏覽器 WebWorker 環境中執行。它受到瀏覽器 Worker 沙箱的限制,並且與在 Node.js 執行階段中執行的一般擴充功能相比,具有限制。
- 不支援匯入或需要其他模組。
importScripts
也不可用。因此,程式碼必須封裝到單個檔案中。 - 可以透過模式
require('vscode')
載入 VS Code API。這將會運作,因為require
有一個墊片,但此墊片不能用於載入其他擴充功能檔案或其他 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
並選取「新增 Web 擴充功能」。請確保已安裝最新版本的 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.dev.org.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.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 核心模組 Polyfill 的清單。
- 如果您正在使用像
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 支援與一般 Node.js 擴充功能主機一起執行 Web 擴充功能主機。
使用「新增 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 問題比對器擴充功能提供。
在啟動的「擴充功能開發主機」執行個體中,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 位元下載到
.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 擴充功能
在您發佈擴充功能供所有人用於 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://127.0.0.1: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://127.0.0.1:5000
,然後選取 安裝。
檢查記錄
您可以檢查瀏覽器開發人員工具的控制台中的記錄,以查看來自您的擴充功能的任何錯誤、狀態和記錄。
您可能會看到來自 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('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) 桌面版中執行(和偵錯)擴充功能測試,請使用 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 檔案,則可以為 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
的用法。取代為使用 vscodeworkspace.fs
。
當您的擴充功能在 Web 中執行時,提供較少的功能是可以接受的。使用 when 條件子句上下文 來控制哪些命令、檢視和工作在 Web 上的虛擬工作區中可用或隱藏。
- 使用
virtualWorkspace
上下文變數來找出目前的工作區是否為非檔案系統工作區。 - 使用
resourceScheme
來檢查目前的資源是否為file
資源。 - 如果存在平台 Shell,則使用
shellExecutionSupported
。 - 實作替代命令處理常式,以顯示對話方塊來解釋為什麼該命令不適用。
WebWorker 可以用作進程分支的替代方案。我們已更新多個語言伺服器以作為 Web 擴充功能執行,包括內建的 JSON、CSS 和 HTML 語言伺服器。「語言伺服器協定」章節在下面提供了更多詳細資訊。
瀏覽器執行階段環境僅支援 JavaScript 和 WebAssembly 的執行。以其他程式語言編寫的程式庫需要交叉編譯,例如,有工具可以將 C/C++ 和 Rust 編譯為 WebAssembly。例如,vscode-anycode 擴充功能使用 tree-sitter,它是編譯為 WebAssembly 的 C/C++ 程式碼。
Web 擴充功能中的語言伺服器協定
vscode-languageserver-node 是 語言伺服器協定 (LSP) 的實作,它用作語言伺服器實作的基礎,例如 JSON、CSS 和 HTML。
自 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
建立填補程式 - 使用 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
是一個 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);
}
});
}