Skip to content

Inline host config #30

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Inline host config #30

wants to merge 8 commits into from

Conversation

ManasJayanth
Copy link
Collaborator

@ManasJayanth ManasJayanth commented Jun 16, 2018

Attempts to close #27

@ManasJayanth
Copy link
Collaborator Author

Moving all deps to devDeps since they are all resolved during compile time.

@jquense
Copy link
Owner

jquense commented Jun 16, 2018

Nice! I'm not sure about about the dependency move, what about all the runtime deps like dom helpers and react. Those are still needed at runtime and probably shouldn't be inlined right?

@ManasJayanth
Copy link
Collaborator Author

ManasJayanth commented Jun 16, 2018 via email

@jquense
Copy link
Owner

jquense commented Jun 16, 2018

I think we only want to inline react-reconciler, otherwise the bundle will include React and dom-helpers which will be a big bundle increase for folks since they will have duplicated copies of react, and anyone using dom-helpers will also be duplicated right?

@ManasJayanth
Copy link
Collaborator Author

ManasJayanth commented Jun 16, 2018 via email

@jquense
Copy link
Owner

jquense commented Jun 18, 2018

sure but it will still make the practical bundle size of ppl using RDL bigger if we let it do that. I don't think that's what we want

@ManasJayanth
Copy link
Collaborator Author

I think I misunderstood earlier. You're right. We're on the same page. I have re-introduced other deps back in package.json and marked them external for rollup.

@ManasJayanth ManasJayanth requested a review from jquense June 18, 2018 15:20
@ManasJayanth
Copy link
Collaborator Author

ManasJayanth commented Jun 18, 2018

Please compare the build sizes between commit 4a53dec and . This is because previously we didn’t inline npm deps in the cjs bundles.

I added fbjs so that we reuse some of the code thats bundled. It helps dedup some code.

Added index.js so that minified bundle is required too. Previously only unminified bundle was a part of package.json's main.

Had to use ADVANCED mode for closure compiler. We might have to add build level checks.

I had to remove flow defs for the host config too, for the time being. Will revisit tomorrow or so.

@ManasJayanth ManasJayanth removed the WIP label Jun 18, 2018
@gaearon
Copy link

gaearon commented Jun 18, 2018

Had to use ADVANCED mode for closure compiler

This is pretty hard to get working, I wouldn't recommend. Why do you need it?

@ManasJayanth
Copy link
Collaborator Author

ManasJayanth commented Jun 18, 2018

$$hostconfig never got inlined in simple mode. @gaearon

@ManasJayanth
Copy link
Collaborator Author

Hello world app bundled (created using CRA) fell down to 23.76 KB

@gaearon
Copy link

gaearon commented Jun 18, 2018

It falling down doesn't mean everything works correctly though. I'd be very cautious.

If it doesn't get inlined, maybe we can explore something on our side to fix this.

@ManasJayanth
Copy link
Collaborator Author

ManasJayanth commented Jun 18, 2018

Hello world app bundled (created using CRA) fell down to 23.76 KB

Sorry. This wasn't meant you justify use of advanced mode. I wanted to report the changes in build size. Probably should have updated my previous comment. You're right about the safety of the build.

I did try a couple of things like avoid unnecessary function calls so that gcc can statically analyse hostconfig. Eventually ran out of ideas.

@ManasJayanth
Copy link
Collaborator Author

@gaearon Can you please share some ideas to help inline the host config? My initial attempts were to make sure the object is statically analysable. I could have missed though.

@ManasJayanth
Copy link
Collaborator Author

I couldn't get the following to inline in simple mode :( Tested it on the online tool too

function log(m) {
  console.log(m);
}
log(m);

@ManasJayanth
Copy link
Collaborator Author

Just reporting it here. Dan is right about GCC - I got bit by GCC's advanced mode in a project I was working on.

@ManasJayanth
Copy link
Collaborator Author

I stand corrected. Figured why GCC didn't inline my code earlier: log was declared global. Following does get inlined.

function main(){
  function log(m) {
    console.log(m);
  }

  log('m');
  log('m');
  log('m');
  log('m');
  log('m');
  log('m');
  log('m');

}

main()

Inlined code

function main() {
  console.log("m");
  console.log("m");
  console.log("m");
  console.log("m");
  console.log("m");
  console.log("m");
  console.log("m");
}
main();

@ManasJayanth
Copy link
Collaborator Author

Reverting the advanced level changes shortly

@ManasJayanth
Copy link
Collaborator Author

I observed the following:

a.js

(function main() {

  const config = {
    log(m) {
      console.log(m);
    }
  }

  function reconciler(c) {
    const log = c.log;
    log('jere');
  }

  reconciler(config);

}())

a.min.js

(function() {
  (function(a) {
    a = a.log;
    a("jere");
  })({
    log: function(a) {
      console.log(a);
    }
  });
})();

b.js

(function main() {

  const config = {
    log(m) {
      console.log(m);
    }
  }

  function reconciler(c) {
    const log = c.log;
    c.log('here');
    // log('jere');
  }

  reconciler(config);

}())

b.min.js

(function() {
  console.log("here");
})();

The transform script moves host config inside the reconciler() to help
GCC inline code better
@ManasJayanth
Copy link
Collaborator Author

Explanation

Trying to understand GCC's behaviour

I tried replicating the bundle's code behaviour of wrapping a config object and tried running GCC on it.

I got GCC to inline the following:

  const config = {
    log(m) {
      console.log(m);
    }
  }

  function reconciler(c) {
    const log = c.log;
    c.log('here');
    // log('jere');
  }

  reconciler(config);

But not the following which resembles the bundle better

  const config = {
    log(m) {
      console.log(m);
    }
  }

  function reconciler(c) {
    const log = c.log;
    log('jere');
    return {
      foo() {
        // More side effects
      }
    }
  }

  reconciler(config);

I tried a couple of other variations too. Reported it on SO and Google Groups. Tweeted about it too.

Most reliable response was from one of the maintainers of GCC on SO.

There is no one answer. The compiler uses heuristics and side effect calculations to decide when to inline. The compiler is also less likely to inline in the global scope than in nested scopes.

Thats when I decided to transform to the structure of our bundle to resemble React DOM's.

The babel transform

Basically, the scripts transforms,

  function createInstance() {
  }
  function appendChild() {
  }
  const config = {
    createInstance: createInstance
    appendChild: appendChild
  }
  function reconciler(config) {
    var ax = config.createInstance,
          by = config.appendChild;

   // ...          
  }
  reconciler(config);

to

  // const config = { ... }  is removed
  function reconciler(config) {
      var ax = function createInstance() {
      }
      var by = function appendChild() {
      }
      // ...          
  }
  reconciler({});

Gotchas

The transform depends on the line Reconciler(__DEV__ ? HostConfigDev : HostConfigProd) in the code. This is because of the reconciler(config); to reconciler({}); transform.

I figure what Reconciler(__DEV__ ? HostConfigDev : HostConfigProd) looks like in the bundle using mozilla's sourcemap consumer lib, and remove it from the ast and perform the rest of the operations.

I am still inspecting both transformed and the inlined code (using the newly added debug mode in GCC) and see if I can spot any potential issues. Another set of eyes would really help.

Will this transform work unconditionally?

Two things are absolutely necessary,

  1. Any change to Reconciler(__DEV__ ? HostConfigDev : HostConfigProd) must be reflected in the transform script.
  2. Config must to written in modules so that Rollup declares them as functions and const/let bindings. The transformer reliable on VariableDeclarations and FunctionDeclarations. Host configs must not longer be of the form { shouldSetTextContent(){ ... }, createInstance() {...} } as the transformer doesn't account for them

Please let me know your thoughts.

@ManasJayanth ManasJayanth removed the WIP label Jul 30, 2018
@jquense
Copy link
Owner

jquense commented Aug 13, 2018

@prometheansacrifice what is the practical benefit we are getting here again, do we know how many bytes were saving this way? It's kinda convoluted so i'm not sure the added fragility to the build is worth it unless its a significant savings yeah?

text: string,
): void,
};
// declare export type HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL> = {
Copy link
Owner

Choose a reason for hiding this comment

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

why are they commented out?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Those types, particularly ssr and mutation blocks need to be flattened.

"react": "^16.3.0",
"react-reconciler": "0.10.0",
"warning": "^3.0.0"
"fbjs": "^0.8.17"
Copy link
Owner

Choose a reason for hiding this comment

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

i really don't want a dep on fbjs if we can help it, its more of an internal FB project, and REact itself is moving away from including it

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Got it. It helped us avoid duplicate declarations for warning and other modules.

Copy link
Owner

Choose a reason for hiding this comment

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

yeah i wonder if it's worth making out own one for that? I have a package react-tackle-box i dump random common stuff into, if it's helpful i could put stuff there

@ManasJayanth
Copy link
Collaborator Author

@jquense Did you get a chance to look at the GCC'd bundle?

@jquense
Copy link
Owner

jquense commented Aug 13, 2018

I haven't had a chance to check yet :/ anything in particular you want me to see? Sorry it's been a while!

@ManasJayanth
Copy link
Collaborator Author

Run the build with debug: true. You notice that all host config has completely disappeared from the bundle. Bundle size drops a few KBs.

I understand the fragility you mentioned. Maybe we could put this behind a --experimental flag? I was wondering if upstream would support SweetJS style macros officially to inline config (but could take a serious look at it)

Without modifiying the AST, I exhausted all my options. Upstream works differently. Actually this transform tries to mimick the upstream AST after bundling.

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

Successfully merging this pull request may close these issues.

Inline host config at build
3 participants