vuejs / vue-cli

🛠️ webpack-based tooling for Vue.js Development
https://cli.vuejs.org/
MIT License
29.75k stars 6.33k forks source link

Better polyfill default setting for @vue/babel-preset-app #1248

Closed LinusBorg closed 6 years ago

LinusBorg commented 6 years ago

What problem does this feature solve?

The current implementation of our bable preset sets the useBuiltIns option of the `babel-preset-env preset to usage:

https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/babel-preset-app/index.js#L19

What that means it that only feature that babel comes across during transpilation are actually polyfilled.

The good

This is awesome since it keeps unnecessary polyfills out of our app

the bad

It's bad because any code in node_modules that relies on a poylfill requires the developer to manually inlcude this polyfill in i.e.main.js so it's picked up by babel. This is because we don't run /node_modules contents through babel, so the usage options doesn'T pick up the feature

The alternative

I think the current default is resulting in bad dev UX, because the dev has to know and understand about the way that usage works.

As an alternative, I propose to select entry as the defsault value for useBuiltIns.

The good

Now all features necessary for the targeted browsers will be polyfilled. That ensures that all code from /node_modules relying on a feature that we ourselves don't use will work nonetheless.

The bad

Unnecessary polyfills might be included. To allow the dev to optimize this, we could document how he could switch to usage polyfilling and what the implications are.

What does the proposed API look like?

https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/babel-preset-app/index.js#L19:

-    useBuiltIns = 'usage',
+    useBuiltIns = 'entry',

https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-service/generator/template/src/main.js

+ import '@babel/polyfill'
import Vue from 'vue'
import App from './App.vue'
pranavjindal999 commented 6 years ago

Yes this is a big issue for me. I use Vuetify and it relies on some new features like Object.values() which I don't use and without including all babel polyfills in main.js, my app breaks in IE. And including all babel polyfills adds up a weight of around 34 KB gzipped which is costly.

But I also faced an issue where I use Promise.finally in my code but it doesn't get included at all in polyfills, no matter what I do. I had to rely on polyfill service for that, and so I disabled all babel polyfills and just relied on the service. May be I did something wrong, but didn't change any cli3 configs, it just doesn't work out of the box for Promise.finally

LinusBorg commented 6 years ago

Promise.finally is a stage4 proposal, it's not officially standard yet. and I babel-preset-env only polyfills things that are in the standard right now, as far as I understand it.

you can probably use the include option to force adding such a polyfill.

pranavjindal999 commented 6 years ago

I'm not sure but what does following part mean in @vue/babel-preset-app/index.js

Doesn't it mean that it will include all proposals from stage 2 and above?

  // stage 2. This includes some important transforms, e.g. dynamic import
  // and rest object spread.
  presets.push([require('@babel/preset-stage-2'), {
    loose,
    useBuiltIns: useBuiltIns !== false,
    decoratorsLegacy: decoratorsLegacy !== false
  }])

image

LinusBorg commented 6 years ago

Doesn't it mean that it will include all proposals from stage 2 and above?

It will include all babel-transform plugins for stage two, not polyfills. Those are two different things.

pranavjindal999 commented 6 years ago

Thanks man. You comment and you know things !!😄

By the way how do we explicitly add stage x polyfills gracefully with vue-cli 3?

LinusBorg commented 6 years ago

the most easy way would be to import them from core-js which babel-polyfill uses under the hood):

import 'core-js/fn/promise/finally'
// https://github.com/zloirock/core-js/blob/master/fn/promise/finally.js
yyx990803 commented 6 years ago

With useBuiltIns: 'entry' and the default browsers target it bloats the hello world app by about 24kb gzipped (vendor chunk size: 21.86kb vs 45.49.kb), which is a lot, especially when a large chunk of them may end up not used at all. I'm not sure if this is something we should use as the default.

Speaking of dependencies, isn't it a convention for any library to clearly state its browser compatibility requirements?

I think it's quite rare for a dependency to (1) ship ES5 code that doesn't need transpilation and (2) at the same time requires some ES6 only polyfill to work (again, excluding Promise).

So I think the saner default would be:

  1. include a number of commonly needed polyfills by default (Promise, Object.assign). This should cover most use cases (e.g. Vuex). In addition these should only be included if they are actually needed for the project's browserslist targets.

  2. For dependencies that does not work in the target environment, the user should explicitly include it in transpileDependencies, which would handle both syntax and polyfills.

  3. In the rare case where a dependency ships ES5 code but requires ES6 polyfills, the user need to explicitly include the polyfills.

LinusBorg commented 6 years ago

I see the issue concerning the bundle size, definitely a problem.

If we keep usage as the default, we will have to make sure that users understand the implications.

I think it's quite rare for a dependency to (1) ship ES5 code that doesn't need transpilation and (2) at the same time requires some ES6 only polyfill to work (again, excluding Promise).

A prominent example would be Vuetify - I think their use of Object.values()/entries() was what made me aware of the issue.

In a perfect world, packages that provide ES5 would also make sure not to use Es6 features that require polyfills, or if they do, make sure to explain that - and indeed, Vuetify does so:

https://vuetifyjs.com/en/getting-started/quick-start

But my experience is that there are many developers out there that don't understand the intricacies of of how babel-preset-env works regarding polyfills (for beginners it's even hard to undestand the differences between tranforms and polyfills), so once reading that vue-cli included polyfills, they often think they are fine - "what should be left to do? polyfills are already included!".

Vue-cli's promise it to abstract way the dependencies and configuration, so it's understandable people don't feel the need to instantly learn in detail how all the moving parts that the cli covers for them work.

So in short: yes, I agree we can keep usage for the sake of bundle-size, but then we have to make people aware what that means.

yyx990803 commented 6 years ago

I think it's problematic for Vuetify to rely on Object.entries() and Object.values() which are pretty new additions (ES2017) - they provide marginal value but complicate the user polyfill requirements. /cc @johnleider

johnleider commented 6 years ago

I'll take a look at the scope of this.

yyx990803 commented 6 years ago

Looks like Vuetify relies on too many ES6+ built-ins to be special-cased for, so I think Vuetify users should just manually opt-in to useBuiltIns: 'entry' + @import '@babel/polyfill.

johnleider commented 6 years ago

This is what we have setup on our cli plugin https://github.com/vuetifyjs/vue-cli-plugin-vuetify/blob/dev/generator/index.js#L67 . So I think that covers it.

yyx990803 commented 6 years ago

@johnleider ah cool. Yeah that covers it.

yyx990803 commented 6 years ago

I intend to keep the default for now (with Promise included by default as it is required by Vuex and most ajax clients). We will have a dedicated section in docs talking about browser targets and polyfills etc.

miaulightouch commented 6 years ago

is this issue solved?

according this guide: https://cli.vuejs.org/guide/browser-compatibility.html#polyfills

if I set transpileDependencies: ['pretty-ms'] in vue.config.js, webpack warn me "export 'default' (imported as 'prettyMs') was not found in 'pretty-ms' with import prettyMs from 'pretty-ms' statement.

if use const prettyMs = require('pretty-ms') instead, seems to solve the problem.

but both show this error in browser: Uncaught TypeError: Cannot assign to read only property 'exports' of object '#<Object>'

(highlighted code throw error)

LinusBorg commented 6 years ago

I just took a look at that repository, there's no reason to add it to transpileDependencies, it contains no es6 code?

miaulightouch commented 6 years ago

arrow function and constants declaration?

pretty-ms source: https://github.com/sindresorhus/pretty-ms/blob/master/index.js

LinusBorg commented 6 years ago

Oh, must have been blind for a second...

miaulightouch commented 6 years ago

um, I'm not sure this would be a solution, but solved my problem.

I create a new project using webpack template and enforce included all dependencies defined in package.json to babel-loader, and set preset-env to modules: commonjs.

this make babel detect all used modules, also include promise polyfill for vuex automatically, and transpile all es6 imports into commonjs.

webpack template demo: https://gitlab.com/MiausF2E/F_2_E-todo/

miaulightouch commented 6 years ago

OK, I got it.

vue-cli require a minor hack, set process.env.VUE_CLI_BABEL_TRANSPILE_MODULES = true to ask @vue/babel-preset-app to transpile modules into commonjs.

vue-cli demo: https://gitlab.com/MiausF2E/f2e-todos-cli-demo

adamorlowskipoland commented 5 years ago

Looks like Vuetify relies on too many ES6+ built-ins to be special-cased for, so I think Vuetify users should just manually opt-in to useBuiltIns: 'entry' + @import '@babel/polyfill.

Guys I've tryied many solution and nothing seems to work. Still have blank pages in EDGE (no errors) and IE11 (error: image & image

Do you have any ideas what is wrong?

LinusBorg commented 5 years ago
  1. no, we don't - you didn'T share any information of what "I've tryied many solution" means and what your code does right now.
  2. This issue is closed and not a support ticket. Please join us on fourm.vuejs.org to ask for help.