Skip to content
This repository has been archived by the owner on May 11, 2018. It is now read-only.

Breaking: make current "useBuiltIns" auto import only used + necessary polyfills per file #241

Merged
merged 14 commits into from
Apr 7, 2017

Conversation

hzoo
Copy link
Member

@hzoo hzoo commented Mar 31, 2017

https://twitter.com/left_pad/status/847911365645938690

Ref #84

TL;DR: automatically add polyfills based on actual usage in the file (check the presence of Promise), and remove any polyfills supported by the target environment (Chrome 55 already supports Promise). Check the tests in this pr for more examples!

Size savings: if you don't use any polyfills in your code: then none will be added. If you do use polyfills, only the ones not supported in your target environment will be added.

Examples

Older browser (like IE): imports are added

screen shot 2017-03-31 at 4 12 39 pm

Modern browser (Chrome 58): no imports added

screen shot 2017-03-31 at 4 17 53 pm


astexplorer live example (only the adding part): http://astexplorer.net/#/gist/dc031f702921a727ec5f82c2bc3341b0/6b87729fb2ed77e8512c0b137e8a3c19dbcdc63c


The only issue not covered in #84 (comment) is regarding the rest of node_modules (3rd party).

Basically the issue is that if you don't use Promise in any of the code that you run Babel on, this option doesn't know to include the Promise polyfill if a library you use needs it.

A workaround is a disclaimer of the option and to use includes instead. We can even warn if you include but it's natively supported.


Not sure about the option name, or if we should making a breaking change for it. It does seem like this should be opt-in since it doesn't know about the use of polyfills in 3rd party modules unless you run babel over those too

  • more tests (suggest some more?)
  • change the console.warn on instance methods to be part of the debug flag?
  • change option to be useBuiltIns, and move current option to be useBuiltIns: "entry"
  • fix import not being transform (due to use of Program.exit, should change to adding the imports in the visitors themselves)
  • Can we track VariableDeclaration with StringLiteral matched to built-in definition and in case of a[trackedVariable] include built-in according to StringLiteral value?

@codecov-io
Copy link

codecov-io commented Mar 31, 2017

Codecov Report

❗ No coverage uploaded for pull request base (2.0@c50d330). Click here to learn what that means.
The diff coverage is 77.14%.

Impacted file tree graph

@@          Coverage Diff           @@
##             2.0     #241   +/-   ##
======================================
  Coverage       ?   87.67%           
======================================
  Files          ?        6           
  Lines          ?      284           
  Branches       ?       99           
======================================
  Hits           ?      249           
  Misses         ?       17           
  Partials       ?       18
Impacted Files Coverage Δ
src/transform-polyfill-require-plugin.js 94.87% <ø> (ø)
src/normalize-options.js 87.09% <ø> (ø)
src/index.js 95.61% <100%> (ø)
src/built-in-definitions.js 100% <100%> (ø)
src/add-used-built-ins-plugin.js 75.51% <75.51%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c50d330...e68a7aa. Read the comment docs.

@@ -0,0 +1,4 @@
"use strict";

import "core-js/modules/es6.promise";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bug: not transformed due to program.exit

@@ -0,0 +1,108 @@
export const definitions = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is basically the opposite of built-in-features.js so maybe can just reverse it somehow

src/index.js Outdated
plugins.push([transformPolyfillRequirePlugin, { polyfills, regenerator }]);
} else if (addUsedBuiltIns) {
plugins.push([addUsedBuiltInsPlugin, { polyfills, regenerator }]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right now it's a separate option/plugin/file to make it easier to look at

@yavorsky
Copy link
Member

yavorsky commented Apr 1, 2017

Can we track VariableDeclaration with StringLiteral matched to built-in definition and in case of a[trackedVariable] include built-in according to StringLiteral value?

@hzoo
Copy link
Member Author

hzoo commented Apr 3, 2017

Yeah we can just dono if it's worth doing it

@hzoo
Copy link
Member Author

hzoo commented Apr 4, 2017

new idea:

Make this the default behavior in 2.0 (useBuiltIns: true) which is "aggressive" in removing polyfills that aren't used in the files. Another option would be the current useBuiltIns which only removes polyfills already available in the environment. And lastly useBuiltIns: false which doesn't do anything.

We need to make sure that babel-polyfill/core-js is included as a dependency though which I'm not sure how to do other than making a runtime error if it's not defined.

@hzoo hzoo changed the base branch from master to 2.0 April 4, 2017 18:12
…t core-js

return babel-polyfill require instead of core-js
@hzoo
Copy link
Member Author

hzoo commented Apr 4, 2017

9f754cf just appends babel-polyfill/ so it requires the same module (otherwise it requires you to have core-js in your dependencies when likely you have babel-polyfill or the wrong version of core-js)

README.md Outdated

> NOTE: Only use `require("babel-polyfill");` once in your whole app. One option is to create a single entry file that only contains the require statement.

This option enables a new plugin that replaces the statement `import "babel-polyfill"` or `require("babel-polyfill")` with individual requires for `babel-polyfill` based on environment.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth adding a note this was the behavior in 1.x?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, maybe not and just put it in upgrade guide.

state.opts.addUsedBuiltIns &&
console.warn(
`
Adding "import 'babel-polyfill'" isn't necessary with the addUsedBuiltIns option anymore.
Copy link
Member

@existentialism existentialism Apr 4, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addUsedBuiltIns -> useBuiltIns: true (same on L84)

@hzoo hzoo changed the title [WIP] create add-used-built-ins option Breaking: make current "useBuiltIns" auto import only used + necessary polyfills per file Apr 5, 2017
@@ -76,6 +76,6 @@ export default function normalizeOptions(opts) {
loose: validateLooseOption(opts.loose),
moduleType: validateModulesOption(opts.modules),
targets: opts.targets,
useBuiltIns: opts.useBuiltIns,
useBuiltIns: opts.useBuiltIns === undefined ? true : opts.useBuiltIns,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

breaking: default to true!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe convert this to a destructure with a default?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along with opts.target and opts.debug

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may also want to validate valid useBuiltIn values (false|true|'entry')

@@ -1,5 +1,5 @@
function isPolyfillSource(value) {
return value === "babel-polyfill" || value === "core-js";
Copy link
Member Author

@hzoo hzoo Apr 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the 7.0 babel-polyfill will have core-js modules in it we can just target that

@hzoo hzoo added this to the 2.0 milestone Apr 5, 2017
Copy link

@mikesherov mikesherov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

README.md Outdated
npm install babel-polyfill --save
```

This option enables a new plugin that replaces the statement `import "babel-polyfill"` or `require("babel-polyfill")` with individual requires for `babel-polyfill` based on environment.
#### `useBuiltIns: true`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you missing docs for useBuiltIns: false now? May seem obvious, but now that it's not the default you should at least describe the option.

src/index.js Outdated
useBuiltIns &&
plugins.push([transformPolyfillRequirePlugin, { polyfills, regenerator }]);
if (useBuiltIns === true) {
plugins.push([addUsedBuiltInsPlugin, { polyfills, regenerator, debug }]);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stray debug?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The debug flag is used in the plugin =>

state.opts.debug && warnOnInstanceMethod(getObjectString(node));

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we don't want to have those warnings, not sure if it's useful or not but we can iterate

@@ -76,6 +76,6 @@ export default function normalizeOptions(opts) {
loose: validateLooseOption(opts.loose),
moduleType: validateModulesOption(opts.modules),
targets: opts.targets,
useBuiltIns: opts.useBuiltIns,
useBuiltIns: opts.useBuiltIns === undefined ? true : opts.useBuiltIns,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe convert this to a destructure with a default?

@@ -76,6 +76,6 @@ export default function normalizeOptions(opts) {
loose: validateLooseOption(opts.loose),
moduleType: validateModulesOption(opts.modules),
targets: opts.targets,
useBuiltIns: opts.useBuiltIns,
useBuiltIns: opts.useBuiltIns === undefined ? true : opts.useBuiltIns,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along with opts.target and opts.debug

t.isIdentifier(prop) &&
has(definitions.instanceMethods, prop.name)
) {
state.opts.debug && warnOnInstanceMethod(getObjectString(node));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe move debug check into warnOnInstanceMethod?

function addUnsupported(path, polyfills, builtIn, builtIns) {
if (Array.isArray(builtIn)) {
for (const i of builtIn) {
if (polyfills.indexOf(i) !== -1) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe polyfills could be a Set so you get O(1) lookups instead of O(n)? Might be premature optimization.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's passed in as an array, so can turn it into a set before passing it down

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to review using a Set throughout the plugin in 2.x?

if (isRequire(bodyPath)) {
console.warn(
`
Adding "require('babel-polyfill')" isn't necessary with the useBuiltIns option anymore.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid "anymore". This message is valid for new and old users. To users it never was necessary

const builtIn = definitions.instanceMethods[prop.name];
addUnsupported(path, state.opts.polyfills, builtIn, this.builtIns);
} else if (
node.computed &&

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very cool that it catches: b['find'] but it won't catch var c = 'find'; b[c];.

And I know it's literally impossible to statically guarantee that you'll find all cases because of stuff like var c = 'fi' + 'nd'; b[c];

However, maybe it makes sense to iterate over all string literals and if any match an instance method name, include the polyfill?

I only say that because it's far more likely to see: b[someCondition ? 'find' : 'findIndex'] and you never see the literal string form because you can use non computed always in that case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok #241 (comment). Yeah we could check strings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'l use path.evaluate

const res = path.get("property").evaluate();
if (res.confident) {
const builtIn = definitions.instanceMethods[res.value];
warnOnInstanceMethod(state, `${obj.name}['${prop.value}']`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be res.value?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah forgot to change 👍

When setting 'useBuiltIns: true', polyfills are automatically imported when needed.
Please remove the call or use 'useBuiltIns: "entry"' instead.
When setting "useBuiltIns: true", polyfills are automatically imported when needed.
Please remove the "import 'babel-polyfill'" call or use "useBuiltIns: 'entry'" instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be "require('babel-polyfill')"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol almost forgot why it was 2 messages in the first place then

README.md Outdated
@@ -176,21 +174,60 @@ This option is useful for "blacklisting" a transform like `transform-regenerator

### `useBuiltIns`

`boolean`, defaults to `false`.
`boolean`, defaults to `true`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe consider with entry option?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean? I have #### useBuiltIns: 'entry' below

Copy link
Member

@yavorsky yavorsky Apr 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

entry is a string, maybe boolean | "entry"?

@existentialism
Copy link
Member

👏

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants