Skip to content

Async rewrite #1093

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

Merged
merged 40 commits into from
May 20, 2025
Merged

Async rewrite #1093

merged 40 commits into from
May 20, 2025

Conversation

mediremi
Copy link
Contributor

@mediremi mediremi commented May 15, 2025

I made utils.findBinary async in order to allow us to get binary paths by dynamically importing the rescript package in the project root.

Every upstream function that needs to be async due to this change has been converted as well.

I've tested this on a v11 and a v12 project and everything seems to work fine.

@mediremi mediremi force-pushed the async-find-binary branch from 1ee276a to 95ca8da Compare May 15, 2025 22:39
@mediremi mediremi changed the title [WIP] async find binary Async rewrite May 15, 2025
Comment on lines +115 to +119
// TODO: export `binPaths` from `rescript` package so that we don't need to
// copy the logic for figuring out `target`.
const target = `${process.platform}-${process.arch}`;
const targetPackagePath = path.join(rescriptDir, "..", `@rescript/${target}/bin.js`)
const { binPaths } = await import(targetPackagePath);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately I couldn't just do import(rescriptDir) to get bin paths as the rescript package only exports ./lib/ and ./package.json in the exports field of its package.json.

We probably either want to export ./common/bins.js or have a default export that reexports binPaths.

As for @rescript/${target}, I had to append /bin.js to the import path for it to work as, like rescript, the per-platform binary packages have no default export.

Copy link
Member

Choose a reason for hiding this comment

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

Oh, you're right. I didn't re-export it because I wasn't sure the API from the compiler (it will be the first programmatic API, so...)

Copy link
Member

@cometkim cometkim May 15, 2025

Choose a reason for hiding this comment

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

As for @rescript/${target}, I had to append /bin.js to the import path for it to work as, like rescript, the per-platform binary packages have no default export.

There is the exports field. Importing the binary package without the /bin.js suffix should also work.

Don't forget to use the right compilerOptions.nodeResolution option in the tsconfig.json. Use NodeNext as long as possible, or Bundler.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Don't forget to use the right compilerOptions.nodeResolution option in the tsconfig.json. Use NodeNext as long as possible, or Bundler.

Ah I didn't realise we were bundling as CJS, this explains the error messages I was getting when trying to import without bin.js. I'll have a go at tweaking compilerOptions.nodeResolution to make this work 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried the following:

  • compilerOptions.moduleResolution values NodeNext and Bundler
  • Updating typescript to v5.8.3
  • ES2022 instead of ES1029

but I still can't get it to work :(

Copy link
Collaborator

Choose a reason for hiding this comment

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

This is still WIP, right? Or could we merge this and do the above in a follow up?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We can improve the dynamic import path in a followup PR 👍

@mediremi mediremi marked this pull request as ready for review May 15, 2025 23:37
@@ -7,7 +7,9 @@ import {
ResponseMessage,
} from "vscode-languageserver-protocol";
import fs from "fs";
import fsAsync from "fs/promises";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In a later PR we might want to replace sync fs functions with their promise-based alternatives.

@@ -255,9 +255,10 @@
},
"devDependencies": {
"@types/node": "^14.14.41",
"@types/semver": "^7.7.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added semver to make parsing the rescript version easier.

If adding this dependency is fine, then I can also refactor incrementalCompilation.figureOutBscArgs and utils.runAnalysisAfterSanityCheck to use it.

if (parseInt(project.rescriptVersion.split(".")[0] ?? "10") >= 11) {

let shouldUseBuiltinAnalysis =
rescriptVersion?.startsWith("9.") ||
rescriptVersion?.startsWith("10.") ||
rescriptVersion?.startsWith("11.") ||
[
"12.0.0-alpha.1",
"12.0.0-alpha.2",
"12.0.0-alpha.3",
"12.0.0-alpha.4",
].includes(rescriptVersion ?? "");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've done the proposed refactoring in this commit: d4cc19d

Copy link
Collaborator

Choose a reason for hiding this comment

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

Great! 👍

@cometkim
Copy link
Member

VSCode supports ESM-based extensions since very recently.
https://code.visualstudio.com/updates/v1_100#_esm-support-for-extensions

If we bump the target version and use fewer legacy dependencies, we can entirely switch to ESM.

Which accepts a fully asynchronous program, including top-level await, without bundler magic or TDZ-related bugs.

// Can't use the native bsb/rescript since we might need the watcher -w
// flag, which is only in the JS wrapper
binaryPath = path.join(rescriptDir, rescriptJSWrapperPath)
} else if (semver.gte(rescriptVersion, "12.0.0-alpha.13")) {
Copy link
Member

Choose a reason for hiding this comment

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

AFAIK, npm's default semver comparison doesn't work intuitively with pre-releases. This might need the includePrelease flag.

https://www.npmjs.com/package/semver#prerelease-tags

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right that we'll need that flag for range matching, so I added it in another place where we're checking major version numbers:

semver.satisfies(project.rescriptVersion as string, ">=11", { includePrerelease: true })

For semver.gte(rescriptVersion, "12.0.0-alpha.13") it won't be needed as we're not doing a range comparison.

@nojaf
Copy link
Contributor

nojaf commented May 16, 2025

Thank you for all this hard work!

@mediremi mediremi force-pushed the async-find-binary branch from f5bb3c6 to 28f1170 Compare May 18, 2025 19:56
@mediremi mediremi force-pushed the async-find-binary branch from 28f1170 to 22bff6d Compare May 18, 2025 19:56
Comment on lines +79 to +81
// If ReScript < 12.0.0-alpha.13, then we want `{project_root}/node_modules/rescript/{c.platformDir}/{binary}`.
// Otherwise, we want to dynamically import `{project_root}/node_modules/rescript` and from `binPaths` get the relevant binary.
// We won't know which version is in the project root until we read and parse `{project_root}/node_modules/rescript/package.json`
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍 Good comment!

Copy link
Collaborator

@zth zth left a comment

Choose a reason for hiding this comment

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

Looks great, thank you very much! Let me know if this is good to merge as is or if there are ongoing things that need to be handled first.

@mediremi
Copy link
Contributor Author

Thanks 😄 I'm happy for this to be merged as-is. We can try to figure out how to improve the dynamic import path in a followup PR 👍

@zth
Copy link
Collaborator

zth commented May 20, 2025

@mediremi awesome! One last thing - mind adding a changelog?

@mediremi
Copy link
Contributor Author

Changelog entry added 👍

@zth zth merged commit e045914 into rescript-lang:master May 20, 2025
6 checks passed
@zth
Copy link
Collaborator

zth commented May 20, 2025

Amazing work @mediremi!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants