Skip to content
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

Deploy both ES5 and ES2015+ code in production #3125

Open
avocadowastaken opened this issue Sep 14, 2017 · 20 comments
Open

Deploy both ES5 and ES2015+ code in production #3125

avocadowastaken opened this issue Sep 14, 2017 · 20 comments

Comments

@avocadowastaken
Copy link

Is this a bug report?

No

Everything described in this article.

But for lazy ones I'll add some highlights here.

<!-- Browsers with ES module support load this file. -->
<script type="module" src="main.js"></script>

<!-- Older browsers load this file (and module-supporting -->
<!-- browsers know *not* to load this file). -->
<script nomodule src="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).

And here is the working example.

@geelen
Copy link

geelen commented 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
Copy link

neeharv commented Sep 14, 2017

Thinking of an approach with env = 'module' on babel-preset-react-app and do a separate build. Have to figure out where the output would go to though.

@avocadowastaken
Copy link
Author

My thoughts about this:

  1. Make another webpack.config for ES2015 builds.
  2. Sort out asset naming strategies cause both builds will use same assets and we don't want to duplicate output files.
  3. Remove InterpolateHtmlPlugin and HtmlWebpackPlugin from 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
Copy link
Contributor

Timer commented Sep 14, 2017

/cc @addyosmani

I recall seeing a tweet that this is horrible for performance.
Can you shed some light? Thanks!

@geelen
Copy link

geelen commented Sep 14, 2017

Ok, after a few hours of hacking, this is what we've found:

  • It can't be two entry points

This feels like the nicest idea, where you now have two entry points (in webpack.config.prod.js):

- entry: [require.resolve('./polyfills'), paths.appIndexJs],
+ entry: {
+   main: [require.resolve('./polyfills'), paths.appIndexJs],
+   esmodule: paths.appIndexJs,
+ }

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!

@avocadowastaken
Copy link
Author

@geelen great work!

This does feel like an achievable goal, and makes sense to be done as a webpack plugin.

Totally agree with this.

I guess we have to summon webpack gurus to find the best way to go.

cc @sokra @TheLarkInn

@philipwalton
Copy link

/cc @addyosmani

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.

@addyosmani
Copy link

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
Copy link

neeharv commented 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-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.

@Timer
Copy link
Contributor

Timer commented Sep 14, 2017

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

@addyosmani thanks for the clarification!

@neeharv
Copy link

neeharv commented 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
Copy link
Contributor

gaearon commented 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
Copy link

timwis commented 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
Copy link
Contributor

gaearon commented Jan 8, 2018

Yes, production build time is the one people are struggling with.

@viankakrisna
Copy link
Contributor

#2763
#913
some people already complain about slow production builds. It would be nice if we can do this parallelly though.

@gaearon
Copy link
Contributor

gaearon commented Jan 8, 2018

I don't really see us increasing complexity to support this.

@viankakrisna
Copy link
Contributor

yea, maybe someone can build a package that simplifies this. 😉

@gaearon gaearon closed this as completed Jan 14, 2018
@MidnightDesign
Copy link
Contributor

...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
Copy link

Djelnar commented Nov 6, 2018

@MidnightDesign cra v2 uses browserslist

@MidnightDesign
Copy link
Contributor

@Djelnar Does that imply that this should work now? How?

@lock lock bot locked and limited conversation to collaborators Jan 9, 2019
@petetnt petetnt reopened this Oct 10, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests