You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Kevin Ramharak (@kevinramharak) thanks for the additional information. The main reason why I didn't invest a lot into this is that VS Code extensions can't use ESM in their main. It still needs to be CommonJS.
Would you be willing to share that prototype you did?
update the exports field in the package.json to enable ESM module resolution to work properly
I used esbuild because I'm familiar with it, and I had a hard time figuring out how the compiling of vscode-languageserver-node was set up. I expect a similar result when tsc is being used with the tsconfig set up to target ESM.
As for ESM support of VS Code extensions since version 1.100.x the NodeJS extension host supports ESM extensions:
I mainly used the example extension to try and get the client tests running, which did work, but there are a few more hurdles to get them to run properly and pass. I unfortunately did not commit those changes.
I do think that even if the extension is bundled with target CJS, the bundler being used can still optimize based on the input files being ESM. I know that esbuild does not tree shake CJS by default, because dynamic require calls cannot be optimized a lot, compared to static import statements.
I only figured out how the tsconfig-gen worked after I finished generating the output I needed for my own extension, which is unfortunate. I also haven't spend a lot of time getting the tests to work, as my extension seemed to run fine.
If you are interested in a PR, I can try and invest some time to see if I can get it to work with tsconfig-gen and get the tests and testbed running, as I haven't tried it much as the codebase is still unfamiliar to me.
Thanks again for the detailed reply. What I actually meant is that ESM is not working in the Web worker extension host. This extension host can be used in the Desktop app and the browser (the underlying problem is that import maps are not supported in a web worker). I can have another look but the problem is a little that the LSP libraries are used in the web worker extension host in various scenarios which I would still need to support. One work around is to import vscode with a require statement in these scenarios and ensure to use a bundler. But before I make that switch I must make sure all these scenarios are still supported.
But I will have a look at your changes and see if something has changed.
Understandable, I understand the need for supporting the other environments and wasn't expecting for a complete change from CJS to ESM. I was thinking more in line of the tsconfig.esm.publish.json approach the types package is already using. An additional build output that would be 'opt in' in the form of the exports defining import on top of require path resolution, was more what I was aiming at. As I do not expect the CJS builds to disappear for a long time (if ever) considering how slow and painful ESM adoption has been in the JS ecosystem.
Activity
dbaeumer commentedon Aug 4, 2025
Actually the new 10.0.0-next.13 version has export entry point which should help with tree shaking. Doesn't that work for you?
alex-kinokon commentedon Aug 4, 2025
It’s still CommonJS which doesn’t seem to work with tree shaking.
[-]ESM build[/-][+]Provide a pure ESM build[/+]kevinramharak commentedon Nov 7, 2025
I did a little bit of prototyping by forking the repo and building the following packages with
esbuild:vscode-jsonrpcvscode-languageserver-protocolvscode-languageclientvscode-languageserverThe results for bundling my (small) extension with
esbuildwith optimisations and minification turned on:server (30% decrease in size)
CJS:
ESM:
extension + client (40% decrease in size)
CJS:
ESM:
That adds up to a total decrease of ~37% for having proper tree-shakable modules as part of the distributed build.
I hope this explains the advantages of providing an ESM build and helps to visualize the issue.
esbuild analyze
I used https://esbuild.github.io/analyze/ to analyze the results of my builds. It also provides useful visualisations:
server
CJS:

ESM:

extension + client
CJS:

ESM:

dbaeumer commentedon Nov 10, 2025
Kevin Ramharak (@kevinramharak) thanks for the additional information. The main reason why I didn't invest a lot into this is that VS Code extensions can't use
ESMin their main. It still needs to beCommonJS.Would you be willing to share that prototype you did?
kevinramharak commentedon Nov 10, 2025
Dirk Bäumer (@dbaeumer)
The branch is here: https://github.com/regex-radar/vscode-languageserver-esm/tree/feature/generate-esm-output
The compare: main...regex-radar:vscode-languageserver-esm:feature/generate-esm-output
The changes needed are:
isolatedModulestotruein thetsconfig.jsonfiles, this will require all type exports to be changed to explicitexport typestatements.import x = require('a')statementsimport semverSatisfies = require('semver/functions/satisfies');import semverSatisfies = require('semver/functions/satisfies');modulesetting should bemoduleResolutionsetting should beexportsfield in thepackage.jsonto enable ESM module resolution to work properlyI used
esbuildbecause I'm familiar with it, and I had a hard time figuring out how the compiling ofvscode-languageserver-nodewas set up. I expect a similar result whentscis being used with thetsconfigset up to target ESM.As for ESM support of VS Code extensions since version
1.100.xthe NodeJS extension host supports ESM extensions:It is being tracked here:
With an example extension here:
I mainly used the example extension to try and get the client tests running, which did work, but there are a few more hurdles to get them to run properly and pass. I unfortunately did not commit those changes.
I do think that even if the extension is bundled with target CJS, the bundler being used can still optimize based on the input files being ESM. I know that
esbuilddoes not tree shake CJS by default, because dynamicrequirecalls cannot be optimized a lot, compared to staticimportstatements.I only figured out how the
tsconfig-genworked after I finished generating the output I needed for my own extension, which is unfortunate. I also haven't spend a lot of time getting the tests to work, as my extension seemed to run fine.If you are interested in a PR, I can try and invest some time to see if I can get it to work with
tsconfig-genand get the tests and testbed running, as I haven't tried it much as the codebase is still unfamiliar to me.dbaeumer commentedon Nov 11, 2025
Thanks again for the detailed reply. What I actually meant is that ESM is not working in the Web worker extension host. This extension host can be used in the Desktop app and the browser (the underlying problem is that import maps are not supported in a web worker). I can have another look but the problem is a little that the LSP libraries are used in the web worker extension host in various scenarios which I would still need to support. One work around is to import
vscodewith arequirestatement in these scenarios and ensure to use a bundler. But before I make that switch I must make sure all these scenarios are still supported.But I will have a look at your changes and see if something has changed.
kevinramharak commentedon Nov 11, 2025
Understandable, I understand the need for supporting the other environments and wasn't expecting for a complete change from CJS to ESM. I was thinking more in line of the
tsconfig.esm.publish.jsonapproach thetypespackage is already using. An additional build output that would be 'opt in' in the form of theexportsdefiningimporton top ofrequirepath resolution, was more what I was aiming at. As I do not expect the CJS builds to disappear for a long time (if ever) considering how slow and painful ESM adoption has been in the JS ecosystem.