You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Deploy both ES5 and ES2015+ code in production #3125
<!-- Browsers with ES module support load this file. --><scripttype="module" src="main.js"></script><!-- Older browsers load this file (and module-supporting --><!-- browsers know *not* to load this file). --><scriptnomodulesrc="main-legacy.js"></script>
Warning! The only gotcha here is Safari 10 doesn’t support the nomodule attribute, but you can solve this by inlining a JavaScript snippet in your HTML prior to using any <script nomodule> tags. (Note: this has been fixed in Safari Tech Preview, and should be released in Safari 11).
Wow @neeharv and I were literally just discussing this article and I came here to post this. This is an amazing idea—it feels like <script type="module"> finally gives us a good point to separate new browsers from the old.
But there's no way to change babel-preset-env in the config, depending on the entry point.
Generating two webpack builds kinda sucks, but is maybe necessary
If you duplicate your production webpack config, you can handle things pretty well, but that has the weakness of... well... duplicating your production webpack config. @neeharv has gotten a build working pretty well using this so I might let him describe it.
One thing worth pointing out is that we had to use the beta version of UglifyJSPlugin to get the ecma config property, otherwise ES6 code would break. So CRA would probably need to bump to whatever version of webpack includes this, once it goes stable.
Adding a post-processing webpack plugin to generate an ES5 version could work
This is where my thinking is at atm. If your webpack plugin targets beautiful new ES6 code with no polyfills and very little transpilation, but then something comes along and transpiles it to ES5. I've played around with unminified-webpack-plugin a bit and tried to get something similar working, but don't know webpack really well enough to ship this quickly. What it would need to do, though, is this:
Receive every JS chunk
Rerun babel on it with a preset-env of older browsers
Emit new chunks (with any dynamic imports rewritten)
Name them as .nomodule.js or something similar
Somehow instruct HTMLWebpackPlugin to inject the right script tags (type="module" for index.js, nomodule for nomodule.js)
This does feel like an achievable goal, and makes sense to be done as a webpack plugin. There are still questions around serviceworker, given that it reads from the manifest, but we haven't got that far yet.
So that's where we got to in a couple of hours hacking. I think this is a really cool idea and has the potential to dramatically cut down bundle sizes for modern browsers, so would love to see this progress!
Reacted by avocadowastaken, Alexey Lyakhov and Oliver Schmidt
I recall seeing a tweet that this is horrible for performance.
Can you shed some light? Thanks!
@Timer, I don't know the exact tweet you're referencing, but I'm sure it was about using the module loading feature and letting the browser load your entire dependency graph rather than using a bundler.
The technique I describe in the article side-steps that performance issue by still depending on webpack to do the bundling.
Thanks for the ping! Just had a chat with @philipwalton. The pattern he suggested in his article is fine to adopt and we reviewed it before it was published. It focuses on including a bundled top-level script using <script type=module> instead of requiring a complete dependency graph to be loaded and parsed.
My performance concerns there have been that Chrome and other browsers currently haven't optimized the unbundled ESM use-case yet and we currently have lots of work to do bringing down IPC and parse/compile costs. Feel free to use the pattern in the article without any perf concerns from my side 👍
I'll be putting up a rough WIP PR on this soon, by turning the webpack config into a factory of sorts and calling the build function multiple times using a different target each time. It's a pretty hacky way right now, but it has the advantage of generating separate asset-manifest files for each target, making it easy to figure the right service-worker to serve up to the user.
A potential problem with doing this using a webpack plugin is that it'll emit new files and add them to the manifest, and then plugins like sw-precache that rely on the manifest will have es5 + es6 code in their static config.
Super excited about this, seeing a rough reduction of ~10% in the default bundle file size already for the ES6 build.
Chrome and other browsers currently haven't optimized the unbundled ESM use-case yet and we currently have lots of work to do bringing down IPC and parse/compile costs. Feel free to use the pattern in the article without any perf concerns from my side
I think it's a cool idea in theory but I don't see how it could progress unless Webpack gets 10x better at caching. We are already blamed for very slow builds; can't make them twice as slow.
I reckon you'd only need the legacy build on production builds and not development builds, so it would only affect the production build time, no? Does that make a difference?
Activity
geelen commentedon Sep 14, 2017
Wow @neeharv and I were literally just discussing this article and I came here to post this. This is an amazing idea—it feels like
<script type="module">finally gives us a good point to separate new browsers from the old.neeharv commentedon Sep 14, 2017
Thinking of an approach with
env = 'module'onbabel-preset-react-appand do a separate build. Have to figure out where the output would go to though.avocadowastaken commentedon Sep 14, 2017
My thoughts about this:
InterpolateHtmlPluginandHtmlWebpackPluginfrom both configs and use node/grunt/gulp api to generate html file with script/link tags.Or add webpack plugin like unminified-webpack-plugin that will convert all modern js files to ES5 style.
Timer commentedon Sep 14, 2017
/cc @addyosmani
I recall seeing a tweet that this is horrible for performance.
Can you shed some light? Thanks!
geelen commentedon Sep 14, 2017
Ok, after a few hours of hacking, this is what we've found:
This feels like the nicest idea, where you now have two entry points (in
webpack.config.prod.js):But there's no way to change
babel-preset-envin the config, depending on the entry point.If you duplicate your production webpack config, you can handle things pretty well, but that has the weakness of... well... duplicating your production webpack config. @neeharv has gotten a build working pretty well using this so I might let him describe it.
One thing worth pointing out is that we had to use the beta version of UglifyJSPlugin to get the ecma config property, otherwise ES6 code would break. So CRA would probably need to bump to whatever version of webpack includes this, once it goes stable.
This is where my thinking is at atm. If your webpack plugin targets beautiful new ES6 code with no polyfills and very little transpilation, but then something comes along and transpiles it to ES5. I've played around with
unminified-webpack-plugina bit and tried to get something similar working, but don't know webpack really well enough to ship this quickly. What it would need to do, though, is this:.nomodule.jsor something similartype="module"forindex.js,nomodulefornomodule.js)This does feel like an achievable goal, and makes sense to be done as a webpack plugin. There are still questions around serviceworker, given that it reads from the manifest, but we haven't got that far yet.
So that's where we got to in a couple of hours hacking. I think this is a really cool idea and has the potential to dramatically cut down bundle sizes for modern browsers, so would love to see this progress!
avocadowastaken commentedon Sep 14, 2017
@geelen great work!
Totally agree with this.
I guess we have to summon webpack gurus to find the best way to go.
cc @sokra @TheLarkInn
philipwalton commentedon Sep 14, 2017
@Timer, I don't know the exact tweet you're referencing, but I'm sure it was about using the module loading feature and letting the browser load your entire dependency graph rather than using a bundler.
The technique I describe in the article side-steps that performance issue by still depending on webpack to do the bundling.
addyosmani commentedon Sep 14, 2017
Thanks for the ping! Just had a chat with @philipwalton. The pattern he suggested in his article is fine to adopt and we reviewed it before it was published. It focuses on including a bundled top-level script using
<script type=module>instead of requiring a complete dependency graph to be loaded and parsed.My performance concerns there have been that Chrome and other browsers currently haven't optimized the unbundled ESM use-case yet and we currently have lots of work to do bringing down IPC and parse/compile costs. Feel free to use the pattern in the article without any perf concerns from my side 👍
neeharv commentedon Sep 14, 2017
I'll be putting up a rough WIP PR on this soon, by turning the webpack config into a factory of sorts and calling the build function multiple times using a different target each time. It's a pretty hacky way right now, but it has the advantage of generating separate
asset-manifestfiles for each target, making it easy to figure the right service-worker to serve up to the user.A potential problem with doing this using a webpack plugin is that it'll emit new files and add them to the manifest, and then plugins like
sw-precachethat rely on the manifest will havees5+es6code in their static config.Super excited about this, seeing a rough reduction of ~10% in the default bundle file size already for the ES6 build.
Timer commentedon Sep 14, 2017
@addyosmani thanks for the clarification!
neeharv commentedon Sep 15, 2017
Landed basic WIP PR in #3136, we need to figure a better way to do this. Would love to hear some suggestions!
gaearon commentedon Jan 8, 2018
I think it's a cool idea in theory but I don't see how it could progress unless Webpack gets 10x better at caching. We are already blamed for very slow builds; can't make them twice as slow.
timwis commentedon Jan 8, 2018
I reckon you'd only need the legacy build on production builds and not development builds, so it would only affect the production build time, no? Does that make a difference?
gaearon commentedon Jan 8, 2018
Yes, production build time is the one people are struggling with.
viankakrisna commentedon Jan 8, 2018
#2763
#913
some people already complain about slow production builds. It would be nice if we can do this parallelly though.
gaearon commentedon Jan 8, 2018
I don't really see us increasing complexity to support this.
viankakrisna commentedon Jan 8, 2018
yea, maybe someone can build a package that simplifies this. 😉
MidnightDesign commentedon Aug 16, 2018
...so this is dead? Has anyone built like a plugin or something? I would really love to have this feature for performance and debugging.
Djelnar commentedon Nov 6, 2018
@MidnightDesign cra v2 uses browserslist
MidnightDesign commentedon Nov 6, 2018
@Djelnar Does that imply that this should work now? How?