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

Add a 'lazy' options to modules-commonjs #6952

Merged
merged 2 commits into from
Dec 18, 2017

Conversation

loganfsmyth
Copy link
Member

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

Not inspired by the code from https://github.com/zertosh/babel-plugin-transform-inline-imports-commonjs itself, but certainly inspired by talking to @zertosh about it in the past, and now feeling like we might want to use it for Babel's own codebase. It's pretty trivial to include in our helper-module-transforms so I figured why not.

Allow import bindings to lazy-initialize themselves the first time they are accessed, instead of up front at file load time.

@babel-bot
Copy link
Collaborator

babel-bot commented Dec 1, 2017

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

@hzoo hzoo added PR: New Feature 🚀 A type of pull request used for our changelog categories area: modules labels Dec 2, 2017
typeof lazy !== "function" &&
(!Array.isArray(lazy) || !lazy.every(item => typeof item === "string"))
) {
throw new Error(`.lazy must be a boolean, array of strings, or a function`);
Copy link
Member

Choose a reason for hiding this comment

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

Is the . at the beginning intentional?

Copy link
Member

Choose a reason for hiding this comment

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

It's because it's a key in the configuration.

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 guess we don't really have a good naming scheme for these errors. I just included it since it is an object property. I can change it.

Copy link
Member

Choose a reason for hiding this comment

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

At least this is consistent with the other error messages used in Babel but it's not super clear to me. What about: "the key 'lazy' in the Babel configuration must be a boolean". Can be done in a second PR. That reminds my that I still have a PR to add code frames to configuration issues. I can continue my work, what do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

What about: "the key 'lazy' in the Babel configuration must be a boolean".

I don't think it's much clearer than what I have here, but I do think we could do better in general with work focused on making the error messages give better feedback.

That reminds my that I still have a PR to add code frames to configuration issues. I can continue my work, what do you think?

Not sure I follow. What code frames could we show for this case? Do you mean showing information about the config file itself?

@@ -132,14 +143,26 @@ export default function(api, options) {

let header;
if (isSideEffectImport(metadata)) {
if (metadata.lazy) throw new Error("Assertion failure");
Copy link
Member

Choose a reason for hiding this comment

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

I guess this should never really happen, but maybe we could make the error a little bit more descriptive?

Copy link
Member Author

Choose a reason for hiding this comment

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

For assertions I don't usually bother. I'd just assert(!metadata.lazy) but usually that isn't enough to make Flowtype happy so I've kind of got into the habit of doing it like this. I really just threw this in for us.

Local paths are much more likely to have circular dependencies, which may break if loaded lazily,
so they are not lazy by default, whereas dependencies between independent modules are rarely cyclical.

* `Array<string>` - Lazy-initialize all imports with source matching one of the given strings.
Copy link
Member

@danez danez Dec 2, 2017

Choose a reason for hiding this comment

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

do we need Array<string>, do you think it is a common case? I guess boolean | (string) => boolean would capture this case too. I was also thinking about more cases like "enable really everything" or "enable everything with a blacklist", but that can all be handled with the callback.

Copy link
Member

Choose a reason for hiding this comment

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

The disadvantage of the callback is that it forces the user to use .babelrc.js instead of .babelrc

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah the .babelrc/package.json#babel case was the reason I added the array. Seems like a whitelist was easy enough to support that I'd just throw it in.

} else if (typeof lazy === "function") {
metadata.lazy = lazy(source);
} else {
throw new Error(`'lazy' must be a boolean, string array, or function`);
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo Dec 2, 2017

Choose a reason for hiding this comment

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

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo Dec 2, 2017

Choose a reason for hiding this comment

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

Actualy, can this error be thrown? It should already be thrown there.

Copy link
Member Author

Choose a reason for hiding this comment

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

One throws up front when you declare the plugin, which seemed like it would be best for users, this one throws inside the transform itself in case someone else decides to use helper-module-transforms and calls it wrong. I can make the errors the same though, that's true.

Copy link
Member

@xtuc xtuc left a comment

Choose a reason for hiding this comment

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

Awesome, that's really nice!

typeof lazy !== "function" &&
(!Array.isArray(lazy) || !lazy.every(item => typeof item === "string"))
) {
throw new Error(`.lazy must be a boolean, array of strings, or a function`);
Copy link
Member

Choose a reason for hiding this comment

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

It's because it's a key in the configuration.

@zertosh
Copy link
Member

zertosh commented Dec 3, 2017

Super cool! Question, why did you decide to go with always calling the memoized require rather than conditionally getting the previously stored require value? For compat with modules whose value is falsey ? If always calling the memoized require's perf is on-par or better then I'll deprecate babel-plugin-transform-inline-imports-commonjs

@loganfsmyth
Copy link
Member Author

@zertosh I honestly didn't think on it too much. If anyone wants to do some benchmarks to compare, and your approach of having a separate variable is noticeably faster, we can certainly do it. I went this route because it seemed like it'd be easier for people debugging code to just have a single name to reference, and because it seemed like this approach would be pretty easy for engines to inline, since the function never changes and is statically guaranteed to always return the same value.

Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

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

Really nice feature 🚀

@loganfsmyth loganfsmyth merged commit 44ea943 into babel:master Dec 18, 2017
@loganfsmyth loganfsmyth deleted the commonjs-lazy branch December 18, 2017 03:32
@jamesreggio
Copy link
Contributor

jamesreggio commented Feb 19, 2018

@zertosh @loganfsmyth — I did some empirical benchmarking of both in my React Native app and witnessed the new lazy option to be 24% faster than babel-plugin-transform-inline-imports-commonjs (with a variance of 5%).

It's possible that other changes in the Babel 7 version of the mainline commonjs plugin are to account for these improvements, but I'd say — from my limited perspective — that it's safe to sunset babel-plugin-transform-inline-imports-commonjs.

Thank you both for this feature. It's made a huge improvement in my cold-start time.

(Addendum: if anybody would like to try using babel-plugin-transform-inline-imports-commonjs with Babel 7, pull my branch here.)

@loganfsmyth
Copy link
Member Author

@jamesreggio Happy to hear it. Beware there's one known bug: #7176

@zertosh
Copy link
Member

zertosh commented Feb 19, 2018

@jamesreggio, interesting - I expected some difference one-way or the other, but not that stark. Curious to see this is a way I can repro (just for my know education). I'll deprecate babel-plugin-transform-inline-imports-commonjs as soon as Babel 7.0 is stable.

@jamesreggio
Copy link
Contributor

@zertosh — I was surprised too. I thought your use of a local variable would be more performant. I wish I could supply a repro to you, but I'm measuring real-user scenarios in our closed-source app, and I don't have time to build a separate benchmark suite.

@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
area: modules outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: New Feature 🚀 A type of pull request used for our changelog categories
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants