Repository: vscode
Issue: VSCode client libraries have invalid exports
Labels: feature-request,editor-core

<!-- ⚠️⚠️ Do Not Delete This! bug_report_template ⚠️⚠️ -->
<!-- Please read our Rules of Conduct: https://opensource.microsoft.com/codeofconduct/ -->
<!-- 🕮 Read our guide about submitting issues: https://github.com/microsoft/vscode/wiki/Submitting-Bugs-and-Suggestions -->
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
<!-- 🧪 Test using the latest Insiders build to see if your issue has already been fixed: https://code.visualstudio.com/insiders/ -->
<!-- 💡 Instead of creating your report here, use 'Report Issue' from the 'Help' menu in VS Code to pre-fill useful information. -->
<!-- 🔧 Launch with `code --disable-extensions` to check. -->

<!-- 🪓 If you answered No above, use 'Help: Start Extension Bisect' from Command Palette to try to identify the cause. -->
<!-- 📣 Issues caused by an extension need to be reported directly to the extension publisher. The 'Help > Report Issue' dialog can assist with this. -->


VSCode has various client libraries that exist in separate repositories. Most of these packages have invalid package exports. As a result, it’s difficult to consume them in a package that is configured correctly. This means some packages can only be used if they are included via bundler hacks. Sometimes it’s the other way around, and they can be used with Node.js, but in a way they are no longer compatible with a bundler.

Currently this is blocking me from publishing a big MDX language server update (<https://github.com/mdx-js/mdx-analyzer/pull/340>).

I want to help remedy this situation. I have already created some issues and pull requests, but perhaps it’s best to organize this from a central issue, hence this issue. Based on my experience working with language servers, Monaco Editor, and the VSCode [list of useful Node.js modules](https://code.visualstudio.com/api/references/extension-manifest#useful-node-modules), I have assembled the list below. A checkmark (✓) indicates everything is ok. A cross (❌) indicates there is a problem with a link to an explanation below. If there is no symbol, that indicates there’s no support for that module format, which is fine.

It also appears VSCode itself doesn’t depend on any of these modules. But some are generated from the VSCode repository. As far as I can tell, this means there is no need to support AMD nor UMD. Possibly relevant are #160416 and #166033.

| Name                                                                                                                   | CJS                                                                | ESM                                  | Dependencies                                     | Notes / Related Issues                                                                                              |
| ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| [`@vscode/emmet-helper`](https://github.com/microsoft/vscode-emmet-helper)                                             | ✓                                                                  | [❌ Faux ESM](#faux-esm)             | ✓                                                |                                                                                                                     |
| [`@vscode/extension-telemetry`](https://github.com/Microsoft/vscode-extension-telemetry)                               | ✓                                                                  | [❌ Browser Field](#browser-field)   | ✓                                                |                                                                                                                     |
| [`@vscode/l10n`](https://github.com/microsoft/vscode-l10n/tree/main/l10n)                                              | ✓                                                                  | [❌ Browser Field](#browser-field)   | [❌ Bundled Dependencies](#bundled-dependencies) |                                                                                                                     |
| [`@vscode/test-electron`](https://github.com/microsoft/vscode-test)                                                    | ✓                                                                  |                                      | ✓                                                |                                                                                                                     |
| [`jsonc-parser`](https://github.com/microsoft/node-jsonc-parser)                                                       | [❌ UMD](#umd)                                                     | [❌ Faux ESM](#faux-esm)             | ✓                                                | <https://github.com/microsoft/node-jsonc-parser/pull/78>                                                            |
| [`monaco-editor`](https://github.com/microsoft/monaco-editor)                                                          |                                                                    | [❌ Faux ESM](#faux-esm)             | [❌ Bundled Dependencies](#bundled-dependencies) | Ships UMD, but it’s not exported.                                                                                   |
| [`monaco-editor-core`](https://www.npmjs.com/package/monaco-editor-core)                                               |                                                                    | [❌ Faux ESM](#faux-esm)             | [❌ Bundled Dependencies](#bundled-dependencies) | Ships UMD, but it’s not exported. Generated from the VSCode repository.                                             |
| [`request-light`](https://github.com/microsoft/node-request-light)                                                     | [❌ UMD](#umd)                                                     | [❌ Browser Field](#browser-field)   | [❌ Bundled Dependencies](#bundled-dependencies) | <https://github.com/microsoft/node-request-light/pull/24> <https://github.com/microsoft/node-request-light/pull/25> |
| [`vscode-css-languageservice`](https://github.com/microsoft/vscode-css-languageservice)                                | [❌ UMD](#umd)                                                     | [❌ Faux ESM](#faux-esm)             | ✓                                                |                                                                                                                     |
| [`vscode-html-languageservice`](https://github.com/microsoft/vscode-html-languageservice)                              | [❌ UMD](#umd)                                                     | [❌ Faux ESM](#faux-esm)             | ✓                                                |                                                                                                                     |
| [`vscode-json-languageservice`](https://github.com/microsoft/vscode-json-languageservice)                              | [❌ UMD](#umd)                                                     | [❌ Faux ESM](#faux-esm)             | ✓                                                | <https://github.com/microsoft/vscode-json-languageservice/issues/200>                                               |
| [`vscode-jsonrpc`](https://github.com/microsoft/vscode-languageserver-node/tree/main/jsonrpc)                          | ✓                                                                  | [❌ Browser Field](#browser-field)   | ✓                                                |                                                                                                                     |
| [`vscode-languageclient`](https://github.com/microsoft/vscode-languageserver-node/tree/main/client)                    | ✓                                                                  | [❌ Browser Field](#browser-field)   | ✓                                                |                                                                                                                     |
| [`vscode-languageserver`](https://github.com/microsoft/vscode-languageserver-node/tree/main/server)                    | ✓                                                                  | [❌ Browser Export](#browser-export) | ✓                                                |                                                                                                                     |
| [`vscode-languageserver-protocol`](https://github.com/microsoft/vscode-languageserver-node/tree/main/protocol)         | ✓                                                                  | [❌ Browser Field](#browser-field)   | ✓                                                |                                                                                                                     |
| [`vscode-languageserver-textdocument`](https://github.com/microsoft/vscode-languageserver-node/tree/main/textDocument) | [❌ UMD](#umd)<br>[❌ Browser Implies ESM](#browser-implies-esm)   | ✓                                    | ✓                                                |                                                                                                                     |
| [`vscode-languageserver-types`](https://github.com/microsoft/vscode-languageserver-node/tree/main/types)               | [❌ UMD](#umd)<br>[❌ Browser Implies ESM](#browser-implies-esm)   | ✓                                    | ✓                                                | <https://github.com/microsoft/vscode-languageserver-node/pull/1297>                                                 |
| [`vscode-markdown-languageservice`](https://github.com/microsoft/vscode-markdown-languageservice)                      | ✓                                                                  |                                      | ✓                                                |                                                                                                                     |
| [`vscode-nls`](https://github.com/microsoft/vscode-nls)                                                                | ✓                                                                  | [❌ Browser Field](#browser-field)   | ✓                                                | <https://github.com/microsoft/vscode-nls/pull/52> Generated from the VSCode repository.                             |
| [`vscode-uri`](https://github.com/microsoft/vscode-uri)                                                                | [❌ UMD](#umd) <br> [❌ Browser Implies ESM](#browser-implies-esm) | ✓                                    | [❌ Bundled Dependencies](#bundled-dependencies) | <https://github.com/microsoft/vscode-uri/pull/38> Generated from the VSCode repository.                             |

#### Faux ESM

Faux ESM means a package publishes ESM syntax, but it is not marked as such.

There are two ways to mark Node.js code as ESM:

1. The ESM files use the `.mjs` extension, and `.d.mts` for file extensions.
2. If `package.json` contains the following, `.js` and `.d.ts` files will be treated as ESM.
   ```jsonc
   {
     "type": "module"
   }
   ```

Dual published faux ESM packages often use the non-standard `"module"` field in `package.json`. Many module bundlers respect this field, causing imports to behave different when bundled from when using it with Node.js. Instead, packages should use package [`"exports"`](https://nodejs.org/api/packages.html#package-entry-points) point to the correct location for CJS or ESM.

```jsonc
{
  "exports": {
    ".": {
      "import": "./path/to/esm/entrypoint.mjs",
      "default": "./path/to/cjs/entrypoint.cjs"
    }
  }
}
```

Also when using ESM, imports must include the file extension.

#### UMD

UMD is compatible with CJS. Node.js supports importing CJS as named imports, if the CJS is structured properly.

For example, the following CJS file:

```js
module.exports.a = 'a'
exports.b = 'b'
```

Can be imported as such from ESM:

```js
// 1)
import module from './commonjs.cjs'
// 2)
import { a, b } from './commonjs.cjs'

console.log({ a, b, module })
```

When using UMD, typically only 1) works, not 2). When dual publishing this with [faux ESM](#faux-esm), often only 2) works in bundlers, not 1).

Also this prevents tree shaking.

#### Bundled Dependencies

Some packages bundle their dependencies. When bundling dependencies, this means they are included in the package. If another package then depends on the bundled dependency, this means it gets duplicated, increasing the bundle size for the end user.

#### Browser Field

Some packages use the `"browser"` field in `package.json`. The intention is to tell bundlers to resolve a different module when targeting the browser. This is a non-standard field. The correct way to tell bundlers how to resolve imports for a browser, is using package [`"exports"`](https://nodejs.org/api/packages.html#package-entry-points).

```jsonc
{
  "exports": {
    ".": {
      "browser": "./path/to/browser/entrypoint.js",
      "default": "./path/to/node/entrypoint.js"
    }
  }
}
```

Likewise, packages can define where packages are imported from conditionally using package [`"imports"`](https://nodejs.org/api/packages.html#subpath-imports):

```jsonc
{
  "imports": {
    // Beware of https://github.com/nodejs/node/issues/49257 for CJS
    "#path": {
      "browser": "path-browserify",
      "default": "path"
    }
  }
}
```

#### ESM Implies Browser

Node.js can use ESM. Module bundlers can resolve imports to CJS, regardless of whether they target Node.js or browsers. Some packages incorrectly assume ESM is for the browser, and ESM is for Node.js. Instead, packages should use [`"exports"`](https://nodejs.org/api/packages.html#package-entry-points) (See [Browser Field](#browser-field)).

### Browser Implies ESM

Some packages resolve the `browser` condition to an ESM field. This means `require()` calls may lead to ESM, which is non-standard behaviour and may lead to unexpected error.

#### File Does Not Exist

This package points to a file that does not exist.

#### Default Export Compiled to CJS

Although I didn’t find any packages to which this apply, it’s good to add this to a list of pitfalls regarding CJS/ESM interop. When a default export is compiled to CJS, it becomes:

```js
function someExport() {}

exports.default = someExport
```

This means that from native ESM, usage becomes:

```js
import someExport from './commonjs.cjs'

someExport.default()
```

This is typically underable, so it’s best to avoid compiling default exports to CJS.

#### Dual Publishing

Various package dual publish ESM and CJS. Many people assume this has benefits, but they don’t truly understand the details. Really this mostly has downsides.

- Dual publishing complicates the build process.
- TypeScript wasn’t meant for dual publishing and becomes less helpful.
- Because of ESM, you can’t use CJS APIs (`require`, `module`, `__dirname`, `__filename`).
- Because of CJS, you can’t use ESM features (import statements, export statements, top-level await).
- Because of CJS, you can’t use ESM-only dependencies.
- You need to dual-publish TypeScript types too, and make sure they are correct.

Generally people are better off picking one module format. Choose CJS if you want your dependants to be abel to use CJS without using dynamic imports. Choose ESM if you want to help push the JavaScript ecosystem forward to a single module syntax.