-
Notifications
You must be signed in to change notification settings - Fork 30k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
VSCode client libraries have invalid exports #192144
Comments
Thanks for assembling this list! What is your suggestion to avoid dual publishing? |
Recently an update to unofficial lib
Could you assess the library for its export validity? |
Avoiding dual publishing means to publish either CJS or ESM, not both. CJSTo publish CJS only, requires the following setup: // package.json
{
// This is default
// "type": "commonjs",
// One of these should be specified. "main" points to the package entry point. "exports" points to the package entry point and makes everything else private.
// "main": "./dist/index.js",
// "exports": "./dist/index.js",
// This is only needed to support the legacy "node10" module resolution, formerly known as "node", if "exports" is used.
// I would omit it, as no one should be using that anymore
// "types": "./dist/index.d.ts"
"scripts": {
"prepack": "tsc -b"
}
} // tsconfig.json
{
"compilerOptions": {
"module": "nodenext",
// Ideally one of the following. I like the latter, I think the TypeScript team recommends the former.
// "verbatimModuleSyntax": true,
// "esModuleInterop": false,
}
} When not writing default exports, this works exactly as expected in from both CJS and ESM dependants. The downside is, this means we can’t use ESM dependencies. This removes support from AMD/UMD. This is technically breaking, but personally I don’t think anyone is affected by this. In fact, this solves an annoying warning that some build tools show: ESMTo publish ESM only, requires the following setup: // package.json
{
"type": "module",
// One of these should be specified. "main" points to the package entry point. "exports" points to the package entry point and makes everything else private.
// "main": "./dist/index.js",
// "exports": "./dist/index.js",
// This is only needed to support the legacy "node10" module resolution, formerly known as "node", if "exports" is used.
// I would omit it, as no one should be using that anymore
// "types": "./dist/index.d.ts"
"scripts": {
"prepack": "tsc -b"
}
} // tsconfig.json
{
"compilerOptions": {
"module": "nodenext",
}
} The biggest upside is that you can use ESM dependencies. Also the existence of two module formats in the JavaScript JavaScript ecosystem is hard to deal with for many users. Personally I believe ESM is the future and it’s best to bite the bullet and go ESM only. This means users of the package also need to go ESM-only. Currently VSCode extensions don’t support ESM. But I don’t think this is a problem. As far as I know VSCode extensions should bundle their dependencies, so the module format doesn’t really matter. Dual PublishingDespite the quirks of dual publishing, it is possible to do so. This means using // package.json
{
"exports": {
"import": "./dist/esm/index.js",
"default": "./dist/cjs/index.js"
},
// This is only needed to support the legacy "node10" module resolution, formerly known as "node", if "exports" is used.
// I would omit it, as no one should be using that anymore
// "types": "./dist/index.d.ts"
"scripts": {
// The fix-esm script writes a `package.json` file as in https://github.com/microsoft/vscode-languageserver-node/pull/1297/files#diff-4ac8cc0c9b1dd409f838c35220acc380491c4aef20dfba60844c576f1354158a
"prepack": "tsc -b && node ./scripts/fix-esm"
}
} So, some breaking changes are:
|
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 (mdx-js/mdx-analyzer#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, 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.
@vscode/emmet-helper
@vscode/extension-telemetry
@vscode/l10n
@vscode/test-electron
jsonc-parser
monaco-editor
monaco-editor-core
request-light
vscode-css-languageservice
vscode-html-languageservice
vscode-json-languageservice
vscode-jsonrpc
vscode-languageclient
vscode-languageserver
vscode-languageserver-protocol
vscode-languageserver-textdocument
❌ Browser Implies ESM
vscode-languageserver-types
❌ Browser Implies ESM
vscode-markdown-languageservice
vscode-nls
vscode-uri
❌ Browser Implies ESM
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:
.mjs
extension, and.d.mts
for file extensions.package.json
contains the following,.js
and.d.ts
files will be treated as ESM.Dual published faux ESM packages often use the non-standard
"module"
field inpackage.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"
point to the correct location for CJS or ESM.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:
Can be imported as such from ESM:
When using UMD, typically only 1) works, not 2). When dual publishing this with 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 inpackage.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"
.Likewise, packages can define where packages are imported from conditionally using package
"imports"
: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"
(See Browser Field).Browser Implies ESM
Some packages resolve the
browser
condition to an ESM field. This meansrequire()
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:
This means that from native ESM, usage becomes:
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.
require
,module
,__dirname
,__filename
).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.
The text was updated successfully, but these errors were encountered: