Skip to content
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

feat(node): stabilize detecting if CJS via "type": "commonjs" in a package.json #26439

Merged
merged 28 commits into from
Nov 13, 2024

Conversation

dsherret
Copy link
Member

@dsherret dsherret commented Oct 20, 2024

Stabilizes --unstable-detect-cjs.

This will respect "type": "commonjs" in a package.json to determine if .js/.jsx/.ts/.tsx` files are CJS or ESM. If the file is found to be ESM it will be loaded as ESM though.

Breaking change to remove referrer based CJS/ESM assignment

An extremely important part of module resolution is the referrer should never impact if a file is considered ESM or CJS. This should be done solely based on the file itself without any other context (in other words, it should only be based on its location or its contents).

It seems that require(...) in Deno was treating .js modules outside a node_modules folder as CJS and then falling back to ESM. This means that Deno could potentially load the same module twice. This fix partly fixes #26438 but it seems like there's still something else going on still.

This bug fix will break some existing code, but it will also make some other code work. The code that gets broken by this can be fixed by adding an explicit "type": "commonjs" to the closest package.json file.

Todo:

Closes #26225
Closes #26742

@nathanwhit
Copy link
Member

We should check how this affects popular frameworks/libraries before merging

@dsherret dsherret added this to the 2.1.0 milestone Oct 21, 2024
@dsherret
Copy link
Member Author

Yeah I think we’ll have to wait to fix this one with the stabilization of unstable-detect-cjs

arnauorriols
arnauorriols previously approved these changes Oct 21, 2024
Copy link
Member

@arnauorriols arnauorriols left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dsherret dsherret changed the title fix(node): requiring a .js file in Deno code should be treated as ESM (except with --unstable-detect-cjs) feat(node): stabilize detecting if CJS via "type": "commonjs" in a package.json Nov 8, 2024
@dsherret dsherret changed the title feat(node): stabilize detecting if CJS via "type": "commonjs" in a package.json feat(node): stabilize detecting if CJS via "type": "commonjs" in a package.json Nov 8, 2024
@dsherret dsherret added the ci-draft Run the CI on draft PRs. label Nov 8, 2024
@dsherret dsherret marked this pull request as ready for review November 12, 2024 18:03
}
DenoSubcommand::Run(run_flags) => {
if run_flags.is_stdin() {
resolve_url_or_path("./$deno$stdin.ts", self.initial_cwd())?
resolve_url_or_path("./$deno$stdin.mts", self.initial_cwd())?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uses mts now so that these commands continue using esm even if the current directory has a "type": "commonjs"

@@ -797,6 +832,7 @@ impl FileSystemDocuments {
pub fn get(
&self,
specifier: &ModuleSpecifier,
is_cjs_resolver: &LspIsCjsResolver,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to split up data/storage and services that act on that data in the lsp. Passing around these "services" as method parameters (horizontal movement) is bad because it makes introducing new functionality tedious, verbose, and noisy. I've continued doing this here though because it would be too much of a refactor for this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's reasonable - maybe open an issue to refactor it and add it to Q4 project board?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened #26847

@@ -182,6 +185,8 @@ impl LspScopeResolver {
.resolve_req_reference(
&req_ref,
&referrer,
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
NodeModuleKind::Esm,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is ok for now :)

Ideally we'd store this keyed on NodeModuleKind as well because resolution could differ.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open an issue for this TODO, especially if you have an example program that will not work correctly in the LSP

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I opened #26846

#[derive(Debug)]
pub struct LspCjsTracker {
cjs_tracker: Arc<CjsTracker>,
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use the cjs tracker in the lsp anymore. Instead we inspect the state of the documents each time.

@@ -732,7 +735,7 @@ delete Object.prototype.__proto__;
/** @type {Array<[string, ts.Extension] | undefined>} */
const resolved = ops.op_resolve(
base,
isCjsCache.get(base) ?? false,
containingSourceFile?.impliedNodeFormat === ts.ModuleKind.CommonJS,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doing this is better.

@dsherret dsherret removed the ci-draft Run the CI on draft PRs. label Nov 12, 2024
ext/node/ops/require.rs Outdated Show resolved Hide resolved
cli/args/flags.rs Show resolved Hide resolved
cli/args/mod.rs Outdated
self.flags.unstable_config.detect_cjs
|| self.workspace().has_unstable("detect-cjs")
pub fn detect_cjs(&self) -> bool {
self.workspace().package_jsons().next().is_some()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this correct? Doesn't it check for existence of package.json and not consulting if it has type: "commonjs"?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only to activate the feature of it searching for the closest package.json files on every ambiguous module load. That means only projects with a package.json get opted into this feature and other Deno programs won't have the perf penalty of the extra work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense, consider adding a docstring here to avoid confusion in the future.

@@ -182,6 +185,8 @@ impl LspScopeResolver {
.resolve_req_reference(
&req_ref,
&referrer,
// todo(dsherret): this is wrong because it doesn't consider CJS referrers
NodeModuleKind::Esm,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open an issue for this TODO, especially if you have an example program that will not work correctly in the LSP

cli/standalone/mod.rs Show resolved Hide resolved
Ok(None)
}
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixes #26742

The issue here is previously we happened to resolve these kind of entrypoints because it would try running the specifier as a CJS entrypoint with an "unknown" media type.

@dsherret dsherret enabled auto-merge (squash) November 13, 2024 14:32
@dsherret dsherret merged commit f091d1a into denoland:main Nov 13, 2024
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants