Movatterモバイル変換


[0]ホーム

URL:


Version 1.109 is now available! Read about the new features and fixes from January.

Dismiss this update

Bundling Extensions

The first reason to bundle your Visual Studio Code extension is to make sure it works for everyone using VS Code on any platform. Only bundled extensions can be used in VS Code for Web environments likegithub.dev andvscode.dev. When VS Code is running in the browser, it can only load one file for your extension so the extension code needs to be bundled into one single web-friendly JavaScript file. This also applies toNotebook Output Renderers, where VS Code will also only load one file for your renderer extension.

In addition, extensions can quickly grow in size and complexity. They may be authored in multiple source files and depend on modules fromnpm. Decomposition and reuse are development best practices but they come at a cost when installing and running extensions. Loading 100 small files is much slower than loading one large file. That's why we recommend bundling. Bundling is the process of combining multiple small source files into a single file.

For JavaScript, different bundlers are available. Popular ones arerollup.js,Parcel,esbuild, andwebpack.

Using esbuild

esbuild is a fast JavaScript bundler that's simple to configure. To acquire esbuild, open the terminal and type:

npm i --save-dev esbuild

Run esbuild

You can run esbuild from the command line but to reduce repetition and enable problem reporting, it is helpful to use a build script,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);});

The build script does the following:

  • It creates a build context with esbuild. The context is configured to:
    • Bundle the code insrc/extension.ts into a single filedist/extension.js.
    • Minify the code if the--production flag was passed.
    • Generate source maps unless the--production flag was passed.
    • Exclude the 'vscode' module from the bundle (since it's provided by the VS Code runtime).
  • Use the esbuildProblemMatcherPlugin plugin to report errors that prevented the bundler to complete. This plugin emits the errors in a format that is detected by theesbuild problem matcher with also needs to be installed as an extension.
  • If the--watch flag was passed, it starts watching the source files for changes and rebuilds the bundle whenever a change is detected.

esbuild can work directly with TypeScript files. However, esbuild simply strips off all type declarations without doing any type checks.Only syntax errors are reported and can cause esbuild to fail.

For that reason, we separately run the TypeScript compiler (tsc) to check the types, but without emitting any code (flag--noEmit).

Thescripts section inpackage.json now looks like that

"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 is a node module that runs scripts in parallel whose name match a given prefix. For us, it runs thewatch:esbuild andwatch:tsc scripts. You need to addnpm-run-all to thedevDependencies section inpackage.json.

Thecompile andwatch scripts are for development and they produce the bundle file with source maps. Thepackage script is used by thevscode:prepublish script which is used byvsce, the VS Code packaging and publishing tool, and run before publishing an extension. Passing the--production flag to the esbuild script will cause it to compress the code and create a small bundle, but also makes debugging hard, so other flags are used during development. To run above scripts, open a terminal and typenpm run watch or selectTasks: Run Task from the Command Palette (⇧⌘P (Windows, LinuxCtrl+Shift+P)).

If you configure.vscode/tasks.json the following way, you will get a separate terminal for each watch task.

{  "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"      }    }  ]}

This watch tasks depends on the extensionconnor4312.esbuild-problem-matchers for problem matching that you need to install for the task to report problems in the problems view. This extension needs to be installed for the launch to complete.

To not forget that, add a.vscode/extensions.json file to the workspace:

{  "recommendations": ["connor4312.esbuild-problem-matchers"]}

Finally, you will want to update your.vscodeignore file so that compiled files are included in the published extension. Check out thePublishing section for more details.

Jump down to theTests section to continue reading.

Using webpack

Webpack is a development tool that's available fromnpm. To acquire webpack and its command line interface, open the terminal and type:

npm i --save-dev webpack webpack-cli

This will install webpack and update your extension'spackage.json file to include webpack in thedevDependencies.

Webpack is a JavaScript bundler but many VS Code extensions are written in TypeScript and only compiled to JavaScript. If your extension is using TypeScript, you can use the loaderts-loader, so that webpack can understand TypeScript. Use the following to installts-loader:

npm i --save-dev ts-loader

All files are available in thewebpack-extension sample.

Configure webpack

With all tools installed, webpack can now be configured. By convention, awebpack.config.js file contains the configuration to instruct webpack to bundle your extension. The sample configuration below is for VS Code extensions and should provide a good starting point:

//@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;

The file isavailable as part of thewebpack-extension sample. Webpack configuration files are normal JavaScript modules that must export a configuration object.

In the sample above, the following are defined:

  • Thetarget indicates which context your extension will run. We recommend usingwebworker so that your extension will work both in VS Code for web and VS Code desktop versions.
  • The entry point webpack should use. This is similar to themain property inpackage.json except that you provide webpack with a "source" entry point, usuallysrc/extension.ts, and not an "output" entry point. The webpack bundler understands TypeScript, so a separate TypeScript compile step is redundant.
  • Theoutput configuration tells webpack where to place the generated bundle file. By convention, that is thedist folder. In this sample, webpack will produce adist/extension.js file.
  • Theresolve andmodule/rules configurations are there to support TypeScript and JavaScript input files.
  • Theexternals configuration is used to declare exclusions, for example files and modules that should not be included in the bundle. Thevscode module should not be bundled because it doesn't exist on disk but is created by VS Code on-the-fly when required. Depending on the node modules that an extension uses, more exclusion may be necessary.

Finally, you will want to update your.vscodeignore file so that compiled files are included in the published extension. Check out thePublishing section for more details.

Run webpack

With thewebpack.config.js file created, webpack can be invoked. You can run webpack from the command line but to reduce repetition, using npm scripts is helpful.

Merge these entries into thescripts section inpackage.json:

"scripts": {    "compile":"webpack --mode development",    "watch":"webpack --mode development --watch",    "vscode:prepublish":"npm run package",    "package":"webpack --mode production --devtool hidden-source-map",},

Thecompile andwatch scripts are for development and they produce the bundle file. Thevscode:prepublish is used byvsce, the VS Code packaging and publishing tool, and run before publishing an extension. The difference is in themode and that controls the level of optimization. Usingproduction yields the smallest bundle but also takes longer, so elsedevelopment is used. To run above scripts, open a terminal and typenpm run compile or selectTasks: Run Task from the Command Palette (⇧⌘P (Windows, LinuxCtrl+Shift+P)).

Run the extension

Before you can run the extension, themain property inpackage.json must point to the bundle, which for the configuration above is"./dist/extension". With that change, the extension can now be executed and tested.

Tests

Extension authors often write unit tests for their extension source code. With the correct architectural layering, where the extension source code doesn't depend on tests, the webpack and esbuild produced bundle shouldn't contain any test code. To run unit tests, only a simple compile is necessary.

Merge these entries into thescripts section inpackage.json:

"scripts": {    "compile-tests":"tsc -p . --outDir out",    "pretest":"npm run compile-tests",    "test":"vscode-test"}

Thecompile-tests script uses the TypeScript compiler to compile the extension into theout folder. With that intermediate JavaScript available, the following snippet forlaunch.json is enough to run tests.

{  "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"}

This configuration for running tests is the same for non-bundled extensions. There is no reason to bundle unit tests because they are not part of the published portion of an extension.

Publishing

Before publishing, you should update the.vscodeignore file. Everything that's now bundled into thedist/extension.js file can be excluded, usually theout folder (in case you didn't delete it yet) and most importantly, thenode_modules folder.

A typical.vscodeignore file looks like this:

.vscodenode_modulesout/src/tsconfig.jsonwebpack.config.jsesbuild.js

Migrate an existing extension

Migrating an existing extension to use esbuild or webpack is easy and similar to the getting started guide above. A real world sample that adopted webpack is the VS Code's References view through thispull request.

There you can see:

  • Addesbuild resp.webpack,webpack-cli, andts-loader asdevDependencies.
  • Update npm scripts to use the bundlers as shown above
  • Update the task configurationtasks.json file.
  • Add and tweak theesbuild.js orwebpack.config.js build file.
  • Update.vscodeignore to excludenode_modules and intermediate output files.
  • Enjoy an extension that installs and loads much faster!

Troubleshooting

Minification

Bundling inproduction mode also performs code minification. Minification compacts source code by removing whitespace and comments and by changing variable and function names into something ugly but short. Source code that usesFunction.prototype.name works differently and so you might have to disable minification.

webpack critical dependencies

When running webpack, you might encounter a warning likeCritical dependencies: the request of a dependency is an expression. Such warnings must be taken seriously and likely your bundle won't work. The message means that webpack cannot statically determine how to bundle some dependency. This is usually caused by a dynamicrequire statement, for examplerequire(someDynamicVariable).

To address the warning, you should either:

  • Try to make the dependency static so that it can be bundled.
  • Exclude that dependency via theexternals configuration. Also make sure that those JavaScript files aren't excluded from the packaged extension, using a negatedglob pattern in.vscodeignore, for example!node_modules/mySpecialModule.

Next steps

02/04/2026

    [8]ページ先頭

    ©2009-2026 Movatter.jp