在 VS Code 中试试

捆绑扩展

捆绑 Visual Studio Code 扩展的第一个原因是确保它能在任何平台上、为所有使用 VS Code 的用户工作。只有捆绑的扩展才能用于 VS Code for Web 环境,例如 github.devvscode.dev。当 VS Code 在浏览器中运行时,它只能加载一个文件来加载您的扩展,因此扩展代码需要捆绑到一个对 Web 友好的 JavaScript 文件中。这也适用于 Notebook 输出渲染器,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.jsondevDependencies 部分。

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.cn/configuration/target/#target

  entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.js.cn/configuration/entry-context/
  output: {
    // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.cn/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.cn/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.cn/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 即时创建。根据扩展使用的 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",
},

compilewatch 脚本用于开发,它们生成捆绑文件。vscode:prepublish 由 VS Code 打包和发布工具 vsce 使用,并在发布扩展之前运行。区别在于 mode,它控制优化级别。使用 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 非常容易,并且类似于上面的入门指南。一个实际采用 webpack 的示例是 VS Code 的 References 视图,通过此 拉取请求

在那里您可以看到

  • esbuildwebpackwebpack-clits-loader 添加为 devDependencies
  • 更新 npm 脚本,如上所示使用捆绑器
  • 更新任务配置 tasks.json 文件。
  • 添加并调整 esbuild.jswebpack.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

下一步

  • 扩展市场 - 了解有关 VS Code 公共扩展市场的更多信息。
  • 测试扩展 - 向您的扩展项目添加测试以确保高质量。
  • 持续集成 - 了解如何在 Azure Pipelines 上运行扩展 CI 构建。