參加你附近的 ,瞭解 VS Code 中的 AI 輔助開發。

捆綁擴充套件

捆綁 Visual Studio Code 擴充套件的第一個原因是確保它適用於在任何平臺上使用 VS Code 的每個人。只有捆綁的擴展才能在 Web 環境(例如 github.devvscode.dev)中的 VS Code 中使用。當 VS Code 在瀏覽器中執行時,它只能載入一個檔案用於您的擴充套件,因此擴充套件程式碼需要捆綁到一個單一的、Web 友好的 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 是一個 Node 模組,它並行執行名稱匹配給定字首的指令碼。對我們來說,它執行 watch:esbuildwatch:tsc 指令碼。您需要將 npm-run-all 新增到 package.json 中的 devDependencies 部分。

compilewatch 指令碼用於開發,它們生成帶有源對映的捆綁檔案。package 指令碼由 vscode:prepublish 指令碼使用,該指令碼由 VS Code 打包和釋出工具 vsce 使用,並在釋出擴充套件之前執行。將 --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 檔案以將 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 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.jsonscripts 部分中

"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:prepublish 由 VS Code 打包和釋出工具 vsce 使用,並在釋出擴充套件之前執行。區別在於 模式,它控制最佳化級別。使用 production 會產生最小的捆綁包,但需要更長時間,因此其他情況下使用 development。要執行上述指令碼,請開啟終端並鍵入 npm run compile 或從命令面板中選擇任務:執行任務⇧⌘P(Windows、Linux Ctrl+Shift+P)。

執行擴充套件

在執行擴充套件之前,package.json 中的 main 屬性必須指向捆綁包,對於上述配置,捆綁包是 "./dist/extension"。完成此更改後,擴充套件現在可以執行和測試。

測試

擴充套件作者通常會為他們的擴充套件原始碼編寫單元測試。如果架構分層正確,即擴充套件原始碼不依賴於測試,那麼 webpack 和 esbuild 生成的捆綁包不應包含任何測試程式碼。要執行單元測試,只需進行簡單的編譯即可。

將這些條目合併到 package.jsonscripts 部分中

"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 引用檢視。

在那裡您可以看到:

  • 新增 esbuild(或 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 構建。