Skip to content

Have @babel/core lazy-load all dependencies and make @babel/register not explode because of that#7588

Merged
loganfsmyth merged 4 commits intobabel:masterfrom
loganfsmyth:register-cycle-fixes
Mar 25, 2018
Merged

Have @babel/core lazy-load all dependencies and make @babel/register not explode because of that#7588
loganfsmyth merged 4 commits intobabel:masterfrom
loganfsmyth:register-cycle-fixes

Conversation

@loganfsmyth
Copy link
Copy Markdown
Member

Q                       A
Fixed Issues? Fixes #5702 (but not primary goal)
Patch: Bug Fix?
Major: Breaking Change?
Minor: New Feature?
Tests Added + Pass? Yes
Documentation PR
Any Dependency Changes?
License MIT

There is some serious craziness in here for @babel/register sooooo sorry 😬 Essentially we want to be able to load babel/core with lazy requires safely in @babel/register, and that's dangerous/broken if register were left as-is because calling babel.transform could then actually trigger the require hook again causing a cycle.

Triggering a cycle isn't guaranteed to be an issue, since you could imagine a world where someone does:

// .babelrc
{
  presets: ['@babel/env'],
  overrides: [{
    exclude: "./my-custom-plugin",
    plugins: [
      "./my-custom-plugin",
    ],
  }],
}

to compile a project with some custom plugin, while also compiling that plugin itself with a different config.

To achieve all that while still having @babel/core kick off require calls at random times during transformation, this PR makes @babel/register much smarter about how it loads @babel/core. You can almost think of it like it loads it with its own loader context that explicitly tracks while files it has loaded. By tracking all that, we can explicitly skip those files in the require hook, avoiding trying to compile files that are necessary for the compilation itself.

There is then a small hole exposed in that loader logic so that when @babel/core loads .babelrc.jss, plugins, and presets, it will not track those, so that they actually do get passed to the require hook. This means it is up to the user to ensure that their Babel config does not introduce dependencies. We also have existing logic in @babel/core to show helpful error messages when there are cycles detected in config files/plugins/presets.

The end result of this PR is that @babel/core can lazy-load everything and all the rest of babel-register pretty much ends up behaving like it did before.

Why!

The end goal of this PR is the lazy-loading. With PR's like #7472 encouraging tooling to load @babel/core to check for config, this means we need to make loading core as fast as possible. Currently, it is the exact opposite of that. A single call to require("@babel/core") on my machine for instance takes ~250-350ms. With this PR, it now takes ~25-30ms on my machine.

This brings down the total time for a call to require("@babel/core").loadPartialConfig() dramatically, which should make it easier for people to adopt over trying to implement the logic themselves.

@loganfsmyth loganfsmyth force-pushed the register-cycle-fixes branch 2 times, most recently from 6ff42db to 52dac67 Compare March 20, 2018 05:03
@babel-bot
Copy link
Copy Markdown
Collaborator

babel-bot commented Mar 20, 2018

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

@babel-bot
Copy link
Copy Markdown
Collaborator

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

}

// Ensure that require.resolve and other properties are carried over.
return Object.assign(babelRegisterRequire, req);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

They're enumerable? :o

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Seems like!

__filename,
)}).wrap(require, __filename, ${uid}, ${inRoot})`;

return `return (function(require){${code}\n}).call(this, ${override});`;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

top level return is icky 😅

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah for some reason I was thinking it was needed, but it isn't is it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

wonder how this affects the error messages (although that might just be messed up already)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

They'll be an extra scope in the debugger if you pause, but otherwise I don't think it will affect anything.

inRoot: boolean,
) => { exports: mixed } | void,
) {
const files = new Set();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The set seems to be write-only?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It ends up exported and used in /node.js to skip them.

@xtuc xtuc added the PR: Internal 🏠 A type of pull request used for our changelog categories label Mar 22, 2018
@hzoo
Copy link
Copy Markdown
Member

hzoo commented Mar 23, 2018

Were you able to test this? should probably run locally before next release then like #6134. For compiling with lazy and also new register although I guess this is a fix for the previous time we tried this.

@loganfsmyth
Copy link
Copy Markdown
Member Author

The tests for babel-node fail without this logic in babel-register so I'm relatively confident that it works well.

The main question for me right now, which I'm going back and forth on, is whether we care about the case of re-entrant configs. And if we do (which I think I do?), should it be something we require users to opt into?

For instance right now people often do ignore: [] to have babel-register process every file, which because we allow re-entrant plugins, means that this triggers a cyclical error because loading the preset itself will trigger an error.

I think I'm personally leaning toward us making that behavior opt-in instead. We'd then instead require users to do

require("@babel/register")({
  reentrant: true
});

if they wanted register to compile a plugin/preset itself, before using that plugin/preset somewhere else in their build process.

It'd be a lot safer for the average user since we get issues like #7208 reasonably often.

Doing that would still require the logic in this PR, but I'd remove the overrideCallback entirely and instead we'd have to pass the actual plugin/config require callback to Babel itself during the call to .transform.

@hzoo
Copy link
Copy Markdown
Member

hzoo commented Mar 24, 2018

Ok, just needs docs on that and maybe think if that's the best word for it. I guess not sure what else it would be called but the intention is to compile configs themselves with register which affect itself? Or could we error with a nicer message when it happens without?

@loganfsmyth
Copy link
Copy Markdown
Member Author

Or could we error with a nicer message when it happens without?

I don't follow. If we error without the flag on you mean? Without the flag I don't think I'd expect it to error, and with the flag we already give some errors about reentrant code.

@hzoo
Copy link
Copy Markdown
Member

hzoo commented Mar 24, 2018

just wondering how someone would know to use the flag, if it's clear then never mind

@loganfsmyth
Copy link
Copy Markdown
Member Author

I think what I might do is tweak this PR to disallow reentrant plugins/presets entirely for now, and land that, and then do a second PR to add the reentrant option and the code that is necessary in babel-core to make it work.

@loganfsmyth
Copy link
Copy Markdown
Member Author

just wondering how someone would know to use the flag, if it's clear then never mind

Gotcha, yeah it's not super clear, but then I don't think it'll be super common anyway. It's not something that'll work with any of the other Babel integrations anyway since they don't do require hooks, so I think making it a special opt-in feature of babel-register seems fine. We'll definitely have to document it clearly though.

@loganfsmyth loganfsmyth force-pushed the register-cycle-fixes branch from 52dac67 to a5df709 Compare March 25, 2018 21:26
@loganfsmyth
Copy link
Copy Markdown
Member Author

Alright I decided to remove most of the complex logic that I had originally here for register in favor of totally disallowing re-entrant compilation. I added a note to the README on the recommended approach of you do want to compile a plugin up front, it should still cover 99% of the cases where people would actually want to live-compile a plugin/preset.

@loganfsmyth loganfsmyth merged commit a10c917 into babel:master Mar 25, 2018
@loganfsmyth loganfsmyth deleted the register-cycle-fixes branch March 25, 2018 22:24
@Nantris
Copy link
Copy Markdown

Nantris commented Apr 28, 2018

@loganfsmyth; note in the README? Not sure where you're referring to or if this is relevant to the issue I'm having anyway (#7208), but if you could point me towards that note I'd appreciate it. Thanks for all your hard work!

@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Oct 5, 2019
@lock lock bot locked as resolved and limited conversation to collaborators Oct 5, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: Internal 🏠 A type of pull request used for our changelog categories

Projects

None yet

Development

Successfully merging this pull request may close these issues.

It takes 380ms to print the version string

6 participants