捆绑扩展
捆绑 Visual Studio Code 扩展的第一个原因是确保它适用于所有平台上使用 VS Code 的每个人。只有捆绑的扩展才能在 VS Code 的 Web 环境中使用,例如 github.dev 和 vscode.dev。当 VS Code 在浏览器中运行时,它只能为你的扩展加载一个文件,因此扩展代码需要捆绑到一个单独的 Web 友好 JavaScript 文件中。这也适用于 笔记本输出渲染器,其中 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: 'silent',
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}`);
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:esbuild
和 watch:tsc
脚本。你需要将 npm-run-all
添加到 package.json
中的 devDependencies
部分。
compile
和 watch
脚本用于开发,它们会生成带有源地图的捆绑包文件。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.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
文件。resolve
和module/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",
},
compile
和 watch
脚本用于开发,它们会生成 bundle 文件。vscode:prepublish
由 vsce
使用,VS Code 打包和发布工具,在发布扩展之前运行。区别在于 模式,它控制优化级别。使用 production
会生成最小的 bundle,但也需要更长的时间,所以其他情况下使用 development
。要运行上述脚本,打开终端并输入 npm run compile
,或从命令面板(⇧⌘P (Windows, Linux Ctrl+Shift+P))中选择**任务:运行任务**。
运行扩展
在运行扩展之前,package.json
中的 main
属性必须指向 bundle,对于上面的配置,是 "./dist/extension"
。有了这个更改,扩展现在就可以执行和测试了。
测试
扩展作者经常为他们的扩展源代码编写单元测试。如果架构分层合理,扩展源代码不依赖于测试,那么 webpack 和 esbuild 生成的 bundle 就不应该包含任何测试代码。要运行单元测试,只需要简单的编译即可。
将这些条目合并到 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 view,通过这个 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 时,您可能会遇到类似**关键依赖项:依赖项的请求是一个表达式**的警告。这类警告必须认真对待,很可能您的 bundle 无法正常工作。该消息意味着 webpack 无法静态地确定如何捆绑某些依赖项。这通常是由动态 require
语句引起的,例如 require(someDynamicVariable)
。
要解决该警告,您应该
- 尝试使依赖项静态,以便可以捆绑它。
- 通过
externals
配置排除该依赖项。还要确保这些 JavaScript 文件没有从打包的扩展中排除,使用.vscodeignore
中的否定 glob 模式,例如!node_modules/mySpecialModule
。