is now available! Read about the new features and fixes from September.

Web Extensions

Visual Studio Code can run as an editor in the browser. One example is thegithub.devuser interface reached by pressing.(the period key) when browsing a repository or Pull Request in GitHub. When VS Code is used in the Web, installed extensions are run in an extension host in the browser, called the 'web extension host'. An extension that can run in a web extension host is called a 'web extension'.

Web extensions share the same structure as regular extensions, but given the different runtime, don't run with the same code as extensions written for a Node.js runtime. Web extensions still have access to the full VS Code API, but no longer to the Node.js APIs and module loading. Instead, web extensions are restricted by the browser sandbox and therefore havelimitationscompared to normal extensions.

The web extension runtime is supported on VS Code desktop too. If you decide to create your extension as a web extension, it will be supported onVS Code for the Web(includingvscode.devandgithub.dev) as well as on the desktop and in services likeGitHub Codespaces.

Web extension anatomy

A web extension isstructured like a regular extension.The extension manifest (package.json) defines the entry file for the extension's source code and declares extension contributions.

For web extensions, themain entry fileis defined by thebrowserproperty, and not by themainproperty as with regular extensions.

Thecontributesproperty works the same way for both web and regular extensions.

The example below shows thepackage.jsonfor a simple hello world extension, that runs in the web extension host only (it only has abrowserentry point):

{
"name":"helloworld-web-sample",
"displayName":"helloworld-web-sample",
"description":"HelloWorld example for VS Code in the browser",
"version":"0.0.1",
"publisher":"vscode-samples",
"repository":"https://github /microsoft/vscode-extension-samples/helloworld-web-sample",
"engines":{
"vscode":"^1.74.0"
},
"categories":["Other"],
"activationEvents":[],
"browser":"./dist/web/extension.js",
"contributes":{
"commands":[
{
"command":"helloworld-web-sample.helloWorld",
"title":"Hello World"
}
]
},
"scripts":{
"vscode:prepublish":"npm run package-web",
"compile-web":"webpack",
"watch-web":"webpack --watch",
"package-web":"webpack --mode production --devtool hidden-source-map"
},
"devDependencies":{
"@types/vscode":"^1.59.0",
"ts-loader":"^9.2.2",
"webpack":"^5.38.1",
"webpack-cli":"^4.7.0",
"@types/webpack-env":"^1.16.0",
"process":"^0.11.10"
}
}

Note:If your extension targets a VS Code version prior to 1.74, you must explicitly listonCommand:helloworld-web-sample.helloWorldinactivationEvents.

Extensions that have only amainentry point, but nobrowserare not web extensions. They are ignored by the web extension host and not available for download in the Extensions view.

Extensions view

Extensions with only declarative contributions (onlycontributes,nomainorbrowser) can be web extensions. They can be installed and run inVS Code for the Webwithout any modifications by the extension author. Examples of extensions with declarative contributions include themes, grammars, and snippets.

Extensions can have bothbrowserandmainentry points in order to run in browser and in Node.js runtimes. TheUpdate existing extensions to Web extensionssection shows how to migrate an extension to work in both runtimes.

Theweb extension enablementsection lists the rules used to decide whether an extension can be loaded in a web extension host.

Web extension main file

The web extension's main file is defined by thebrowserproperty. The script runs in the web extension host in aBrowser WebWorkerenvironment. It is restricted by the browser worker sandbox and has limitations compared to normal extensions running in a Node.js runtime.

  • Importing or requiring other modules is not supported.importScriptsis not available as well. As a consequence, the code must be packaged to a single file.
  • TheVS Code APIcan be loaded via the patternrequire('vscode').This will work because there is a shim forrequire,but this shim cannot be used to load additional extension files or additional node modules. It only works withrequire('vscode').
  • Node.js globals and libraries such asprocess,os,setImmediate,path,util,urlare not available at runtime. They can, however, be added with tools like webpack. Thewebpack configurationsection explains how this is done.
  • The opened workspace or folder is on a virtual file system. Access to workspace files needs to go through the VS Codefile systemAPI accessible atvscode.workspace.fs.
  • Extension contextlocations (ExtensionContext.extensionUri) and storage locations (ExtensionContext.storageUri,globalStorageUri) are also on a virtual file system and need to go throughvscode.workspace.fs.
  • For accessing web resources, theFetchAPI must be used. Accessed resources need to supportCross-Origin Resource Sharing(CORS)
  • Creating child processes or running executables is not possible. However, web workers can be created through theWorkerAPI. This is used for running language servers as described in theLanguage Server Protocol in web extensionssection.
  • As with regular extensions, the extension'sactivate/deactivatefunctions need to be exported via the patternexports.activate =....

Develop a web extension

Thankfully, tools like TypeScript and webpack can hide many of the browser runtime constraints and allow you to write web extensions the same way as regular extensions. Both a web extension and a regular extension can often be generated from the same source code.

For example, theHello Web Extensioncreated by theyo codegeneratoronly differs in the build scripts. You can run and debug the generated extension just like traditional Node.js extensions by using the provided launch configurations accessible using theDebug: Select and Start Debuggingcommand.

Create a web extension

To scaffold a new web extension, useyo codeand pickNew Web Extension.Make sure to have the latest version ofgenerator-code(>= [email protected]) installed. To update the generator and yo, runnpm i -g yo generator-code.

The extension that is created consists of the extension's source code (a command showing a hello world notification), thepackage.jsonmanifest file, and a webpack or esbuild configuration file.

To keep things simpler, we assume you usewebpackas the bundler. At the end of the article we also explain what is different when choosingesbuild.

  • src/web/extension.tsis the extension's entry source code file. It's identical to the regular hello extension.
  • package.jsonis the extension manifest.
    • It points to the entry file using thebrowserproperty.
    • It provides scripts:compile-web,watch-webandpackage-webto compile, watch, and package.
  • webpack.config.jsis the webpack config file that compiles and bundles the extension sources into a single file.
  • .vscode/launch.jsoncontains the launch configurations that run the web extension and the tests in the VS Code desktop with a web extension host (settingextensions.webWorkeris no longer needed).
  • .vscode/task.jsoncontains the build task used by the launch configuration. It usesnpm run watch-weband depends on the webpack specificts-webpack-watchproblem matcher.
  • .vscode/extensions.jsoncontains the extensions that provide the problem matchers. These extensions need to be installed for the launch configurations to work.
  • tsconfig.jsondefines the compile options matching thewebworkerruntime.

The source code in thehelloworld-web-sampleis similar to what's created by the generator.

Webpack configuration

The webpack configuration file is automatically generated byyo code.It bundles the source code from your extension into a single JavaScript file to be loaded in the web extension host.

Later we explain how to use esbuild as bundler, but for now we start with webpack.

webpack.config.js

constpath=require('path');
constwebpack=require('webpack');

/**@typedef{import('webpack').Configuration}WebpackConfig**/
/**@typeWebpackConfig */
constwebExtensionConfig={
mode:'none',// this leaves the source code as close as possible to the original (when packaging we set this to 'production')
target:'webworker',// extensions run in a webworker context
entry:{
extension:'./src/web/extension.ts',// source of the web extension main file
'test/suite/index':'./src/web/test/suite/index.ts'// source of the web extension test runner
},
output:{
filename:'[name].js',
path:path.join(__dirname,'./dist/web'),
libraryTarget:'commonjs',
devtoolModuleFilenameTemplate:'../../[resource-path]'
},
resolve:{
mainFields:['browser','module','main'],// look for `browser` entry point in imported node modules
extensions:['.ts','.js'],// support ts-files and js-files
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.
assert:require.resolve('assert')
}
},
module:{
rules:[
{
test:/\.ts$/,
exclude:/node_modules/,
use:[
{
loader:'ts-loader'
}
]
}
]
},
plugins:[
newwebpack.ProvidePlugin({
process:'process/browser'// provide a shim for the global `process` variable
})
],
externals:{
vscode:'commonjs vscode'// ignored because it doesn't exist
},
performance:{
hints:false
},
devtool:'nosources-source-map'// create a source map that points to the original source file
};
module.exports=[webExtensionConfig];

Some important fields ofwebpack.config.jsare:

  • Theentryfield contains the main entry point into your extension and test suite.
    • You may need to adjust this path to appropriately point to the entry point of your extension.
    • For an existing extension, you can start by pointing this path to the file you're using currently formainof yourpackage.json.
    • If you do not want to package your tests, you can omit the test suite field.
  • Theoutputfield indicates where the compiled file will be located.
    • [name]will be replaced by the key used inentry.So in the generated config file, it will producedist/web/extension.jsanddist/web/test/suite/index.js.
  • Thetargetfield indicates which type of environment the compiled JavaScript file will run. For web extensions, you want this to bewebworker.
  • Theresolvefield contains the ability to add aliases and fallbacks for node libraries that don't work in the browser.
    • If you're using a library likepath,you can specify how to resolvepathin a web compiled context. For instance, you can point to a file in the project that definespathwithpath: path.resolve(__dirname, 'src/my-path-implementation-for-web.js').Or you can use the Browserify node packaged version of the library calledpath-browserifyand specifypath: require.resolve('path-browserify').
    • Seewebpack resolve.fallbackfor the list of Node.js core module polyfills.
  • Thepluginssection uses theDefinePlugin pluginto polyfill globals such as theprocessNode.js global.

Test your web extension

There are currently three ways to test a web extension before publishing it to the Marketplace.

  • Use VS Code running on the desktop with the--extensionDevelopmentKind=weboption to run your web extension in a web extension host running in VS Code.
  • Use the@vscode/test-webnode module to open a browser containing VS Code for the Web including your extension, served from a local server.
  • Sideloadyour extension ontovscode.devto see your extension in the actual environment.

Test your web extension in VS Code running on desktop

To use the existing VS Code extension development experience, VS Code running on the desktop supports running a web extension host along with the regular Node.js extension host.

Use thepwa-extensionhostlaunch configuration provided by theNew Web Extensiongenerator:

{
"version":"0.2.0",
"configurations":[
{
"name":"Run Web Extension in VS Code",
"type":"pwa-extensionHost",
"debugWebWorkerHost":true,
"request":"launch",
"args":[
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentKind=web"
],
"outFiles":["${workspaceFolder}/dist/web/**/*.js"],
"preLaunchTask":"npm: watch-web"
}
]
}

It uses the tasknpm: watch-webto compile the extension by callingnpm run watch-web.That task is expected intasks.json:

{
"version":"2.0.0",
"tasks":[
{
"type":"npm",
"script":"watch-web",
"group":"build",
"isBackground":true,
"problemMatcher":["$ts-webpack-watch"]
}
]
}

$ts-webpack-watchis a problem matcher that can parse the output from the webpack tool. It is provided by theTypeScript + Webpack Problem Matchersextension.

In theExtension Development Hostinstance that launches, the web extension will be available and running in a web extension host. Run theHello Worldcommand to activate the extension.

Open theRunning Extensionsview (command:Developer: Show Running Extensions) to see which extensions are running in the web extension host.

Test your web extension in a browser using @vscode/test-web

The@vscode/test-webnode module offers a CLI and API to test a web extension in a browser.

The node module contributes an npm binaryvscode-test-webthat can open VS Code for the Web from the command line:

  • It downloads the web bits of VS Code into.vscode-test-web.
  • Starts a local server onlocalhost:3000.
  • Opens a browser (Chromium, Firefox, or Webkit).

You can run it from command line:

npx @vscode/test-web --extensionDevelopmentPath=$extensionFolderPath$testDataPath

Or better, add@vscode/test-webas a development dependency to your extension and invoke it in a script:

"devDependencies":{
"@vscode/test-web":"*"
},
"scripts":{
"open-in-browser":"vscode-test-web --extensionDevelopmentPath=.."
}

Check the@vscode/test-web READMEfor more CLI options:

Option Argument Description
--browserType The browser to launch:chromium(default),firefoxorwebkit
--extensionDevelopmentPath A path pointing to an extension under development to include.
--extensionTestsPath A path to a test module to run.
--permission Permission granted to the opened browser: e.g.clipboard-read,clipboard-write.
Seefull list of options.Argument can be provided multiple times.
--folder-uri URI of the workspace to open VS Code on. Ignored whenfolderPathis provided
--extensionPath A path pointing to a folder containing additional extensions to include.
Argument can be provided multiple times.
folderPath A local folder to open VS Code on.
The folder content will be available as a virtual file system and opened as workspace.

The web bits of VS Code are downloaded to a folder.vscode-test-web.You want to add this to your.gitignorefile.

Test your web extension in vscode.dev

Before you publish your extension for everyone to use on VS Code for the Web, you can verify how your extension behaves in the actualvscode.devenvironment.

To see your extension on vscode.dev, you first need to host it from your machine for vscode.dev to download and run.

First, you'll need toinstallmkcert.

Then, generate thelocalhost.pemandlocalhost-key.pemfiles into a location you won't lose them (for example$HOME/certs):

$ mkdir -p $HOME/certs
$ cd $HOME/certs
$ mkcert -install
$ mkcert localhost

Then, from your extension's path, start an HTTP server by runningnpx serve:

$ npx serve --cors -l 5000 --ssl-cert $HOME/certs/localhost.pem --ssl-key $HOME/certs/localhost-key.pem
npx: installed 78 in 2.196s

┌────────────────────────────────────────────────────┐
│ │
│ Serving! │
│ │
│ - Local: https://localhost:5000 │
│ - On Your Network: https://172.19.255.26:5000 │
│ │
│ Copied local address to clipboard! │
│ │
└────────────────────────────────────────────────────┘

Finally, openvscode.dev,runDeveloper: Install Extension From Location...from the Command Palette (⇧⌘P(Windows, LinuxCtrl+Shift+P)), paste the URL from above,https://localhost:5000in the example, and selectInstall.

Check the logs

You can check the logs in the console of the Developer Tools of your browser to see any errors, status, and logs from your extension.

You may see other logs from vscode.dev itself. In addition, you can't easily set breakpoints nor see the source code of your extension. These limitations make debugging in vscode.dev not the most pleasant experience so we recommend using the first two options for testing before sideloading onto vscode.dev. Sideloading is a good final sanity check before publishing your extension.

Web extension tests

Web extension tests are supported and can be implemented similar to regular extension tests. See theTesting Extensionsarticle to learn the basic structure of extension tests.

The@vscode/test-webnode module is the equivalent to@vscode/test-electron(previously namedvscode-test). It allows you to run extension tests from the command line on Chromium, Firefox, and Safari.

The utility does the following steps:

  1. Starts a VS Code for the Web editor from a local web server.
  2. Opens the specified browser.
  3. Runs the provided test runner script.

You can run the tests in continuous builds to ensure that the extension works on all browsers.

The test runner script is running on the web extension host with the same restrictions as theweb extension main file:

  • All files are bundled into a single file. It should contain the test runner (for example, Mocha) and all tests (typically*.test.ts).
  • Onlyrequire('vscode')is supported.

Thewebpack configthat is created by theyo codeweb extension generator has a section for tests. It expects the test runner script at./src/web/test/suite/index.ts.The providedtest runner scriptuses the web version of Mocha and contains webpack-specific syntax to import all test files.

require('mocha/mocha');// import the mocha web build

exportfunctionrun():Promise<void> {
returnnewPromise((c,e)=>{
mocha.setup({
ui:'tdd',
reporter:undefined
});

// bundles all files in the current directory matching `*.test`
constimportAll=(r:__WebpackModuleApi.RequireContext)=>r.keys().forEach(r);
importAll(require.context('.',true,/\.test$/));

try{
// Run the mocha test
mocha.run(failures=>{
if(failures>0) {
e(newError(`${failures}tests failed.`));
}else{
c();
}
});
}catch(err) {
console.error(err);
e(err);
}
});
}

To run the web test from the command line, add the following to yourpackage.jsonand run it withnpm test.

"devDependencies":{
"@vscode/test-web":"*"
},
"scripts":{
"test":"vscode-test-web --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/index.js"
}

To open VS Code on a folder with test data, pass a local folder path (folderPath) as the last parameter.

To run (and debug) extension tests in VS Code (Insiders) desktop, use theExtension Tests in VS Codelaunch configuration:

{
"version":"0.2.0",
"configurations":[
{
"name":"Extension Tests in VS Code",
"type":"extensionHost",
"debugWebWorkerHost":true,
"request":"launch",
"args":[
"--extensionDevelopmentPath=${workspaceFolder}",
"--extensionDevelopmentKind=web",
"--extensionTestsPath=${workspaceFolder}/dist/web/test/suite/index"
],
"outFiles":["${workspaceFolder}/dist/web/**/*.js"],
"preLaunchTask":"npm: watch-web"
}
]
}

Publish a web extension

Web extensions are hosted on theMarketplacealong with other extensions.

Make sure to use the latest version ofvsceto publish your extension.vscetags all extensions that are web extension. For thatvsceis using the rules listed in theweb extension enablementsection.

Update existing extensions to Web extensions

Extension without code

Extensions that have no code, but only contribution points (for example, themes, snippets, and basic language extensions) don't need any modification. They can run in a web extension host and can be installed from the Extensions view.

Republishing is not necessary, but when publishing a new version of the extension, make sure to use the most current version ofvsce.

Migrate extension with code

Extensions with source code (defined by themainproperty) need to provide aweb extension main fileand set thebrowserproperty inpackage.json.

Use these steps to recompile your extension code for the browser environment:

  • Add a webpack config file as shown in thewebpack configurationsection. If you already have a webpack file for your Node.js extension code, you can add a new section for web. Check out thevscode-css-formatteras an example.
  • Add thelaunch.jsonandtasks.jsonfiles as shown in theTest your web extensionsection.
  • In the webpack config file, set the input file to the existing Node.js main file or create a new main file for the web extension.
  • Inpackage.json,add abrowserand thescriptsproperties as shown in theWeb extension anatomysection.
  • Runnpm run compile-webto invoke webpack and see where work is needed to make your extension run in the web.

To make sure as much source code as possible can be reused, here are a few techniques:

  • To polyfill a Node.js core module such aspath,add an entry toresolve.fallback.
  • To provide a Node.js global such asprocessuse theDefinePlugin plugin.
  • Use node modules that work in both browser and node runtime. Node modules can do that by defining bothbrowserandmainentry points. Webpack will automatically use the one matching its target. Examples of node modules that do this arerequest-lightandvscode-nls.
  • To provide an alternate implementation for a node module or source file, useresolve.alias.
  • Separate your code in a browser part, Node.js part, and common part. In common, only use code that works in both the browser and Node.js runtime. Create abstractions for functionality that has different implementations in Node.js and the browser.
  • Look out for usages ofpath,URI.file,context.extensionPath,rootPath.uri.fsPath.These will not work with virtual workspaces (non-file system) as they are used in VS Code for the Web. Instead use URIs withURI.parse,context.extensionUri.Thevscode-urinode module providesjoinPath,dirName,baseName,extName,resolvePath.
  • Look out for usages offs.Replace by using vscodeworkspace.fs.

It is fine to provide less functionality when your extension is running in the web. Usewhen clause contextsto control which commands, views, and tasks are available or hidden with running in a virtual workspace on the web.

  • Use thevirtualWorkspacecontext variable to find out if the current workspace is a non-file system workspace.
  • UseresourceSchemeto check if the current resource is afileresource.
  • UseshellExecutionSupportedif there is a platform shell present.
  • Implement alternative command handlers that show a dialog to explain why the command is not applicable.

WebWorkers can be used as an alternative to forking processes. We have updated several language servers to run as web extensions, including the built-inJSON,CSS,andHTMLlanguage servers. TheLanguage Server Protocolsection below gives more details.

The browser runtime environment only supports the execution of JavaScript andWebAssembly.Libraries written in other programming languages need to be cross-compiled, for instance there is tooling to compileC/C++andRustto WebAssembly. Thevscode-anycodeextension, for example, usestree-sitter,which is C/C++ code compiled to WebAssembly.

Language Server Protocol in web extensions

vscode-languageserver-nodeis an implementation of theLanguage Server Protocol(LSP) that is used as a foundation to language server implementations such asJSON,CSS,andHTML.

Since 3.16.0, the client and server now also provide a browser implementation. The server can run in a web worker and the connection is based on the webworkerspostMessageprotocol.

The client for the browser can be found at 'vscode-languageclient/browser':

import{LanguageClient}from`vscode-languageclient/browser`;

The server atvscode-languageserver/browser.

Thelsp-web-extension-sampleshows how this works.

Web extension enablement

VS Code automatically treats an extension as a web extension if:

  • The extension manifest (package.json) hasbrowserentry point.
  • The extension manifest has nomainentry point and none of the following contribution points:localizations,debuggers,terminal,typescriptServerPlugins.

If an extension wants to provide a debugger or terminal that also work in the web extension host, abrowserentry point needs to be defined.

Using ESBuild

If you want to use esbuild instead of webpack, do the following:

Add aesbuild.jsbuild script:

constesbuild=require('esbuild');
constglob=require('glob');
constpath=require('path');
constpolyfill=require('@esbuild-plugins/node-globals-polyfill');

constproduction=process.argv.includes('--production');
constwatch=process.argv.includes('--watch');

asyncfunctionmain() {
constctx=awaitesbuild.context({
entryPoints:['src/web/extension.ts','src/web/test/suite/extensionTests.ts'],
bundle:true,
format:'cjs',
minify:production,
sourcemap:!production,
sourcesContent:false,
platform:'browser',
outdir:'dist/web',
external:['vscode'],
logLevel:'silent',
// Node.js global to browser globalThis
define:{
global:'globalThis'
},

plugins:[
polyfill.NodeGlobalsPolyfillPlugin({
process:true,
buffer:true
}),
testBundlePlugin,
esbuildProblemMatcherPlugin/* add to the end of plugins array */
]
});
if(watch) {
awaitctx.watch();
}else{
awaitctx.rebuild();
awaitctx.dispose();
}
}

/**
* For web extension, all tests, including the test runner, need to be bundled into
* a single module that has a exported `run` function.
* This plugin bundles implements a virtual file extensionTests.ts that bundles all these together.
*@type{import('esbuild').Plugin}
*/
consttestBundlePlugin={
name:'testBundlePlugin',
setup(build) {
build.onResolve({filter:/[\/\\]extensionTests\.ts$/},args=>{
if(args.kind==='entry-point') {
return{path:path.resolve(args.path) };
}
});
build.onLoad({filter:/[\/\\]extensionTests\.ts$/},asyncargs=>{
consttestsRoot=path.join(__dirname,'src/web/test/suite');
constfiles=awaitglob.glob('*.test.{ts,tsx}',{cwd:testsRoot,posix:true});
return{
contents:
`export { run } from './mochaTestRunner.ts';`+
files.map(f=>`import('./${f}');`).join(''),
watchDirs:files.map(f=>path.dirname(path.resolve(testsRoot,f))),
watchFiles:files.map(f=>path.resolve(testsRoot,f))
};
});
}
};

/**
* This plugin hooks into the build process to print errors in a format that the problem matcher in
* Visual Studio Code can understand.
*@type{import('esbuild').Plugin}
*/
constesbuildProblemMatcherPlugin={
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);
});

The build script does the following:

  • It creates a build context with esbuild. The context is configured to:
    • Bundle the code insrc/web/extension.tsinto a single filedist/web/extension.js.
    • Bundle all tests, including the test runner (mocha) into a single filedist/web/test/suite/extensionTests.js.
    • Minify the code if the--productionflag was passed.
    • Generate source maps unless the--productionflag was passed.
    • Exclude the 'vscode' module from the bundle (since it's provided by the VS Code runtime).
    • creates polyfills forprocessandbuffer
    • 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 theesbuildproblem matcher with also needs to be installed as an extension.
    • Use the testBundlePlugin to implement a test main file (extensionTests.js) that references all tests files and the mocha test runnermochaTestRunner.js
  • If the--watchflag 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 error are reported and can cause esbuild to fail.

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

Thescriptssection inpackage.jsonnow looks like that

"scripts":{
"vscode:prepublish":"npm run package-web",
"compile-web":"npm run check-types && node esbuild.js",
"watch-web":"npm-run-all -p watch-web:*",
"watch-web:esbuild":"node esbuild.js --watch",
"watch-web:tsc":"tsc --noEmit --watch --project tsconfig.json",
"package-web":"npm run check-types && node esbuild.js --production",
"check-types":"tsc --noEmit",
"pretest":"npm run compile-web",
"test":"vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/extensionTests.js",
"run-in-browser":"vscode-test-web --browserType=chromium --extensionDevelopmentPath=.."
}

npm-run-allis a node module that runs scripts in parallel whose name match a given prefix. For us, it runs thewatch-web:esbuildandwatch-web:tscscripts. You need to addnpm-run-allto thedevDependenciessection inpackage.json.

The followingtasks.jsonfiles gives you separate terminals for each watch task:

{
"version":"2.0.0",
"tasks":[
{
"label":"watch-web",
"dependsOn":["npm: watch-web:tsc","npm: watch-web:esbuild"],
"presentation":{
"reveal":"never"
},
"group":{
"kind":"build",
"isDefault":true
},
"runOptions":{
"runOn":"folderOpen"
}
},
{
"type":"npm",
"script":"watch-web:esbuild",
"group":"build",
"problemMatcher":"$esbuild-watch",
"isBackground":true,
"label":"npm: watch-web:esbuild",
"presentation":{
"group":"watch",
"reveal":"never"
}
},
{
"type":"npm",
"script":"watch-web:tsc",
"group":"build",
"problemMatcher":"$tsc-watch",
"isBackground":true,
"label":"npm: watch-web:tsc",
"presentation":{
"group":"watch",
"reveal":"never"
}
},
{
"label":"compile",
"type":"npm",
"script":"compile-web",
"problemMatcher":["$tsc","$esbuild"]
}
]
}

This is themochaTestRunner.jsreferenced in the esbuild build script:

// Imports mocha for the browser, defining the `mocha` global.
import'mocha/mocha';

mocha.setup({
ui:'tdd',
reporter:undefined
});

exportfunctionrun():Promise<void> {
returnnewPromise((c,e)=>{
try{
// Run the mocha test
mocha.run(failures=>{
if(failures>0) {
e(newError(`${failures}tests failed.`));
}else{
c();
}
});
}catch(err) {
console.error(err);
e(err);
}
});
}

Samples