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

Initial work to support advanced compilation of UMD bundles using GCC #11967

Closed
wants to merge 34 commits into from

Conversation

banga
Copy link

@banga banga commented Jan 5, 2018

As described in #11092, this PR adds support for passing in externs to GCC to allow it to compile bundles in advanced mode without renaming properties that are part of the public API. Most of the externs were taken from https://github.com/cljsjs/packages/tree/master/react/resources/cljsjs/, although some more needed to be added. I've split them into various files with necessary comments so they should be reasonably easy to follow.

At this point, the node bundles do not build correctly (the module.exports part is stripped out), so we can't run tests just yet. So I just tested by pointing the babel-standalone fixture to the built prod files and loading it. The current fixture works successfully, and I was able to create a functional component as well. But when I tried creating a component by extending React.Component, I saw class constructor cannot be invoked without 'new', indicating that another property is getting renamed and causing the functional component codepath to execute for non-functional components.

(This is my first PR to an FB project, so I just submitted the CLA.)

  • Get all test-build-prod tests to pass
  • Don't use fbjs externs for UMD bundles for better minification
  • Clean up externs files to remove old methods that no longer exist
  • Try Splitting JS into modules to allow minifying react internal properties accessed across bundle boundaries

This makes closure options dynamic, based on bundle type, since UMD and Node
bundles expect different things in their externs.

At this point, this can build the UMD_PROD versions of core and dom-client and
load the babel-standalone fixture with the compiled react and react-dom files.
The built bundle is not correct yet because it's missing module.exports.
@sophiebits
Copy link
Collaborator

That error is likely caused by renaming isReactComponent.

React.version;

React.createClass = function(specification) {};
React.createFactory = function(reactClass) {};
Copy link
Collaborator

Choose a reason for hiding this comment

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

A lot of these are outdated. Once we get the tests passing we should trim these down significantly.

@banga
Copy link
Author

banga commented Jan 5, 2018

@sophiebits yeah that was it. I got the node bundles to build correctly last night and got a handful of test-build-prod tests to pass. I'll clean-up my changes and push tonight. The most frequent error that was breaking tests now was Target Container is not a DOM Element, which might be too generic to debug just from that, but let me know if you have ideas.

@gaearon
Copy link
Collaborator

gaearon commented Jan 6, 2018

Can't wait 😍

@appsforartists
Copy link

The Angular team has tools (tsickle and clutz) to convert between TypeScript and Closure annotations. I haven't tested them myself yet, so I don't know how well they work, but I believe the goal is to enable TypeScript applications to use advanced GCC.

It may be worth exploring using tsickle to auto-gen your Closure externs. I'm not sure if it would work on Flow code, but I bet @evmar is amenable to PRs.

I suspect that automatically generating them is better in the long run than maintaining the externs by hand.

Shrey Banga added 10 commits January 5, 2018 19:44
The missing isReactComponent was causing Component subclasses to be treated as
functional components.

The missing properties on ReactElement were breaking other type dependant
behavior.
The browser environment helps ensure that dom attributes don't get renamed, for
example.

With processCommonJsModules set to true, gcc was removing the module.exports
from the compiled bundles.
So we don't have to keep defining these for each bundle
Event -> FakeEvent
hasOwnProperty -> objHasOwnProperty
escape -> escapeKey

Suppressed the error on ReactART.Text since it's exported
Also suppress duplicate warnings because now that we don't use
processCommonJsModules, these are treated as global variables.
This is needed by both UMD and NODE bundles, even though the NODE bundle
generates a duplicate warning.
These are treated as external dependencies in node bundles, so have to prevent
renaming them. We should eventually still rename these in UMD bundles.
@gaearon
Copy link
Collaborator

gaearon commented Jan 6, 2018

One thing that’s nice in our case is that the API surface is relatively small. So once we get them right for the first time they’re not as much pain to maintain.

Shrey Banga added 6 commits January 5, 2018 19:55
GCC seems to incorrectly remove all of the properties from `RESERVED_PROPS`
because they are never accessed by name. Replaced this with a Set since that was
the intent anyway.
This allows us to run prettier even after compiling in ADVANCED mode, which can
be really useful when debugging problems with the minified output.
@banga
Copy link
Author

banga commented Jan 6, 2018

Alright after today's work, we have 222 out of 244 tests passing in the react package:
image.

@evmar
Copy link

evmar commented Jan 6, 2018

@appsforartists Within Google we do generate externs for React from the DefinitelyTyped React d.ts files. The API surface is a bit larger than you'd expect due to including all the CSS properties etc.

But it's also probably more complicated than what you want because:

  1. the d.ts files include lots of type definitions (including fancy generics to model setState() etc)
  2. they ended up needing some fixing by hand at the end due to various bugs (in how we handle said fancy generics -- Closure can't model some of them)
  3. if you're just worrying about the untyped Closure optimizations you don't need the types.

Oh, and now that I think harder about it, these d.ts files are for clients of React to use the minified bundle, while here you are concerned about preventing bits of React's internal API from getting renamed. (You might find this section of the docs useful in that respect.)

@banga
Copy link
Author

banga commented Jan 6, 2018

Yeah, the more challenging part seems to be the non-public API shared between the bundles (e.g. FiberNode and updater). We need to either prevent them from being renamed, or find a way to compile everything together and split the bundles so they are renamed consistently. So far I'm just manually doing that, with some hints from exported types.

@stereobooster
Copy link

When Dan twitted about using GCC for minification I thought about "GNU Compiler Collection", but this was about "Google Closure Compiler" 🤣

@banga
Copy link
Author

banga commented Jan 7, 2018

Ok now all react and react-dom tests are passing!

@gaearon
Copy link
Collaborator

gaearon commented Jan 7, 2018

We probably don’t need to apply advanced mode to any bundles except React and ReactDOM. Others’ size is less critical / important.

@gaearon
Copy link
Collaborator

gaearon commented Jan 7, 2018

It’s also worth exploring a subset of this PR that only applies advanced to the React bundle. It’s not as important as ReactDOM but it’s a good way to get started since it’s fairly small and won’t need a ton of externs. That would be relatively safe to merge while we continue to iterate on ReactDOM.

@sophiebits
Copy link
Collaborator

If we do run it on all bundles, let’s have separate externs for each bundle. There are some names we want to munge in react-dom but not react-reconciler, for instance.

@banga
Copy link
Author

banga commented Jan 7, 2018

@gaearon sounds good. Should I start a separate PR for just compiling the react bundle?

@sophiebits yup, I’ve been keeping the externs separate by bundle type, although some bundles need to pull in externs for others (eg react-dom needs react externs)

@gaearon
Copy link
Collaborator

gaearon commented Jan 8, 2018

Yes, a separate more scoped PR that can pass a CI would be great. Thank you for working on this!

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

Successfully merging this pull request may close these issues.

7 participants