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

Optimizing Compiler: Component Folding #7323

Open
sebmarkbage opened this Issue Jul 21, 2016 · 5 comments

Comments

Projects
None yet
5 participants
@sebmarkbage
Member

sebmarkbage commented Jul 21, 2016

This is like the final frontier for React but I never really wrote anything about it so I figured I'd create an issue.

Basically, the idea is to utilize information about how React works to do constant folding and inlining of components under certain conditions.

Example Source:

function Foo(props) {
  if (props.data.type === 'img') {
    return <img src={props.data.src} className={props.className} alt={props.alt} />;
  }
  return <span>{props.data.type}</span>;
}
Foo.defaultProps = {
  alt: "An image of Foo."
};
var CSSClasses = {
  bar: 'bar'
};
module.exports = CSSClasses;
var Foo = require('Foo');
var Classes = require('Classes');
function Bar(props) {
  return <Foo data={{ type: 'img', src: props.src }} className={Classes.bar} />;
}

By knowing what Foo and Classes is made up of, we can turn the Bar component into this:

var Foo = require('Foo');
var Classes = require('Classes');
function Bar(props) {
  return <Foo data={{ type: 'img', src: props.src }} className={Classes.bar} />;
}
function Bar_optimized(props) {
  return <img src={props.src} className="Bar" alt="An image of Foo." />;
}

Dead-code elimination then strips it down to just:

function Bar_optimized(props) {
  return <img src={props.src} className="Bar" alt="An image of Foo." />;
}

Now there are a bunch of different cases where this needs to bail out. For example, we need to know that the CSSClasses object and the defaultProps object is immutable, or we need to infer that it is immutable using Escape analysis.

With classes these bail out cases are even more complex.

The problem is that current JS infrastructure is particularly bad at this kind of whole program or whole package linking. Node doesn't have a notion of per package private modules so anything can mutate anything by default. Transpilers such as Babel are set up to work on a single file at a time. They don't have access to the source of other files to do this analysis. Rollup is closer but is limited to a small set of static primitives.

However, once smarter compilers become more prevalent in the JS world or we find ways to hack around the limitations, we can start building out more of these smarter compiler optimizations.

@Munter

This comment has been minimized.

Show comment
Hide comment
@Munter

Munter Jul 21, 2016

Access to other files source code can be established by populating the full dependency graph. Tools like browserify, webpack, systemjs or assetgraph give you that

Munter commented Jul 21, 2016

Access to other files source code can be established by populating the full dependency graph. Tools like browserify, webpack, systemjs or assetgraph give you that

@sebmarkbage

This comment has been minimized.

Show comment
Hide comment
@sebmarkbage

sebmarkbage Jul 21, 2016

Member

@Munter This is not currently available by default nor set up in people's build systems. At any kind of large code base it is also too slow to just read/parse these files randomly. It needs to be smarter, parallelize and cache info. It's certainly not impossible to build but it just isn't integrated. We'd have to build it and get adoption before this particular transform is possible.

Member

sebmarkbage commented Jul 21, 2016

@Munter This is not currently available by default nor set up in people's build systems. At any kind of large code base it is also too slow to just read/parse these files randomly. It needs to be smarter, parallelize and cache info. It's certainly not impossible to build but it just isn't integrated. We'd have to build it and get adoption before this particular transform is possible.

@caridy

This comment has been minimized.

Show comment
Hide comment
@caridy

caridy Jul 21, 2016

To illustrate what @sebmarkbage is talking about rollup limitations, here is a pseudo code of the example above:

http://rollupjs.org/#%7B%22options%22%3A%7B%22format%22%3A%22es%22%2C%22moduleName%22%3A%22myBundle%22%2C%22globals%22%3A%7B%7D%7D%2C%22modules%22%3A%5B%7B%22name%22%3A%22main.js%22%2C%22code%22%3A%22import%20Foo%20from%20'.%2Ffoo.js'%3B%5Cnimport%20Classes%20from%20'.%2Fcssclasses.js'%3B%5Cnexport%20default%20function%20Bar(props)%20%7B%5Cn%20%20return%20new%20Foo(%7Bdata%3A%20%7B%20type%3A%20'img'%2C%20src%3A%20props.src%20%7D%2C%20className%3A%20Classes.bar%7D)%3B%5Cn%7D%22%7D%2C%7B%22name%22%3A%22foo.js%22%2C%22code%22%3A%22export%20default%20function%20Foo(props)%20%7B%5Cn%20%20if%20(props.data.type%20%3D%3D%3D%20'img')%20%7B%5Cn%20%20%20%20return%20new%20Img(%7Bsrc%3A%20props.data.src%2C%20className%3A%20props.className%2C%20alt%3A%20props.alt%7D)%3B%5Cn%20%20%7D%5Cn%20%20return%20new%20Span(props.data.type)%3B%5Cn%7D%5CnFoo.defaultProps%20%3D%20%7B%5Cn%20%20alt%3A%20%5C%22An%20image%20of%20Foo.%5C%22%5Cn%7D%3B%5Cn%5Cnexport%20function%20thisWillBeRemoved()%20%7B%5Cn%20%20%20%2F%2F%20code%20will%20be%20eliminated%5Cn%7D%22%7D%2C%7B%22name%22%3A%22cssclasses.js%22%2C%22code%22%3A%22export%20default%20%7B%5Cn%20%20bar%3A%20'bar'%5Cn%7D%3B%5Cn%5Cnexport%20const%20thisWillBeAlsoIgnored%20%3D%201%3B%22%7D%5D%7D

Then, running the output of rollup thru closure compiler, you will get this:

module.exports =  = function(b) {
         b = {data:{type:"img", src:b.src}, className:"bar"};
         return "img" === b.data.type ?
                 Img({src:b.data.src, className:b.className, alt:b.alt}) :
                 Span(b.data.type);
};

In other words, the precedent is there, and certainly, rollup's tree-shaking capabilities can be improved to cover other primitives, but we will need much more optimizations in terms of how react uses the components to achieve what you described.

/cc @Rich-Harris

caridy commented Jul 21, 2016

To illustrate what @sebmarkbage is talking about rollup limitations, here is a pseudo code of the example above:

http://rollupjs.org/#%7B%22options%22%3A%7B%22format%22%3A%22es%22%2C%22moduleName%22%3A%22myBundle%22%2C%22globals%22%3A%7B%7D%7D%2C%22modules%22%3A%5B%7B%22name%22%3A%22main.js%22%2C%22code%22%3A%22import%20Foo%20from%20'.%2Ffoo.js'%3B%5Cnimport%20Classes%20from%20'.%2Fcssclasses.js'%3B%5Cnexport%20default%20function%20Bar(props)%20%7B%5Cn%20%20return%20new%20Foo(%7Bdata%3A%20%7B%20type%3A%20'img'%2C%20src%3A%20props.src%20%7D%2C%20className%3A%20Classes.bar%7D)%3B%5Cn%7D%22%7D%2C%7B%22name%22%3A%22foo.js%22%2C%22code%22%3A%22export%20default%20function%20Foo(props)%20%7B%5Cn%20%20if%20(props.data.type%20%3D%3D%3D%20'img')%20%7B%5Cn%20%20%20%20return%20new%20Img(%7Bsrc%3A%20props.data.src%2C%20className%3A%20props.className%2C%20alt%3A%20props.alt%7D)%3B%5Cn%20%20%7D%5Cn%20%20return%20new%20Span(props.data.type)%3B%5Cn%7D%5CnFoo.defaultProps%20%3D%20%7B%5Cn%20%20alt%3A%20%5C%22An%20image%20of%20Foo.%5C%22%5Cn%7D%3B%5Cn%5Cnexport%20function%20thisWillBeRemoved()%20%7B%5Cn%20%20%20%2F%2F%20code%20will%20be%20eliminated%5Cn%7D%22%7D%2C%7B%22name%22%3A%22cssclasses.js%22%2C%22code%22%3A%22export%20default%20%7B%5Cn%20%20bar%3A%20'bar'%5Cn%7D%3B%5Cn%5Cnexport%20const%20thisWillBeAlsoIgnored%20%3D%201%3B%22%7D%5D%7D

Then, running the output of rollup thru closure compiler, you will get this:

module.exports =  = function(b) {
         b = {data:{type:"img", src:b.src}, className:"bar"};
         return "img" === b.data.type ?
                 Img({src:b.data.src, className:b.className, alt:b.alt}) :
                 Span(b.data.type);
};

In other words, the precedent is there, and certainly, rollup's tree-shaking capabilities can be improved to cover other primitives, but we will need much more optimizations in terms of how react uses the components to achieve what you described.

/cc @Rich-Harris

@appsforartists

This comment has been minimized.

Show comment
Hide comment
@appsforartists

appsforartists Jul 22, 2016

This reminds me a bit of @jeffmo's talk about using Flow for tree-shaking at React Europe.

appsforartists commented Jul 22, 2016

This reminds me a bit of @jeffmo's talk about using Flow for tree-shaking at React Europe.

@Sinewyk

This comment has been minimized.

Show comment
Hide comment
@Sinewyk

Sinewyk Jul 25, 2016

I thought about it a lot and there would probably be a need of some sort of cursor to edit what to fold and what not to fold regarding code size no ?

After a lot of folding, there would logically be the original component definition (if we couldn't fold everything ?) + the number of time it was folded * the size of what was folded.

Depending on what was done; the bundled size could jump a lot. Unless I'm missing something, this component folding should only fold one liners for webApp, and high to no limitations for server side rendering (if you build your backend) and desktop apps (electron & stuff).

Sinewyk commented Jul 25, 2016

I thought about it a lot and there would probably be a need of some sort of cursor to edit what to fold and what not to fold regarding code size no ?

After a lot of folding, there would logically be the original component definition (if we couldn't fold everything ?) + the number of time it was folded * the size of what was folded.

Depending on what was done; the bundled size could jump a lot. Unless I'm missing something, this component folding should only fold one liners for webApp, and high to no limitations for server side rendering (if you build your backend) and desktop apps (electron & stuff).

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