捆綁擴充套件
打包 Visual Studio Code 擴充套件的首要原因是確保該擴充套件適用於在任何平臺上使用 VS Code 的所有使用者。只有打包後的擴展才能在 VS Code for Web 環境中使用,例如 github.dev 和 vscode.dev。當 VS Code 在瀏覽器中執行時,它一次只能載入擴充套件的一個檔案,因此擴充套件程式碼需要打包成一個單一的、適合 Web 環境的 JavaScript 檔案。這同樣適用於 Notebook 輸出渲染器,在這種情況下,VS Code 也只會載入渲染器擴充套件的一個檔案。
此外,擴充套件可能會迅速增長其大小和複雜性。它們可能由多個原始檔編寫,並依賴於 npm 上的模組。分解和重用是開發中的最佳實踐,但它們在安裝和執行擴充套件時會帶來額外的開銷。載入 100 個小檔案比載入一個大檔案要慢得多。這就是我們推薦打包的原因。打包是將多個小原始檔合併成一個檔案的過程。
對於 JavaScript,有多種打包器可供選擇。流行的有 rollup.js、Parcel、esbuild 和 webpack。
使用 esbuild
esbuild 是一個快速且易於配置的 JavaScript 打包器。要安裝 esbuild,請開啟終端並輸入
npm i --save-dev esbuild
執行 esbuild
你可以從命令列執行 esbuild,但為了減少重複並啟用問題報告,使用構建指令碼 esbuild.js 會很有幫助。
const esbuild = require('esbuild');
const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');
async function main() {
const ctx = await esbuild.context({
entryPoints: ['src/extension.ts'],
bundle: true,
format: 'cjs',
minify: production,
sourcemap: !production,
sourcesContent: false,
platform: 'node',
outfile: 'dist/extension.js',
external: ['vscode'],
logLevel: 'warning',
plugins: [
/* add to the end of plugins array */
esbuildProblemMatcherPlugin
]
});
if (watch) {
await ctx.watch();
} else {
await ctx.rebuild();
await ctx.dispose();
}
}
/**
* @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/extension.ts中的程式碼打包成一個檔案dist/extension.js。 - 如果傳遞了
--production標誌,則壓縮程式碼。 - 除非傳遞了
--production標誌,否則生成源對映。 - 從包中排除 'vscode' 模組(因為它由 VS Code 執行時提供)。
- 將
- 使用 esbuildProblemMatcherPlugin 外掛來報告阻止打包器完成的錯誤。此外掛以打包器可以識別的格式發出錯誤。esbuild problem matcher 也需要作為擴充套件安裝。
- 如果傳遞了
--watch標誌,它將開始監視原始檔的更改,並在檢測到更改時重新構建包。
esbuild 可以直接處理 TypeScript 檔案。但是,esbuild 僅移除所有型別宣告而不進行任何型別檢查。只有語法錯誤會被報告,並可能導致 esbuild 失敗。
因此,我們單獨執行 TypeScript 編譯器(tsc)來檢查型別,但不輸出任何程式碼(使用 --noEmit 標誌)。
package.json 中的 scripts 部分現在如下所示
"scripts": {
"compile": "npm run check-types && node esbuild.js",
"check-types": "tsc --noEmit",
"watch": "npm-run-all -p watch:*",
"watch:esbuild": "node esbuild.js --watch",
"watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
"vscode:prepublish": "npm run package",
"package": "npm run check-types && node esbuild.js --production"
}
npm-run-all 是一個 Node 模組,它會並行執行名稱與給定字首匹配的指令碼。對我們而言,它執行 watch:esbuild 和 watch:tsc 指令碼。您需要將 npm-run-all 新增到 package.json 的 devDependencies 部分。
compile 和 watch 指令碼用於開發,它們會生成帶有源對映的包檔案。package 指令碼由 vscode:prepublish 指令碼使用,該指令碼由 VS Code 的打包和釋出工具 vsce 使用,並在釋出擴充套件之前執行。向 esbuild 指令碼傳遞 --production 標誌將導致它壓縮程式碼並建立一個小型包,但這也會使除錯變得困難,因此在開發過程中使用了其他標誌。要執行上述指令碼,請開啟終端並鍵入 npm run watch,或從命令面板中選擇 **Tasks: Run Task**(⇧⌘P (Windows、Linux Ctrl+Shift+P))。
如果您按以下方式配置 .vscode/tasks.json,您將為每個監視任務獲得一個單獨的終端。
{
"version": "2.0.0",
"tasks": [
{
"label": "watch",
"dependsOn": ["npm: watch:tsc", "npm: watch:esbuild"],
"presentation": {
"reveal": "never"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"type": "npm",
"script": "watch:esbuild",
"group": "build",
"problemMatcher": "$esbuild-watch",
"isBackground": true,
"label": "npm: watch:esbuild",
"presentation": {
"group": "watch",
"reveal": "never"
}
},
{
"type": "npm",
"script": "watch:tsc",
"group": "build",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"label": "npm: watch:tsc",
"presentation": {
"group": "watch",
"reveal": "never"
}
}
]
}
此監視任務依賴於擴充套件 connor4312.esbuild-problem-matchers 進行問題匹配,您需要安裝此擴展才能在問題檢視中報告問題。必須安裝此擴展才能使啟動完成。
為了不忘記這一點,請在工作區中新增一個 .vscode/extensions.json 檔案。
{
"recommendations": ["connor4312.esbuild-problem-matchers"]
}
最後,您需要更新您的 .vscodeignore 檔案,以便將編譯後的檔案包含在釋出的擴充套件中。有關更多詳細資訊,請參閱 釋出 部分。
跳轉到 測試 部分繼續閱讀。
使用 webpack
Webpack 是一個開發工具,可從 npm 獲取。要安裝 webpack 及其命令列介面,請開啟終端並輸入
npm i --save-dev webpack webpack-cli
這將安裝 webpack 並更新您的擴充套件的 package.json 檔案,將 webpack 新增到 devDependencies 中。
Webpack 是一個 JavaScript 打包器,但許多 VS Code 擴充套件是用 TypeScript 編寫的,然後才編譯成 JavaScript。如果您的擴充套件使用 TypeScript,您可以使用 ts-loader,以便 webpack 可以理解 TypeScript。使用以下命令安裝 ts-loader
npm i --save-dev ts-loader
所有檔案都可以在 webpack-extension 示例中找到。
配置 webpack
安裝所有工具後,就可以配置 webpack 了。按照約定,webpack.config.js 檔案包含用於指示 webpack 打包擴充套件的配置。下面的示例配置適用於 VS Code 擴充套件,並應提供一個良好的起點。
//@ts-check
'use strict';
const path = require('path');
const webpack = require('webpack');
/**@type {import('webpack').Configuration}*/
const config = {
target: 'webworker', // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.js.org/configuration/target/#target
entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
output: {
// the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
path: path.resolve(__dirname, 'dist'),
filename: 'extension.js',
libraryTarget: 'commonjs2',
devtoolModuleFilenameTemplate: '../[resource-path]'
},
devtool: 'source-map',
externals: {
vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/
},
resolve: {
// support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules
extensions: ['.ts', '.js'],
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.
}
},
module: {
rules: [
{
test: /\.ts$/,
exclude: /node_modules/,
use: [
{
loader: 'ts-loader'
}
]
}
]
}
};
module.exports = config;
該檔案 可用 在 webpack-extension 示例中。Webpack 配置檔案是普通的 JavaScript 模組,必須匯出配置物件。
在上面的示例中,定義了以下內容
target指示您的擴充套件將在哪個環境中執行。我們建議使用webworker,以便您的擴充套件既能在 VS Code for web 中執行,也能在 VS Code 桌面版本中執行。- webpack 應使用的入口點。這類似於
package.json中的main屬性,不同之處在於您為 webpack 提供一個“源”入口點,通常是src/extension.ts,而不是“輸出”入口點。webpack 打包器可以理解 TypeScript,因此不需要單獨的 TypeScript 編譯步驟。 output配置告訴 webpack 在哪裡放置生成的包檔案。按照約定,通常是dist資料夾。在此示例中,webpack 將生成一個dist/extension.js檔案。resolve和module/rules配置用於支援 TypeScript 和 JavaScript 輸入檔案。externals配置用於宣告排除項,例如不應包含在包中的檔案和模組。vscode模組不應被打包,因為它不在磁碟上,而是在需要時由 VS Code 動態建立。根據擴充套件使用的 node 模組,可能需要更多的排除項。
最後,您需要更新您的 .vscodeignore 檔案,以便將編譯後的檔案包含在釋出的擴充套件中。有關更多詳細資訊,請參閱 釋出 部分。
執行 webpack
建立 webpack.config.js 檔案後,就可以呼叫 webpack 了。您可以從命令列執行 webpack,但為了減少重複,使用 npm 指令碼會很有幫助。
將這些條目合併到 package.json 的 scripts 部分
"scripts": {
"compile": "webpack --mode development",
"watch": "webpack --mode development --watch",
"vscode:prepublish": "npm run package",
"package": "webpack --mode production --devtool hidden-source-map",
},
compile 和 watch 指令碼用於開發,它們會生成包檔案。vscode:prepublish 由 VS Code 的打包和釋出工具 vsce 使用,並在釋出擴充套件之前執行。區別在於 模式,它控制最佳化級別。使用 production 會生成最小的包,但耗時也更長,因此否則使用 development。要執行上述指令碼,請開啟終端並鍵入 npm run compile,或從命令面板中選擇 **Tasks: Run Task**(⇧⌘P (Windows、Linux Ctrl+Shift+P))。
執行擴充套件
在執行擴充套件之前,package.json 中的 main 屬性必須指向包,對於上述配置,它指向 "./dist/extension"。完成此更改後,就可以執行和測試擴充套件了。
測試
擴充套件作者通常會為他們的擴充套件原始碼編寫單元測試。透過正確的架構分層,其中擴充套件原始碼不依賴於測試,webpack 和 esbuild 生成的包不應包含任何測試程式碼。要執行單元測試,只需進行簡單的編譯即可。
將這些條目合併到 package.json 的 scripts 部分
"scripts": {
"compile-tests": "tsc -p . --outDir out",
"pretest": "npm run compile-tests",
"test": "vscode-test"
}
compile-tests 指令碼使用 TypeScript 編譯器將擴充套件編譯到 out 資料夾。有了中間的 JavaScript 後,launch.json 的以下程式碼片段就足以執行測試了。
{
"name": "Extension Tests",
"type": "extensionHost",
"request": "launch",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionTestsPath=${workspaceFolder}/out/test"
],
"outFiles": ["${workspaceFolder}/out/test/**/*.js"],
"preLaunchTask": "npm: compile-tests"
}
此用於執行測試的配置與非打包擴充套件相同。沒有必要打包單元測試,因為它們不屬於擴充套件的已釋出部分。
釋出
在釋出之前,您應該更新 .vscodeignore 檔案。現在打包到 dist/extension.js 檔案中的所有內容都可以排除,通常是 out 資料夾(如果您尚未刪除它),最重要的是,node_modules 資料夾。
典型的 .vscodeignore 檔案如下所示
.vscode
node_modules
out/
src/
tsconfig.json
webpack.config.js
esbuild.js
遷移現有擴充套件
將現有擴充套件遷移到使用 esbuild 或 webpack 很簡單,並且與上面的入門指南類似。一個採用 webpack 的真實世界示例是 VS Code 的 References 檢視,透過此 pull request。
在那裡您可以看到
- 將
esbuild或webpack、webpack-cli和ts-loader新增為devDependencies。 - 更新 npm 指令碼以使用上面所示的打包器。
- 更新任務配置檔案
tasks.json。 - 新增和調整
esbuild.js或webpack.config.js構建檔案。 - 更新
.vscodeignore以排除node_modules和中間輸出檔案。 - 享受安裝和載入速度快得多的擴充套件!
故障排除
程式碼壓縮
在 production 模式下打包還會執行程式碼壓縮。程式碼壓縮透過刪除空格和註釋以及將變數和函式名更改為醜陋但簡短的名稱來壓縮原始碼。使用 Function.prototype.name 的原始碼工作方式不同,因此您可能需要停用程式碼壓縮。
webpack 關鍵依賴項
執行 webpack 時,您可能會遇到類似 **Critical dependencies: the request of a dependency is an expression** 的警告。此類警告必須認真對待,您的包很可能無法正常工作。該訊息意味著 webpack 無法靜態確定如何打包某些依賴項。這通常是由動態 require 語句引起的,例如 require(someDynamicVariable)。
要解決此警告,您應該
- 嘗試使依賴項靜態化,以便可以將其打包。
- 透過
externals配置排除該依賴項。另請確保這些 JavaScript 檔案未從打包的擴充套件中排除,方法是在.vscodeignore中使用否定的 glob 模式,例如!node_modules/mySpecialModule。