-
-
Notifications
You must be signed in to change notification settings - Fork 631
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
Optimizations for loading JS files, including Lodash (more of a doc issue) #206
Comments
Hi @chrisvfritz, the reason we add it is that we use many functions in lodash. Yes, you can do some fancier importing to trim it out. I tell you what. If you can do a comparison of performance with and without lodash for the super simple generated example case, and if there is real difference, we'll switch. CC: @jdalton, @alexfedoseev |
@chrisvfritz One other factor is that the npm react-on-rails does not include lodash, partly so we don't pull it in. On our production system, we found that with using many libraries, and we were already including lodash. |
I can tell you upfront that the creation of bound methods is more expensive in lodash than the execution of them. We push the cost to creation because it's a 1 time hit that enables us to optimize for execution. That said, if you're only using 1 function and are ES5+ you could, and probably should, drop the dependency (esp. if you're only depending on As @chrisvfritz suggested, this is fine this.myFunction = this.myFunction.bind(this); |
@chrisvfritz We tried class property way, but there is an issue with it: it works if the method is used inside component, but when we pass it to children via props — it's not bound to context, so we must do it explicitly (you can take a look at this commit and 2 branches in the repo to reproduce the issue). So we found that it's easier to use only one way (to avoid accidental bugs) — bind methods to context inside the constructor (which is also standards-compliant). |
@jdalton Thanks for commenting. If you're using react-on-rails, it's ES6 all the way! |
Thanks for hopping in for the definitive explanation of In that case, assuming this.myFunction = ::this.myFunction It's a little briefer and still allows trimming |
@chrisvfritz Please refer to: https://github.com/shakacode/style-guide-javascript We went down that road. We had issues. A lot of thought (and practice) went into our recommendations. Main issue is that |
Then how about just changing:
to: import { bindAll } from 'lodash' It should cut down the initial build size considerably and it could be argued that even in a project that really needs every lodash function, it's a best practice to only import what you need. |
import { bindAll } from 'lodash/bindAll' |
😃 👍 |
@chrisvfritz I'd be curious to see if the difference is perceptible. How many bytes are we talking about? I suspect that compared to a few images, there's really no difference measurable. |
For some reason, @jdalton's suggested import was giving me import bindAll from 'lodash/function/bindAll' On a fresh Rails app after following the Getting Started instructions:
I personally feel that saving 60K gzipped is not insignificant, especially considering that the size of JavaScript files is much, much more important than the size of an image. I don't think they can be compared. For example, if I'm waiting on an image to load, I can still see the rest of the content and fully interact with the page. Not a huge deal in most apps. If a user is waiting on the JavaScript to load, they can't do anything if the page heavily relies on React or other JS. For most active applications, JavaScript is also much less likely to be cached. Images tend not to be updated very often - even spritesheets are rarely changed in most projects I've been a part of. But it's not uncommon to push out builds with newly compiled JavaScript several times per day. So even if it were only 3K, I've seen the 3Ks add up. And they do make a measurable difference for users, especially those on (still common) slow and unreliable Internet. I'm using React and not Riot, so I'm not expecting micro build sizes. But in this case, all we have to do to avoid unnecessary bloat is follow a best practice that happens to be just as easy as not following the best practice. It's changing a line. I'm kind of baffled at the resistance, to be honest. |
@chrisvfritz The reason why we don't like to spend time optimizing the size of lodash is:
We'd rather stay consistent and simple, and then optimize once we have real data. I'd be curious if you could come up with a _time_ difference that shows the 60K to load the JS really makes a difference. It would be interesting to see based on some hypothetical scenarios of mobile vs. broadband. Here's a good article on the topic from @nateberkopec: https://www.nateberkopec.com/2015/10/07/frontend-performance-chrome-timeline.html Thanks. This is a great discussion. |
I gave you lodash v4's path. You must be using v3. |
Couple of things:
@justin808 have you written anywhere about how you're using react and turbolinks on the same app? that's interesting. |
Thanks for the suggestion @nateberkopec. That is exactly what I do on heavier apps. 😃 Also 👍 for Chrome's throttling on the Network tab - was just about to suggest that feature. Before even running perf comparisons though, some simple math will tell us that 60K on the average 4Mbps for mobile in the US adds 120ms to load time, just for the download, before we even get to execution. Something most of these stats assume though is that the user has a strong connection to LTE or 4G. I've seen that with many carriers, in many parts of the United States, connections to those networks are spotty. That means 1 Mbps or lower is typical for many users and now that 60K adds at least 480ms to load time. I already integrate Turbolinks 3 into some of my apps (it allows much finer control than Turbolinks 2) and split assets into vendor and app. These help a lot. They make 95% of page loads really fast. But even with all these optimizations, that critical minority of first-time visits (which for some apps, won't even be a minority) is unaffected. It's still slow. Before it even does anything, the footprint of this project's JavaScript is bigger than for most of my applications after months of development. Here's a case where we can make a significant and measurable impact and for free. It's not premature optimization, because a critical criterion in the definition is that the optimization has "a strong negative impact when debugging and maintenance are considered." There's zero negative impact here. Knuth rightly notes that:
This is a relatively small efficiency, but it's in the rare 3% where we get it for free. No downsides. For your apps, it sounds like it won't make a difference. But this is a public project and for my apps and probably many others, it will. @nateberkopec: As an aside on Turbolinks, Turbolinks 3 combined with React has been great for me. I can still use Rails for all my routing, but get fantastic page loads and I use nprogress-rails for even greater perceived performance. Some Turbolinks 3 features I frequently take advantage of in my React components:
|
@chrisvfritz If you want to throw in a PR that moves the lodash to v4 and uses the new syntax, super. I can also do it as well. @nateberkopec and @jdalton Thanks so much for your insights. It's too bad that @sokra's amazing Webpack can do the job automatically with sticking to the basic In terms of turbolinks, ReactOnRails has the code to work with it, so you just turn it on:
|
@chrisvfritz @justin808 We paired with @robwise a bit on class properties + fat arrows issue. I was wrong and the problem in repo (that I linked previously) was not with wrong context, but with equality check in |
@alexfedoseev Phew. 😃 That's great news! @justin808 Should we go that route after all then and remove lodash as a dependency? |
@jdalton I love the simplicity of that solution! @chrisvfritz Let's see the relative bundle sizes if we we apply the Longer term, we do like defining the callbacks with fat arrows. However, we just got bit by a babel issue with static props needing semicolons (Stage 1) that was a real hassle to change. Maybe once we get to stage 2 for this proposal? Class properties are Stage 1 as of 1/17/2016: |
babel-plugin-lodash does look pretty clever! For a public project though, I worry that it might be a bit too magical. Others could open new issues to "fix" the faux-global import, not realizing that a special lodash-specific plugin is being used to make lodash imports work differently from imports of any other library. So I'd personally prefer the more explicit, non-plugin way. I hear ya on that @#%$ing semicolon issue. I'm fine waiting for stage 2. It's times like this I'm reminded how crazy it is that everyone has accepted we should all use the most unstable and feature-limited compile-to-JS language yet developed. Can't deny that the tooling is great though. 😞 |
Closing an issue is cheap and so is documentation, but I hear ya. |
@jdalton I'm definitely sharing it for internal projects! And I've bookmarked it for later because it's given me some other ideas. 😃 I can't wait for the day when this is just how importing works, but right now, most people don't even realize it's possible. And for public projects, I generally strive for intuitive over elegant - and have usually regretted it when I haven't. |
@chrisvfritz I sort of expect that Webpack would be able clean out unused code, so I don't think it's too unexpected given that we're heavily based on Webpack. |
Tree shaking is going to be implemented in Webpack 2: http://www.2ality.com/2015/12/webpack-tree-shaking.html |
@justin808 Up to you ultimately. I'd definitely like Weback to do that for all my code, but I've never seen anything like it. Hence why I personally don't expect it. And technically, though this is the kind of responsibility we'd expect Webpack to handle, it has nothing to do with the magical trimming here. It's happening in a Babel plugin outside of Webpack. That's obviously caused some confusion already. And it splits bundling responsibilities between two projects, adding complexity. Choosing between convenience and simplicity, I'll personally choose simplicity every time. |
Right now, the plan is to update the generator to use the babel https://github.com/lodash/babel-plugin-lodash. Any volunteers to help with this PR? I'd like to see us do this first on the tutorial: shakacode/react-webpack-rails-tutorial#204 |
Here's a related issue: |
@chrisvfritz We're switching to the fat arrow syntax for binding. So the issue then becomes just efficiently including Lodash. @jbhatab I'm tempted to close this one, as it's more of a doc issue or an example issue for https://github.com/shakacode/react-webpack-rails-tutorial/. |
@justin808 we're removing lodash from the generator, this can be closed |
I see this pattern in the constructors of example components:
Putting aside the fact that all of lodash was being imported when only a single function was needed, I think even that single function is unnecessary, since a class property (
stage-1
) can be used for the same effect:Even if you decide to later remove the
stage-0
preset from.babelrc
, I'd personally much prefer the use of ES5's.bind
, since it'll also do the job.Then Lodash can be completely removed as a dependency.
The text was updated successfully, but these errors were encountered: