🚀 在 VS Code 中

捆綁擴充功能

捆綁 Visual Studio Code 擴充功能的第一個原因是確保它適用於在任何平台上使用 VS Code 的每個人。只有捆綁的擴充功能才能在 VS Code for Web 環境中使用,例如 github.devvscode.dev。當 VS Code 在瀏覽器中執行時,它只能為您的擴充功能載入一個檔案,因此擴充功能程式碼需要捆綁到一個單一的網頁友善 JavaScript 檔案中。這也適用於 筆記本輸出轉譯器,VS Code 也只會為您的轉譯器擴充功能載入一個檔案。

此外,擴充功能的大小和複雜性可能會快速增長。它們可能是用多個原始碼檔案編寫的,並且依賴於來自 npm 的模組。分解和重複使用是開發最佳實務,但它們在安裝和執行擴充功能時會帶來成本。載入 100 個小檔案比載入一個大檔案慢得多。這就是我們建議捆綁的原因。捆綁是將多個小原始碼檔案組合到一個單一檔案的過程。

對於 JavaScript,可以使用不同的捆綁器。流行的捆綁器包括 rollup.jsParcelesbuildwebpack

使用 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 問題比對器可偵測的格式發出錯誤,該問題比對器也需要作為擴充功能安裝。
  • 如果傳遞了 --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 是一個節點模組,它並行執行名稱與給定前綴匹配的指令碼。對於我們來說,它執行 watch:esbuildwatch:tsc 指令碼。您需要將 npm-run-all 新增到 package.json 中的 devDependencies 區段。

compilewatch 指令碼用於開發,它們產生帶有原始碼對應的捆綁檔案。package 指令碼由 vscode:prepublish 指令碼使用,而 vscode:prepublish 指令碼由 vsce(VS Code 封裝和發佈工具)使用,並在發佈擴充功能之前執行。將 --production 旗標傳遞給 esbuild 指令碼會導致它壓縮程式碼並建立一個小捆綁,但也使偵錯變得困難,因此在開發期間使用了其他旗標。若要執行上述指令碼,請開啟終端機並輸入 npm run watch 或從命令選取區中選取工作:執行工作 (⇧⌘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 檔案,以在 devDependencies 中包含 webpack。

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.dev.org.tw/configuration/target/#target

  entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.dev.org.tw/configuration/entry-context/
  output: {
    // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.dev.org.tw/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.dev.org.tw/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.dev.org.tw/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 檔案。
  • resolvemodule/rules 設定用於支援 TypeScript 和 JavaScript 輸入檔案。
  • externals 設定用於宣告排除項,例如不應包含在捆綁中的檔案和模組。不應捆綁 vscode 模組,因為它在磁碟上不存在,而是在需要時由 VS Code 即時建立。根據擴充功能使用的節點模組,可能需要更多排除項。

最後,您將想要更新您的 .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",
},

compilewatch 指令碼用於開發,它們產生捆綁檔案。vscode:prepublishvsce(VS Code 封裝和發佈工具)使用,並在發佈擴充功能之前執行。不同之處在於 模式,它控制最佳化程度。使用 production 會產生最小的捆綁,但也會花費更長的時間,因此其他情況下會使用 development。若要執行上述指令碼,請開啟終端機並輸入 npm run compile 或從命令選取區中選取工作:執行工作 (⇧⌘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 很簡單,並且與上面的入門指南類似。透過此 提取請求,VS Code 的參考檢視採用 webpack 的一個真實世界範例。

您可以在其中看到

  • 新增 esbuild resp. webpackwebpack-clits-loader 作為 devDependencies
  • 更新 npm 指令碼以使用如上所示的捆綁器
  • 更新工作設定 tasks.json 檔案。
  • 新增和調整 esbuild.jswebpack.config.js 建置檔案。
  • 更新 .vscodeignore 以排除 node_modules 和中繼輸出檔案。
  • 享受安裝和載入速度更快的擴充功能!

疑難排解

最小化

production 模式下的捆綁也會執行程式碼最小化。最小化透過移除空格和註解以及將變數和函數名稱變更為醜陋但簡短的名稱來壓縮原始碼。使用 Function.prototype.name 的原始碼以不同的方式運作,因此您可能必須停用最小化。

webpack 關鍵依賴項

執行 webpack 時,您可能會遇到類似關鍵依賴項:依賴項的請求是表達式的警告。此類警告必須認真對待,並且您的捆綁很可能無法運作。此訊息表示 webpack 無法靜態判斷如何捆綁某些依賴項。這通常是由動態 require 陳述式引起的,例如 require(someDynamicVariable)

若要解決此警告,您應該執行以下操作之一

  • 嘗試使依賴項靜態化,以便可以捆綁它。
  • 透過 externals 設定排除該依賴項。另請確保這些 JavaScript 檔案未從封裝的擴充功能中排除,方法是在 .vscodeignore 中使用否定 glob 模式,例如 !node_modules/mySpecialModule

下一步

  • 擴充功能市集 - 深入瞭解 VS Code 的公共擴充功能市集。
  • 測試擴充功能 - 將測試新增至您的擴充功能專案,以確保高品質。
  • 持續整合 - 瞭解如何在 Azure Pipelines 上執行擴充功能 CI 建置。