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

[babel 8] Move ESLint parsing to a Worker #13199

Merged

Conversation

nicolo-ribaudo
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo commented Apr 24, 2021

Q                       A
Fixed Issues?
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR Link
Any Dependency Changes?
License MIT

One of the main blockers for moving to ESM is that ESLint doesn't support asynchronous parsers yet (ref: eslint/eslint#14295).

This PR works around the problem by moving @babel/core usage inside @babel/eslint-parser inside a service worker: by doing that it doesn't matter that loading @babel/core is asynchronous, because we can always communicate with the worker synchronously. This can also be easily extended to allow users to use babel.config.mjs files with @babel/eslint-parser (currently it throws an error).

This PR touches all the files in @babel/eslint-parser, but the only new things are src/client.cjs, src/worker/index.cjs and src/worker/babel-core.cjs. Everything else is just moved around.

(EDIT: See #13199 (comment)) This PR causes (when BABEL_8_BREAKING is true) a performance drop of 10% when linting our repo with eslint-plugin-prettier disabled (5% when it's enabled). For this reason, I'll follow up with a different approach: rather than passing the input source code to the worker and getting back the AST (which is big and expensive to clone across workers), I want to only give the worker the input filename and make it return the set of options that we need to give to @babel/parser. By doing so, we can keep config/plugins resolution async while avoiding passing the big AST object around. However, this

@babel-bot
Copy link
Collaborator

babel-bot commented Apr 24, 2021

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/46140/

@codesandbox-ci
Copy link

codesandbox-ci bot commented Apr 24, 2021

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 3febba7:

Sandbox Source
babel-repl-custom-plugin Configuration
babel-plugin-multi-config Configuration

@nicolo-ribaudo nicolo-ribaudo force-pushed the babel-8-eslint-parser-worker branch from ad943ff to e76b293 Compare May 7, 2021 10:44
@nicolo-ribaudo
Copy link
Member Author

nicolo-ribaudo commented May 7, 2021

I moved parsing from the worker to the main thread when using the default @babel/parser: by doing so we don't have to pass the whole AST between workers, but only the parser options.
When using a custom parser we still parse in the worker thread, since we cannot pass the parser function to the main thread (tested by https://github.com/babel/babel/blob/main/eslint/babel-eslint-tests/test/integration/duplicated-babel-parser.js).

The negative performance impact of using a separate worker is gone: running time yarn lint on main gives 26,32s user 0,85s system 143% cpu 18,874 total, while running time BABEL_8_BREAKING=true yarn lint on this PR gives 26,10s user 0,99s system 150% cpu 18,006 total/26,41s user 0,87s system 151% cpu 17,990 total. These measurements are done without eslint-plugin-prettier, since it makes ESLint way slower hiding the parser performance effects.

I converted the files to .cjs since when moving to native ESM we'll want to keep these as CJS, otherwise they won't work with ESLint.

The last commit isn't strictly required, but since this PR makes it easy to load the config asynchronously I removed the restriction that disallows .mjs config files when using @babel/eslint-parser. Since it's a new feature, we could decide to enable it behind an option in Babel 7 (but it can be done in a separate PR).


if (process.env.BABEL_8_BREAKING) {
return babel
.loadPartialConfigAsync(parseOptions)
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we load partial config when options.requireConfigFile is false?

Copy link
Member Author

@nicolo-ribaudo nicolo-ribaudo May 7, 2021

Choose a reason for hiding this comment

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

I tried to match the current behavior, and I think that when requireConfigFile: false it still loads the config file if it's present, but it doesn't throw if it's not. To disable config file loading, you need to pass babelOptions: { configFile: false }.

eslint/babel-eslint-parser/src/worker/index.cjs Outdated Show resolved Hide resolved
eslint/babel-eslint-parser/src/client.cjs Show resolved Hide resolved
@nicolo-ribaudo nicolo-ribaudo force-pushed the babel-8-eslint-parser-worker branch from 374d10e to 45f120b Compare May 10, 2021 16:45

let send;

exports.getVersion = sendCached("GET_VERSION");
Copy link
Member

Choose a reason for hiding this comment

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

Could we extract these action types to a constant that we reuse everywhere so we don't risk bugs due to typos?

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 moved them to this file (rather than always using the string) so that we only have to care about those strings in the two "communication files" (worker client and server). Everywhere else we use getVersion() etc.
Also, https://github.com/babel/babel/pull/13199/files#diff-62c6b0f19ec3e004fffbecf4acab02bc005e538ea3c08e9bd558def1f28634c7R32 throws so that we catch any invalid string early.

@nicolo-ribaudo nicolo-ribaudo force-pushed the babel-8-eslint-parser-worker branch from b597589 to 3febba7 Compare May 14, 2021 22:10
Copy link
Member

@kaicataldo kaicataldo left a comment

Choose a reason for hiding this comment

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

🎉

@nicolo-ribaudo nicolo-ribaudo merged commit 9d620c2 into babel:main May 17, 2021
@nicolo-ribaudo nicolo-ribaudo deleted the babel-8-eslint-parser-worker branch May 17, 2021 15:04
@nicolo-ribaudo
Copy link
Member Author

I'll try again moving to esm and let's see how far I can get this time 🤞

@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Aug 17, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 17, 2021
@nicolo-ribaudo nicolo-ribaudo added this to the v8.0.0 milestone Aug 8, 2023
@nicolo-ribaudo nicolo-ribaudo added PR: Breaking Change 💥 A type of pull request used for our changelog categories for next major release and removed babel 8 labels Aug 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: eslint outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: Breaking Change 💥 A type of pull request used for our changelog categories for next major release PR: Needs Docs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants