microsoft/vscode

Enable consuming of ES modules in extensions

igorskyflyer opened this issue Β· 72 comments

When developing extensions and using either JavaScript or TypeScript we are unable to consume ES modules, only somewhat legacy CommonJS modules, setting the type to module and rewriting the extension to use import instead of require breaks the extension, generating an exception that states that all modules should use import instead of require in internal VS Code JavaScript files, I conclude it's caused by the type: module that forces Node to treat all .js files as ES modules. Tried using TypeScript which transpiles its own syntax to CommonJS module - so that's a no, I have also tried using just .mjs extension, again the same issue.

What is the status of this issue and are there plans to enable using of ES modules in extension development? That (could) bring somewhat big performance gains when bundling extensions with, for example, esbuild because it would enable tree-shaking - dead code removal, thus loading only necessary code. But I think this is not an extension API only issue, right? This needs to be done for VS Code itself?

(Experimental duplicate detection)
Thanks for submitting this issue. Please also check if it is already covered by an existing one, like:

Closing after 15 days of no reply.

Sounds like a duplicate of #116056. Is it possible to reopen that issue?


The VS Code extension host currently only accepts CJS module, as shown in the (trimmed) error message below:

Activating extension failed

Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
require() of ES modules is not supported.

Instead change the requiring code to use import()

at internal/modules/cjs/loader.js:823:14

at require (internal/modules/cjs/helpers.js:88:18)
at Function.t [as __$__nodeRequire] (c:\Program Files\Microsoft VS Code\resources\app\out\vs\loader.js:5:101)
at v._loadCommonJSModule
at v._doActivateExtension
at v._activateExtension (c:\Program Files\Microsoft VS Code\resources\app\out\vs\workbench\services\extensions\node\extensionHostProcess.js:99:11695)

However, I think ES module will be more convenient in future.

Besides, TypeScript can transpile dynamic import() in CJS module in a surprising way (microsoft/TypeScript#43329), while it's never a problem in ES module where import() is emitted as is. VS Code extension authors can benefit from building extension as ES module.

For what it's worth, I ran into this trying to update node-fetch from 2.x to 3.x, since the newer version is ESM only. Looks to be the direction things are going, so extension developers are only more likely in the future to run into issues due to lack of ESM support.

FYI @TylerLeonhardt one of the Code issues I'd love to see fixed πŸ˜ƒ

TypeScript 4.5 will perhaps have a new module option called node12, which preserves import() in CJS module. Then, although your entry point still have to be a CJS module now, you can load ES modules internally in an asynchronous manner.

See

Excuse me, @TylerLeonhardt, do you know why my comment above was marked as spam? I have to assume that was a mistake.

@andschwa I'm sorry, it might have been me that marked the comment as spam, but I don't remember doing it. I personally tend to hide comments that do not bring any value to the underlying discussion or distract from it.

TypeScript 4.5 will perhaps have a new module option called node12, which preserves import() in CJS module. Then, although your entry point still have to be a CJS module now, you can load ES modules internally in an asynchronous manner.

See

This has now been done in microsoft/TypeScript#45884
Looks like the remaining issue to watch is microsoft/TypeScript#46452 (assuming VSCode is waiting for this to happen before carrying on). Seems like they're still aiming for a 4.6 release

Conversation here seems to have drifted off topic. While node12 module resolution logic in TypeScript will help with extension authoring (and directly consuming ES module packages in VSCode), it is not required to support ES modules. Extensions are regular JavaScript, so all that needs to happen is for dynamic import support to be introduced.

I aim to author ESM wherever I can (easier to share code across the ecosystem) so I had a look at the VSCode source to see if I can push this forward.

Best I can tell, there are 2 environments we care about (within the scope of this issue, web extensions have additional limitations so I'm ignoring it for now). They can run in the main thread (which is hopefully rare) or in the extension host.

Extension Host

What i've managed to gleam from VSCode source is that the extension host is started with the worker_threads API, which potentially could be imposing a dynamic require restriction (I'll test this in NodeJS to clarify).


this._worker = new Worker(
FileAccess.asFileUri('vs/platform/extensions/node/extensionHostStarterWorkerMain.js', require).fsPath,
);

Extensions hosted here are imported with require (or rather what I believe is a webpack escaped version). I've included more of the call chain in the snippets below to help with context.

const extensionDescription = this._registry.getExtensionDescription(extensionId)!;
return this._activateExtension(extensionDescription, reason);

this._loadCommonJSModule<IExtensionModule>(extensionDescription.identifier, joinPath(extensionDescription.extensionLocation, entryPoint), activationTimesBuilder),

r = require.__$__nodeRequire<T>(module.fsPath);

Main Thread

This follows a different path, but appears to enter the extension host before being redirected back to the main thread (likely necessary to orchestrate instantiation of extension dependencies).

if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) {
await this._mainThreadExtensionsProxy.$activateExtension(extensionId, reason);
return new HostExtension();
}

$activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise<void> {
return this._extensionService._activateById(extensionId, reason);
}

this._extensionHostManagers.map(manager => manager.activate(extensionId, reason))

And that is about as far as I could get.

EDIT: Or this could be an upstream limitation I guess. electron/electron#21457
EDIT 2: Definitely electron/electron#21457 (comment)

AFAIK, generally speaking, VS Code extension hosts and VS Code extensions are complied by TypeScript and run on the same engines, thus, loading ES modules in VS Code extension hosts and VS Code extensions is essentially the same problem.


Let's take a look at the runtime first.

There're two extension hosts. One is on Node.js. The other is on web worker.

Node.js's support for ES module is usable, although some features are experimental.

Module loading methods in Node.js

Method L: ES L: CJS A: ES A: CJS
import Y Y Y -
import() Y Y Y Y
require() - Y - Y
  • ES: ECMAScript module
  • CJS: CommonJS module
  • L: Can Load
  • A: Available In
  • Y: Yes
  • -: No

Web browsers' support is a bit awkward, as ES module is still not available in web workers on Firefox.

https://caniuse.com/mdn-javascript_statements_import_worker_support


TypeScript, as part of the toolchain, is critical. But when setting "module": "CommonJS", the dynamic import() expressions are replaced with require() calls, which is fatal. That's why people pay great attention to changes in TypeScript.

Bundlers, such as webpack and Rollup, also have a few problems with ES module.


We can learn from #135450 that VS Code needs to intercept the loading of vscode to control extension API access. I guess that's not a big problem, because we can configure bundlers to always emit require("vscode"), so that the extension hosts won't have to worry about other module systems.

EDIT:

Please forget what I said about require("vscode") above.

I thought it's enough to register a globalThis.require() or modify the module.prototype.require(), and gave an example demonstrating how to call require() in ES modules. But it's just not feasible for a JavaScript function to trace the caller module.

Then, it's still a big problem to talk to JavaScript engines to intercept the import "vscode". Both Node.js and WHATWG have proposals.

Having stumbled across electron#21457 while investigating why an Atom extension couldn't use ESM imports, I'm a bit flummoxed. The crux of it seems to be that (a) Electron is complicated, (b) supporting ESM within Electron is complicated due to security issues and clashes in resolution algorithms between Node and browser environments; and (c) nobody seems to be sure what to do about it, including Electron's maintainers. The result is an issue that's been open for two years, is too important to be closed as a won't-fix, but seems no closer to resolution than on the day it was opened.

In a vacuum I'd take it as a symptom of a loss of mindshare and relevance β€” people seem much less sanguine about Electron than they used to be, and Atom (Electron's original reason for being) feels like it's basically in maintenance mode now. But then I remembered about Slack, and Figma, and Discord, and VSCode, and I'm now wondering how the platform underpinning all of these major apps is so paralyzed over ES modules. Is electron#21457 a blocker for this issue, as @Silic0nS0ldier suspects?

The Node ecosystem will only embrace ESM more and more. For too long, publishing packages on NPM has involved module bundlers and transpilation and some amount of voodoo, but many package authors did it anyway, because there was the promise of an eventual blue-sky future when all of that would be unnecessary. Without ESM support in Electron, I imagine the only recourse for VSCode extension authors (like Atom package authors) is to resort to those same transpilers, and/or tools like standard-things/esm which were always intended as transitional stopgaps and will only work less and less well over time.

So I’m curious about whether electron#21457 is on VSCode's radar, and whether its developers (and Slack's, and Discord's…) can perhaps be part of the brainstorming needed to get it moving.

The mystery I'm stuck on is why this impasse doesn't feel more urgent to major Electron apps like VSCode, but the answer could be quite simple. Perhaps there are aspects of VSCode's internals that make this problem less salient; or even remove Electron as a blocker; I'm not an expert there. But when VSCode is asked β€œDo you support ES modules?” in the year 2022, even a β€œyes, butβ€¦β€œ answer is a red flag when the answer is a flat β€œyes” most everywhere else.

EDIT: Unfortunately this only works in local extension dev, not when packaged. See the next 2 messages for details.

I started building a new extension a while back, here's the workaround I'm using at the moment that doesn't involve any third party libraries/transpilers:

In package.json, we point to a .cjs file as the main entrypoint:

  "main": "./main.cjs",
  "type": "module",

In main.cjs, we use dynamic import to import the ESM module, and then await it in activate:

const importingMain = import('./main.js')

module.exports = {
  activate: async (context) => {
    ;(await importingMain).activate(context)
  },
}

In main.js, we use createRequire to import vscode which is still CommonJS, but otherwise everything else can be in native esm downstream:

import { createRequire } from 'module'
const require = createRequire(import.meta.url)

const vscode = require('vscode')

export const activate = (context) => {
  //...
}

The setup seems to be working OK for me so far, hope it's helpful for others as well!

@lewisl9029 I tried using your dynamic approach (similar to what I tried prior to posting back in December #130367 (comment)) without success.

First issue, vsce interprets ./main.cjs as ./main.cjs.js. Easily enough worked around by shifting ESM code into its own folder which contains package.json with "type": "module", or using .mjs.

Second issue, I get an error once the dynamic import is hit. Same I hit previously.
TypeError [ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING]: A dynamic import callback was not specified.
Most search hits for this will turn up Jest which doesn't support ESM in a lot of cases (it replaces the module loader in CJS code).
EDIT: This is mentioned at #116056

Has anyone else had success? And if so, could you share your VSCode version and OS? Are you using VSCE to package your extension?

@Silic0nS0ldier I haven't ran in to the first issue, but glad you found a workaround.

Re: the second issue, I seem to remember testing a packaged version with this pattern and seeing it work, but my memory must be failing me, because when I try to repro now I also see the same ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING error.

I traced it back to the options to vm.Script missing a importModuleDynamically function to implement dynamic import resolution.

I suppose if we have someone sufficiently motivated, and if we can get VSCode team's blessing, we could implement that function to get this pattern to work with packaged extensions. But I suspect there would be a high bar there in terms of security/compatibility implications and won't become available for quite a few releases.

In the mean time though, unfortunately it does mean this is pattern is only useful for local dev, which practically means not very useful at all. Apologies for getting people's hopes up only to disappoint. 😞

@lewisl9029 Could be that support was removed in a past release, likely involuntarily. Regardless knowing that dynamic import works in the extension development host is useful information, it means Electron isn't the blocker.

Found another semi-blocker to using native unbundled ESM: https://code.visualstudio.com/api/working-with-extensions/bundling-extension

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 like github.dev and vscode.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 to Notebook Output Renderers, where VS Code will also only load one file for your renderer extension.

This is a rather unfortunate constraint, because there are plenty of ways to flatten module waterfalls without bundling these days.

Unfortunately since I'm targeting Web as well, looks like I'm going to have to bundle regardless of ESM support. 😞

I believe I've worked out why dynamic import is failing. Within the extension host a custom loader is used, VSCode Loader, an AMD implementation.

The NodeScriptLoader (loader is built to work in multiple runtimes) loads modules using require('vm').Script which if not given a callback for option importModuleDynamically will cause loaded modules to error with ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING. NodeJS docs

To support dynamic import 2 things need to happen.

  1. Provide a dynamic import callback (importModuleDynamically) in NodeScriptLoader (VSCode Loader).
  2. Update the compiled loader script at src/vs/loader.js (VSCode).

Problems I can see here:

  • Does NodeJS 14 support importModuleDynamically?
    It does not appear in @types/node@14.14.43. EDIT: This is undocumented
    EDIT 2: This is available in VSCode 1.63.2/NodeJS 4.16.0
  • ES modules have different module resolution logic. I don't think import() can be used on its own (it resolve relative to the module scope). import.meta.resolve would help but is marked experimental.
    EDIT: import.meta.resolve does not work for code inside the context the VSCode loader creates. If wanting to create your own ESM support you'll need to supply your own module resolution logic.

Just my 5cent once you understood the both module systems your clear already able to use ESM only excempt as already saying is bootstraping of some electron code

as a Engine Coder it is clear why there is no ESM Support on bootstrap it is because NodeJS did not finish the Nativ Module bindings for the ESM Integration ok there is dlopen but thats it

Short version

dynamic import() with full file path to a real ESM file will always work! and did work the last 7 years since it exists and did work 8+ years since it is in dev thats how long i use ESM inside Electron and vscode as also extensions just my 5cent.

there is a ESM Module Loader coded in CJS its called esm and is aviable via npm i esm

It implements FULL! i Repeat FULL! More then Complet implementation of the ESM loader in Userland written in CJS. that even Works to bootstrap.

@frank-dspeed , you are correct. The issue seems to be more related to how typescript works with dynamic imports. Stable version of Typescript converts dynamic import() to require statements while doing transpilation. This is problematic.
Typescript's beta seems to be trying to solve this issue. (microsoft/TypeScript#46452)
target: 'NodeNext' is something i am looking forward to. This beta version still has some bugs. I am waiting for it to get stable.

@Manish3323 i am evaluating that a lot since some monthes and can tell you it solves nothing my final solution is simple but effecient i coded my own universal loader that can load ESM and CJS and invinted my own Module Standards that are universal compatible.

The Main Problem between the both ecosystems is that ESM is Async by default while some environments do depend on something that is called a sideEffect and a sideEffect in the Nativ Environment means that you can not use ESM to catch the first events as it will get executed after the first process.nextTick also you can not require anything syncron from ESM as ESM is already Async by default even if the engine starts it it is Async.

also all NodeNativ code can also only get required so all nativ code dependent node modules. can only get loaded via the CJS context that is bound in sync to the engine as it is Sync by default.

if you want my conclusion about getting good support then the conlusion is the support is fully there while the coders are not aware of the edgecases like avoid default exports and module.exports
so go namedExports only if your forced to supply a default then simply always export a namespace with the same modules assigned to it that you export and also export the names this way the engine does no magic and you module works also transpiled with a single ts file.

i publish the code of the loader soon it is in general rollup hooking into require to require also mjs files as cjs or mjs or vice versa on runtime and register the modules in the require cache.

https://github.com/stealify/ecmascript-loader

so it allows to require any node_module without any problems when it is ESM it will resolve everything and transpil everything as also register all modules and return the result.

I’ve hit this issue with the latest (0.48.0) release of the markdownlint extension: https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint. As one of the comments above notes, import works perfectly well during development (both for normal and web worker modes), but then fails after publishing a package to the Marketplace and using it. Unlike the people above that may be able to recompile or alter their code, my scenario involves loading a user script from their workspace at runtime, so I need import to function correctly. The error I get from VS Code is β€œInvalid host defined options”. I’m happy to provide more information or steps to reproduce if that would be helpful.

@DavidAnson I was in a similar situation (my extension relies on https://github.com/vitejs/vite which recently migrated to ESM).

Here is the workaround I implemented:

  • VS Code extension spins up a local Node server which contains all core logic (using https://github.com/sindresorhus/execa)
  • VS Code extension communicates with the Node server via HTTP (ideally it would be via IPC, but I couldn't quite figure that out especially since node-ipc was recently compromised)

It's slower and uses more memory, but at least it doesn't crash πŸ˜•

See the code here: https://github.com/fwouts/previewjs/blob/a1222788f95b58cbbade800a4adbf584990fc291/integrations/vscode/src/index.ts#L40

@fwouts, thank you so much! In my case, there has been very little adoption of .mjs configuration files (it's a new feature of the CLI), so I removed the documentation for this in the extension and will leave it to fail at runtime for anyone who tries. My hope is that VS Code will address the issue soon and it will start working without any further action.

I ran into this issue with the latest release of Karma Test Explorer while trying to update some dependencies that had now migrated to ESM. I was able to make it work as an ESM package by using esbuild to bundle to cjs, and bundling the output files with a .cjs extension as seen in the esbuild config here.

Also had to alias the entry main.cjs file to main.cjs.js, which got it past a vsce packaging hiccup where it would look for main.cjs.js and not recognize the main.cjs in the package.json as a cjs file.

@lucono I have submitted a pr to vsce for .cjs extension
microsoft/vscode-vsce#740

Create a file named patches/@vscode__vsce@2.17.0.patch

# support cjs entry `main: "./xxx.cjs"` in package.json
# https://github.com/microsoft/vscode-vsce/pull/740

diff --git a/out/package.js b/out/package.js
index 3711bf76db20fa2b351089a8c32ae8c437a9b7d9..f9da497b9925f24e4596a85cb41aa2cd2d43946e 100644
--- a/out/package.js
+++ b/out/package.js
@@ -693,7 +693,7 @@ class LaunchEntryPointProcessor extends BaseProcessor {
         }
     }
     appendJSExt(filePath) {
-        if (filePath.endsWith('.js')) {
+        if (filePath.endsWith('.js') || filePath.endsWith('.cjs')) {
             return filePath;
         }
         return filePath + '.js';

add following section in package.json

    "pnpm": {
        "patchedDependencies": {
            "@vscode/vsce@2.17.0": "patches/@vscode__vsce@2.17.0.patch"
        }
    }

pnpm install patched @vscode/vsce package

pnpm install

There were some new discussion in the electron project, and I tried to make them aware that this problem is also important for the VS Code extensions: electron/electron#21457 (comment)

There was also a more technical comment (electron/electron#21457 (comment)), which I'm not able to reply, so, it would be great if someone with a better understanding of the problem could also join the discussion, perhaps we can move things forward.

Reading the latest mention of this issue - by the Electron team - the problem is NOT in the Electron itself but in VS Code? Any thoughts on this? πŸ€”

the latest electron/governance#536 (comment) of this issue - by the Electron team - the problem is NOT in the Electron itself but in VS Code

Hey @igorskyflyer, I wanted to clarify that I was merely referencing a few comments from @Silic0nS0ldier in this GitHub issue: #130367 (comment) #130367 (comment).

In particular, this part:

Could be that support was removed in a past release, likely involuntarily. Regardless knowing that dynamic import works in the extension development host is useful information, it means Electron isn't the blocker.

I'm not providing any new analysis on the topic (especially not in an "Electron team confirms it" way). I don't know how VSCode extensions are implemented and can't pretend that I know enough to know where the error should be fixed. I've updated my original comment to make that clearer.

On another note, electron/electron#37535 (currently under review) does expand ESM support in Electron, which might be a promising avenue to explore for Electron apps in a future release. :)

/cc @jrieken (who’s been working on this)

@erickzhao, thank you for your insightful comment and for clearing things out! I hope this issue will be resolved soon, we would all benefit from it. 😁

Hoping this would be fixed too πŸ™

This should have been implemented 8 years ago when it came out in 2015. That's 28% percent of the existence of JavaScript. I wasn't expecting much of a language where code bundled by WebPack looks like "that" and it's so terrible the most popular thing to fix it, by default runs on a version of that language (ES3) that came out two years before 9/11, creating even more code that looks like "that", but come on, there's a standard for this exact reason, so that it isn't like this, but it doesn't work unless you actually implement it instead of it being half implemented and now sometimes just doesn't work at all. And I know it's not easy to fix, especially if other people have only half implemented it, but you guys only started even considering this in 2021 and I don't even see any progress (unless there is, please tell me). Look at all that ugly code to get non-ESM modules to work, CJS, UMD, AMD etc, are yout telling me that's easier than a built-in, standardised feature where you can just import any file? Oh Node or VSCode or whatever won't allow you to do it or something? You can just import it as a normal file. Maybe change a few module.exports to export {} and that one property in the package.json from cjs to module. Maybe even an option to dynamically import fn activate and deactivate instead of requiring it. So hard. And TS allowing ESM but WebPack bundling it to use require doesn't count. Can I please just have my code work? Everyone knows: the older the JS feature, the worse. Oh but compatibilty! 1: Nobody is using JS previous to 2015 anyway. 2: This is stopping compatibility with versions that aren't terrible that people are using. 3: Even if you are running JS older than 2015, you deserve it not being compatible with your version, and not just for not allowing other people to make progress and use the actually good thing and causing this situation here (essentially for causing point 2). And also, what's this here that vscode put in my package.json?: "engines": {"vscode": "^1.77.0"}. very based. Wow it's almost as if trying to support older versions only creates problems and benefits only those who can't press ctrl+shift+p and type "update". And that was a minor version and two patches ago. Maybe the versions of JS that came out more than a decade ago, are shit and are incompatible with actually nice to work with versions of JS should be deprecated (extremely hot take). I know that once Rust supports a feature, they'll support it forever, but comparing Rust to JS is only slightly cocky.

This isn't supposed to be an attack on anyone, I get codebases can get very unwieldy, but maybe press ctrl+shift+h, alt+r, type (const|var) ([\w]+) = require((.*)), press tab, then type import $2 from $3. As for how you export things in not ESM, I have no clue and I don't intend to know.

TLDR: "hey guys, it's me, internet explorer, have you guys heard about let and const?"

This was just added to Electron (which was the blocker), the question will be when Electron 28 gets merged into vscode. If a tracker issue for that ever gets created then this issue should be closed/tied to that dependent issue.
image

I would say there is a long way to go... vscode stable still uses Electron 22, and latest stable Electron is still 26.

@felipecrs right, I'm not saying it's happening tomorrow, but the downstream blocker is now removed so now it's entirely on the vscode team to implement rather than it being kicked to a dependency :)

For sure. I just wanted to set the expectations. Thanks for the heads up.

I have a honest question, is there any reason why VS Code doesn't keep up more closely with Electron updates? Is it only because it's too hard to handle the breaking changes?

FYI, for some cases it is possible to use worker_threads to use the esm package, prettier-vscode is doing this.
prettier/prettier-vscode#3016

tats-u commented

electron/electron#21457

https://www.electronjs.org/docs/latest/tutorial/electron-timelines

Electron 28 with the fix will be available on November.
VS Code uses the versions $3n + 1$ of Electron after 19. (19β†’22β†’25) The next will be 28.

#154092
#177600
#188268

The lag is about 2 months.

I hope this will be fixed in VS Code at the beginning of the next year.

electron/electron#21457

https://www.electronjs.org/docs/latest/tutorial/electron-timelines

Electron 28 with the fix will be available on November. VS Code uses the versions 3n+1 of Electron after 19. (19β†’22β†’25) The next will be 28.

#154092 #177600 #188268

The lag is about 2 months.

I hope this will be fixed in VS Code at the beginning of the next year.

Wow, this is actually great news, I hope so too! We can then finally switch to ESM, use the standardized ESM syntax and extensions will then get smaller = faster because of tree-shaking. Thanks for the info. 😁

VS Code just updating Electron probably isn't automatically going to allow for standard JS module extension, and it probably isn't necessary since current VS Code can already load JS modules via dynamic import().

VS Code require()s extensions, so that will have to be changed to a dynamic import(), which could already be done today.

And for our own extension code, it's relatively easy to make the extension package type: "module" in package.json and have one extension.cjs file that dynamic imports the rest of the extension in activate() since activate returns a Promise.

And for our own extension code, it's relatively easy to make the extension package type: "module" in package.json and have one extension.cjs file that dynamic imports the rest of the extension in activate() since activate returns a Promise.

That would be after they make it an import though right? Do you have an example of an extension doing this today?

That would be after they make it an import though right

No, any script can use dynamic import. The tricky part is that dynamic imports are async, so you need to call them from other async entrypoints, but activate is already that.

Do you have an example of an extension doing this today?

I have it working locally. It looks like:

extension.cts:

import * as vscode from 'vscode';

export async function activate(context: vscode.ExtensionContext) {
  const {FooEditorProvider} = await import('./lib/foo-editor.js');
  context.subscriptions.push(FooEditorProvider.register(context));
}

lib/foo-editor.ts:

import {createRequire} from 'module';
const require = createRequire(import.meta.url);
import vscode = require('vscode');

export class FooEditorProvider implements vscode.CustomTextEditorProvider {
  public static register(context: vscode.ExtensionContext): vscode.Disposable {
    // ...
  }
}

I described my pure ESM solution here: xenova/transformers.js#317 (comment)

Pretty much like yours @justinfagnani while using package.json as sorts of importmap to circumvent that you cannot do a normal import * as vscode from 'vscode' in ESM.

VS Code just updating Electron probably isn't automatically going to allow for standard JS module extension, and it probably isn't necessary since current VS Code can already load JS modules via dynamic import().

What about Web extensions (extensions that can run in browser IDEs like vscode.dev and github.dev)?
Would this update allow us to have multiple module files instead of having to use webpack? From the docs:

[...] The script runs in the web extension host in a Browser WebWorker environment. 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. importScripts is not available as well. As a consequence, the code must be packaged to a single file.
  • The VS Code API can be loaded via the pattern require('vscode'). This will work because there is a shim for require, but this shim cannot be used to load additional extension files or additional node modules. It only works with require('vscode').

#196941
The next Electron is 27 not 28...

As an update here, until this is supported natively, I've had pretty good success with both esbuild and packer/swc in converting ESM to CJS automatically during bundling, including synthetic default imports and whatnot.

I personally am desperate to make typescript + vite work for VSCode extension development.

It seems that any tsconfig.json (https://github.com/Malix-off/VSC-Extension-StashFlow/blob/dev/tsconfig.json) and vite.config (https://github.com/Malix-off/VSC-Extension-StashFlow/blob/dev/vite.config.ts) I tried didn't worked

I think it might be tied to that issue.
Do you guys found a workaround?

Update:

  1. ESNext TS + Vite works now!
    Thanks a lot to @rxliuli for https://github.com/Malix-off/VSC-Extension-StashFlow/pull/4 ❀️

#196941 The next Electron is 27 not 28...

The next one is now Electron v28.
Progress can be tracked here: #201935

We should be able to close this issue very soon! πŸ˜„

I personally am desperate to make typescript + vite work for VSCode extension development.

It seems that any tsconfig.json (https://github.com/Malix-off/VSC-Extension-StashFlow/blob/dev/tsconfig.json) and vite.config (https://github.com/Malix-off/VSC-Extension-StashFlow/blob/dev/vite.config.ts) I tried didn't worked

I think it might be tied to that issue. Do you guys found a workaround?

Update:

  1. ESNext TS + Vite works now!
    Thanks a lot to @rxliuli for chore: Update vite config of build cjs output Malix-off/VSC-Extension-StashFlow#4 ❀️

FYI pretty sure this won't work inside a vscode runner with vscode-test, because the vitest runner is ES only.

I didn't use vitest, so can't confirm

Looks like #203956 was just merged in main, so the next one of the next Insiders builds will be based on Electron 28 (probably after the endgame release from the stable branch is verified).

Looks like #203956 was just merged in main, so the next one of the next Insiders builds will be based on Electron 28 (probably after the endgame release from the stable branch is verified).

Igor Dimitrijević (igorskyflyer) shared an image.

image

Great news, the latest Insiders build includes Electron 28!

A big step forward, but now the VSCode loader needs to support ESM as well before we can use it in extensions.

I'm not familiar enough with js, so I don't know how much of this is still relevant, but an issue has been waiting in the vscode-loader repo at microsoft/vscode-loader#36 for a while. Maybe others do have expertise in that area and can chime in.

So the answer is we're still mid-way. The base platform now supports ES modules, but the VS Code infrastructure doesn't.
Correct?

Btw, there is also the question of Web Extensions to consider.

IIRC as of now we compile them using CJS. And I suppose the base framework needs to be updated to handle ESM imports.

I have a project that depends on being able to load ESM into VSCode extensions, which I assume also means that VSCode extensions themselves need to be ESM.

I'm considering downgrading the project to CJS.

Should I wait? Is a fix to this weeks away or months... or more?

Hi folks β€” can you confirm dynamic imports should work in VSCode extensions? When running my code for production (i.e. it's packaged and installed) I'm getting an Invalid host defined options error:

[error] TypeError: Invalid host defined options
    at Object.module.exports.<computed> [as createServer] (/Users/douges/.vscode/extensions/triplex.triplex-vscode-0.0.1/node_modules/vite/index.cjs:23:36)

Which, when zooming to that line of code, is this highlighted line:

// vite/index.cjs
asyncFunctions.forEach((name) => {
  module.exports[name] = (...args) =>
+    import('./dist/node/index.js').then((i) => i[name](...args))
})

Leading me to believe it's not supported. Could it be because Vite has this in a cjs file? Something else?

Edit: I've seen microsoft/vscode-loader#36. Seems quite relevant and matches my experience.

@metawrap-dev imo you should switch to CJS.

Don't make plans based on what you read on issues. Not the first time highly requested features take years to land.

@itsdouges @metawrap-dev as of today, the general recommendation is to use a bundler like esbuild (recommended) or rollup and use their features/plugins to convert ES modules to CJS automatically. They can even translate default imports and you can use import syntax in your code which will get translated to the require syntax by the bundler, so when ESM is available you don't have to go back and change all that.

Generally have had no issues with this, the only major roadblock I've seen is you can't run E2E in-vscode testing using testing frameworks that require ESM such as vitest, but mocha is the de-facto standard used by everything including the official vscode test cli and you can still run tests that don't need/reference the vscode api or mock it in whatever framework you want.

@itsdouges @metawrap-dev as of today, the general recommendation is to use a bundler like esbuild (recommended) ...

Thanks. That's exactly what I did as a workaround. I am glad that you have confirmed the choice,

Can anyone tell me, is there any possibility of importing a esm dependency in a vscode extension?

You can import an ESM package, but you then have to bundle as CJS. This makes the whole experience considerably worse, as the restart cycles during development are longer, and most of the times source maps don't even work.

Why is this in not "On Deck" but "Backlog"?

c.f.
#160416
#226260

There has ever been no one mentioning a concrete error message.

An extension using Pyodide:

#197329

Prettier (v10.0.0):

TypeError: A dynamic import callback was not specified.
	at importModuleDynamicallyCallback (node:internal/modules/esm/utils:228:9)
	at Object.<anonymous> (c:\Users\tatsu\.vscode\extensions\esbenp.prettier-vscode-10.0.0\node_modules\prettier\index.cjs:600:23)
	at u._compile (c:\Users\tatsu\AppData\Local\Programs\Microsoft VS Code\resources\app\out\bootstrap-fork.js:2:1257)
	at Module._extensions..js (node:internal/modules/cjs/loader:1432:10)
	at Module.load (node:internal/modules/cjs/loader:1215:32)
	at Module._load (node:internal/modules/cjs/loader:1031:12)
	at c._load (node:electron/js2c/node_init:2:13801)
	at E._load (c:\Users\tatsu\AppData\Local\Programs\Microsoft VS Code\resources\app\out\vs\workbench\api\node\extensionHostProcess.js:177:6051)
*snip*
 600   β”‚ var prettierPromise = import("./index.mjs");

In VS Code Exploration available from #160416 (comment), the error message is changed:
The execution of .mjs file itself looks successful.

Cannot find module 'c:\Users\WDAGUtilityAccount\.vscode-exploration\extensions\esbenp.prettier-vscode-10.0.0\node_modules\prettier\internal\internal.mjs' imported from c:\Users\WDAGUtilityAccount\.vscode-exploration\extensions\esbenp.prettier-vscode-10.0.0\node_modules\prettier\index.mjs
Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'c:\Users\WDAGUtilityAccount\.vscode-exploration\extensions\esbenp.prettier-vscode-10.0.0\node_modules\prettier\internal\internal.mjs' imported from c:\Users\WDAGUtilityAccount\.vscode-exploration\extensions\esbenp.prettier-vscode-10.0.0\node_modules\prettier\index.mjs
    at finalizeResolution (node:internal/modules/esm/resolve:265:11)
    at moduleResolve (node:internal/modules/esm/resolve:940:10)
    at defaultResolve (node:internal/modules/esm/resolve:1176:11)
    at nextResolve (node:internal/modules/esm/hooks:866:28)
    at resolve (data:text/javascript;base64,CglleHBvcnQgYXN5bmMgZnVuY3Rpb24gcmVzb2x2ZShzcGVjaWZpZXIsIGNvbnRleHQsIG5leHRSZXNvbHZlKSB7CgkJaWYgKHNwZWNpZmllciA9PT0gJ2ZzJykgewoJCQlyZXR1cm4gewoJCQkJZm9ybWF0OiAnYnVpbHRpbicsCgkJCQlzaG9ydENpcmN1aXQ6IHRydWUsCgkJCQl1cmw6ICdub2RlOm9yaWdpbmFsLWZzJwoJCQl9OwoJCX0KCgkJLy8gRGVmZXIgdG8gdGhlIG5leHQgaG9vayBpbiB0aGUgY2hhaW4sIHdoaWNoIHdvdWxkIGJlIHRoZQoJCS8vIE5vZGUuanMgZGVmYXVsdCByZXNvbHZlIGlmIHRoaXMgaXMgdGhlIGxhc3QgdXNlci1zcGVjaWZpZWQgbG9hZGVyLgoJCXJldHVybiBuZXh0UmVzb2x2ZShzcGVjaWZpZXIsIGNvbnRleHQpOwoJfQ==:13:10)
    at nextResolve (node:internal/modules/esm/hooks:866:28)
    at Hooks.resolve (node:internal/modules/esm/hooks:304:30)
    at handleMessage (node:internal/modules/esm/worker:196:24)
    at Immediate.checkForMessages (node:internal/modules/esm/worker:138:28)
    at process.processImmediate (node:internal/timers:478:21)

Update: a newer build is available: #226399 (comment)

Update: ESM will be enabled since 1.94 (Insiders since early next month)
See #226260