{"action":"created","issue":{"url":"https://api.github.com/repos/webpack/webpack/issues/3216","repository_url":"https://api.github.com/repos/webpack/webpack","labels_url":"https://api.github.com/repos/webpack/webpack/issues/3216/labels{/name}","comments_url":"https://api.github.com/repos/webpack/webpack/issues/3216/comments","events_url":"https://api.github.com/repos/webpack/webpack/issues/3216/events","html_url":"https://github.com/webpack/webpack/issues/3216","id":186316415,"number":3216,"title":"RFC: Webpack Performance Budgets","user":{"login":"addyosmani","id":110953,"avatar_url":"https://avatars.githubusercontent.com/u/110953?v=3","gravatar_id":"","url":"https://api.github.com/users/addyosmani","html_url":"https://github.com/addyosmani","followers_url":"https://api.github.com/users/addyosmani/followers","following_url":"https://api.github.com/users/addyosmani/following{/other_user}","gists_url":"https://api.github.com/users/addyosmani/gists{/gist_id}","starred_url":"https://api.github.com/users/addyosmani/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/addyosmani/subscriptions","organizations_url":"https://api.github.com/users/addyosmani/orgs","repos_url":"https://api.github.com/users/addyosmani/repos","events_url":"https://api.github.com/users/addyosmani/events{/privacy}","received_events_url":"https://api.github.com/users/addyosmani/received_events","type":"User","site_admin":false},"labels":[{"id":1089822,"url":"https://api.github.com/repos/webpack/webpack/labels/enhancement","name":"enhancement","color":"DDDDDD","default":true},{"id":428473033,"url":"https://api.github.com/repos/webpack/webpack/labels/P3:%20Important","name":"P3: Important","color":"f9d0c4","default":false},{"id":412821164,"url":"https://api.github.com/repos/webpack/webpack/labels/RFC/Proposal","name":"RFC/Proposal","color":"f9d0c4","default":false},{"id":90419071,"url":"https://api.github.com/repos/webpack/webpack/labels/webpack-2","name":"webpack-2","color":"c5def5","default":false},{"id":433080621,"url":"https://api.github.com/repos/webpack/webpack/labels/X3:%20discussion%20required","name":"X3: discussion required","color":"fbca04","default":false},{"id":398105880,"url":"https://api.github.com/repos/webpack/webpack/labels/X4:%20work%20required","name":"X4: work required","color":"c2e0c6","default":false}],"state":"open","locked":false,"assignee":{"login":"TheLarkInn","id":3408176,"avatar_url":"https://avatars.githubusercontent.com/u/3408176?v=3","gravatar_id":"","url":"https://api.github.com/users/TheLarkInn","html_url":"https://github.com/TheLarkInn","followers_url":"https://api.github.com/users/TheLarkInn/followers","following_url":"https://api.github.com/users/TheLarkInn/following{/other_user}","gists_url":"https://api.github.com/users/TheLarkInn/gists{/gist_id}","starred_url":"https://api.github.com/users/TheLarkInn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TheLarkInn/subscriptions","organizations_url":"https://api.github.com/users/TheLarkInn/orgs","repos_url":"https://api.github.com/users/TheLarkInn/repos","events_url":"https://api.github.com/users/TheLarkInn/events{/privacy}","received_events_url":"https://api.github.com/users/TheLarkInn/received_events","type":"User","site_admin":false},"assignees":[{"login":"TheLarkInn","id":3408176,"avatar_url":"https://avatars.githubusercontent.com/u/3408176?v=3","gravatar_id":"","url":"https://api.github.com/users/TheLarkInn","html_url":"https://github.com/TheLarkInn","followers_url":"https://api.github.com/users/TheLarkInn/followers","following_url":"https://api.github.com/users/TheLarkInn/following{/other_user}","gists_url":"https://api.github.com/users/TheLarkInn/gists{/gist_id}","starred_url":"https://api.github.com/users/TheLarkInn/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/TheLarkInn/subscriptions","organizations_url":"https://api.github.com/users/TheLarkInn/orgs","repos_url":"https://api.github.com/users/TheLarkInn/repos","events_url":"https://api.github.com/users/TheLarkInn/events{/privacy}","received_events_url":"https://api.github.com/users/TheLarkInn/received_events","type":"User","site_admin":false}],"milestone":null,"comments":40,"created_at":"2016-10-31T15:39:09Z","updated_at":"2016-11-15T18:52:50Z","closed_at":null,"body":"**Webpack version:**\r\n1.10.x and 2.x\r\n\r\n**Please tell us about your environment:**\r\nOSX 10.x / Linux / Windows 10 [all]\r\n\r\n**Expected/desired behavior:**\r\n\r\nHighlight at build-time any JavaScript chunks or bundles that are over a size threshold and can negatively impact web performance load times, parse/compile and interactivity. A default performance budget could indicate if **total chunk sizes** for a page are over a limit (e.g 250KB).\r\n\r\n![main-proposal](https://cloud.githubusercontent.com/assets/110953/19857345/90d6681e-9f3a-11e6-8e0a-be9f2742483a.png)\r\n\r\nI'd love to see if we could make this a **default** and offer a config option for opting out :running_woman: Concern with an opt-in is the folks with the worst perf issues may not know to enable it or use this via a plugin if this suggestion was deferred to one. These folks may also not be testing on mobile devices.\r\n\r\nOptionally, highlight where better performance patterns might be helpful:\r\n\r\n![second-proposal](https://cloud.githubusercontent.com/assets/110953/19857355/9a34b82a-9f3a-11e6-9820-85846141593f.png)\r\n\r\n**Current behaviour:**\r\n\r\nMany of the apps bundled with Webpack that we trace ship a large, single bundle that ends up pegging the main thread and taking longer that it should for webapps to be interactive:\r\n\r\n![screen shot 2016-10-28 at 1 38 32 pm](https://cloud.githubusercontent.com/assets/110953/19858610/5aaeea68-9f3f-11e6-8a14-d4ee64b92f66.jpg)\r\n\r\nThis isn't Webpack's fault, just the culture around shipping large monolithic bundles. This situation gets particularly bad on mobile when trying these apps out on real devices.\r\n\r\nIf we could fix this, it would also make it way more feasible for the Webpack + React (or Angular) stacks to be good candidates for building fast web apps and Progressive Web Apps 🔥\r\n\r\n**What is the motivation / use case for changing the behavior?**\r\n\r\nI recently dove into profiling a large set (180+) React apps in the wild, based on 470 responses we got from a developer [survey](https://twitter.com/addyosmani/status/788819042777739264). This included a mix of small to large scale apps.\r\n\r\nI noted a few common characteristics:\r\n\r\n- 83+% of them use Webpack for module bundling (17% are on Webpack 2)\r\n- Many ship large monolithic JS bundles down to their users (0.5-1MB+). **In most cases, this makes apps [interactive](https://github.com/tdresser/time-to-interactive) in well over 12.4 seconds** on real mobile devices (see table 1) compared to the 4 seconds we see for apps properly using code-splitting and keeping their chunks small. They’re also far slower on desktop than they should be - more JS = more parse/execution time spent at a JS engine level.\r\n- Developers either don’t use code-splitting or use it while still shipping down large chunks of JS in many cases. More on this soon.\r\n\r\n*Table 1: Summary of time-to-interactive scoring (individual TTI was computed by [Lighthouse](https://github.com/googlechrome/lighthouse))*\r\n\r\n| Condition  | Network latency | Download Throughput | Upload Throughput | TTI average React+Webpack app | TTI smaller bundles + code-splitting |\r\n|------------|-----------------|---------------------|-------------------|-----------------------|--------------------------------------|\r\n| Regular 2G | 150ms           | 450kbps             | 150kbps           | 14.7s                 | 5.1s                                 |\r\n| Regular 3G | 150ms           | 1.6MBPs             | 750kbps           | 12.4s                 | 4s                                   |\r\n| Regular 4G | 20ms            | 4MBPs               | 3MBPs             | 8.8s                  | 3.8s                                 |\r\n| Wifi       | 2ms             | 30MBPs              | 15MBPs            | 6s                    | 3.4s                                 |\r\n\r\nWe generally believe that splitting up your work into smaller chunks can get you closer to being interactive sooner, in particular when using HTTP/2. Only serving down the code a user needs for a route is just one pattern here (e.g [PRPL](http://bit.ly/prpl-pattern )) that we’ve seen helps a great deal.\r\n\r\nExamples of this include the great work done by [Housing.com](https://housing.com) and [Flipkart.com](https://flipkart.com). They use Webpack and are getting those real nice numbers in the last column thanks to diligence with perf budgets and code-splitting 👍. \r\n\r\n## What impacts a user's ability to interact with an app?\r\n\r\nA slow time to being interactive can be attributed to a few things:\r\n\r\n1. **Client is slow** i.e keeping the main thread busy 😓 Large JS bundles will take longer to compile and run. There may be other issues at play, but large JS bundles will definitely peg the main thread. Staying fast by shipping the smallest amount of JS needed to get a route/page interactive is a good pattern, especially on mobile where large bundles will take even longer to load/parse/execute/run ⏳ \r\n2. Server/backend may be slow to respond\r\n3. Suboptimal back and forth between the server and client (lots of waterfall requests) that are a sequence of network busy -> CPU idle -> CPU busy -> network idle and so on. \r\n\r\nIf we looked at tools like [performancebudget.io](https://performancebudget.io), targeting loading in [RAIL](https://developers.google.com/web/fundamentals/performance/rail)’s <3s on 3G would place our total JS budget at a far more conservative 106KB once you factor in other resources a typical page might include (like stylesheets and images). The less conservative number of 250KB is an upper bound estimate.\r\n\r\n## Code-splitting and confusion\r\n\r\nA surprising 58%+ of responders **said** they were using code-splitting. We also profiled just this subset and found that their average time to being interactive was 12.3 seconds (remember that overall, the average TTI we saw was 12.4 with or without splitting). So, what happened?\r\n\r\nDigging into this data further, we discovered two things. \r\n\r\n- Either folks that thought they were code-splitting actually weren't and there was a terminology break-down somewhere (e.g maybe they thought using `CommonsChunkPlugin` to 'split' vendor code from main chunks was code-splitting?) 🤔\r\n- Folks that definitely were code-splitting had **zero** insight into chunk size impact on web performance. We saw lots of people with chunk sizes of 400, 500...all the way up to 1200KB of script who were then also lazy-loading in even more script 😢\r\n\r\n![image](https://cloud.githubusercontent.com/assets/110953/19859619/2be126f2-9f43-11e6-9b8e-7fa82eab3be6.png)\r\n\r\nKeep in mind: it's entirely possible to ship fast apps using JS that are interactive quickly with Webpack - if Flipkart can hit it in under 5 seconds, we can definitely bring this number down for the average Webpack user too.\r\n\r\nNote: if you absolutely need a large bundle of JS for a route/page to be useful at all, our advice is to just serve it in one bundle rather than code-split. At an engine level this is cheaper to parse. In most cases, devs aren't going to need all that JS for just one page/view/route so splitting helps.\r\n\r\n## What device was used in our lab profiling?\r\n\r\nA Nexus 5X with a real network connection. We also ran tests on emulated setups with throttled CPU and network (2G, 3G, 4G, Wifi). One key thing to note is if this proposal was implemented, it could benefit load times for webapps on all hardware, regardless of whether it's Android or iOS. Fewer bytes shipped down the line = a little more ❤️ for users data plans.\r\n\r\nThe time-to-interactive definition we use in Lighthouse is the moment after `DOMContentLoaded` where the main thread is available enough to handle user input ✋. We look for the first 500ms window where estimated input latency is <50ms at the 90th percentile.\r\n\r\n## Suppressing the feature\r\n\r\nUsers could opt-out of the feature through their Webpack configuration (we can 🚲 🏠 over that). If a problem is that most devs don't run their dev configs optimized for production, it may be worth considering this feature be enabled when the -p production flag is enabled, however I'm unsure how often that is used. Right now it's unclear if we just want to define a top-level `performanceHints` config vs a `performance` object:\r\n\r\n```js\r\nperformance: {\r\n   hints: true,\r\n   maxBundleSize: 250,\r\n   warnAtPercent: 80\r\n}\r\n```\r\n\r\n## Optional additions to proposal\r\n\r\nGoing further, we could also consider informing you if:\r\n\r\n- You weren’t taking advantage of patterns like code-splitting (e.g not using `require.ensure()` or `System.import()`). This could be expanded to also provide suggestions on other perf plugins (like `CommonChunksPlugin`)\r\n- What if Webpack opted for code-splitting by default as long as you were using `System.import()` or `require.ensure()`? The minimum config is just the minimum requirements aka the entry ouput today.\r\n- What if it could guide you through setting up code-splitting and patterns like [PRPL](https://bit.ly/prpl-pattern) if it detected perf issues? i.e at least install the right Webpack plugins and get your config setup or point you to the docs to finish getting setup?\r\n\r\n*Thanks to Sean Larkin, Shubhie Panicker, Gray Norton and Rob Wormald for reviewing this RFC before I submitted it.*\r\n"},"comment":{"url":"https://api.github.com/repos/webpack/webpack/issues/comments/260731496","html_url":"https://github.com/webpack/webpack/issues/3216#issuecomment-260731496","issue_url":"https://api.github.com/repos/webpack/webpack/issues/3216","id":260731496,"user":{"login":"addyosmani","id":110953,"avatar_url":"https://avatars.githubusercontent.com/u/110953?v=3","gravatar_id":"","url":"https://api.github.com/users/addyosmani","html_url":"https://github.com/addyosmani","followers_url":"https://api.github.com/users/addyosmani/followers","following_url":"https://api.github.com/users/addyosmani/following{/other_user}","gists_url":"https://api.github.com/users/addyosmani/gists{/gist_id}","starred_url":"https://api.github.com/users/addyosmani/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/addyosmani/subscriptions","organizations_url":"https://api.github.com/users/addyosmani/orgs","repos_url":"https://api.github.com/users/addyosmani/repos","events_url":"https://api.github.com/users/addyosmani/events{/privacy}","received_events_url":"https://api.github.com/users/addyosmani/received_events","type":"User","site_admin":false},"created_at":"2016-11-15T18:52:50Z","updated_at":"2016-11-15T18:52:50Z","body":"Thanks for including the patch in a branch of the demo app, @grgur. Will make giving this a spin far easier. Cheers.\r\n\r\n@schiehll That's a great idea. \r\n\r\nAt present, we know from the HTTPArchive that the average amount of script being shipped down on mobile pages (gzipped) is ~408KB (that's about 1MB or so). This takes close to 3s to parse and evaluate on average mobile hardware and about 0.5-0.6s on desktop:\r\n\r\n![screen shot 2016-11-15 at 10 42 23 am](https://cloud.githubusercontent.com/assets/110953/20318742/548d03c6-ab20-11e6-8753-58601d4a6468.jpg)\r\n\r\nLoad time over 3G can take another few seconds and ideally, we want the budget numbers to give you a good chance of still being interactive in under 5 seconds or so. Of course, (target)KB is going to reflect different parse and compile times from app to app, but I think we can noodle on a best guess number that we iterate on.\r\n\r\nFrom Chrome's side I'll see if we have any further research that can help provide justifications for the numbers that we can document with some certainty of being a good ballpark guess."}}
{"actor":{"display_login":"addyosmani"}}