-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
RFC: Rethink polyfilling story #10008
Comments
Is there a short-term possibility of just creating a fork of In that scenario, we wouldn't need to rethink Babel's entire plugin architecture, but could offer significant bundle-size reductions to most library authors with a (considerably) lower level of effort. Then, we could expose an API that looks something like: {
"presets": [
["@babel/preset-env", { "targets": [">1%"], "corejs": 3, "transformRuntime": {
"helpers": true, "regenerator": false
} }]
]
} I think this proposal has considerable long-term merits, but it also seems like we can achieve 95% of what people need in a much shorter timeframe. |
Had pretty much the same idea as @schmod but he beat me to it. Never quite understood why several parts of transform-runtime were not brought over to preset-env before. E.g. the deduplication of helpers like class construction into separate modules; that seems like a very beneficial general purpose optimization you would want people to take. And thus seems quite a sane default. There will be people that need to compile one self-contained module with the helpers inlined. And they can turn that off. Now, with env's browserslist becoming a bigger thing with conditional polyfilling, it just seems like it makes more sense to integrate the transform-runtime plugin wholesale.
And then when that's all fine and working -- and tested in practice, work on adding the layer of indirection and extensibility with polyfill providers. |
@schmod If {
"presets": [
["@babel/preset-env", { "targets": [">1%"] }],
],
"plugins": [
["@babel/transform-runtime", {
"corejs": 3,
"helpers": true,
"regenerator": false,
}]
]
} |
I'd love to see a simple It would be relatively easy to implement and provide an immediate upgrade path without worrying about deprecating anything people are already using. That doesn't mean keeping it around forever, but it's a lot easier to steer people already using |
Really, |
Well put. For something as big as an entire injection architecture around polyfills, easing people in with an easy-to-adopt upgrade path first has some benefits that should be considered, imho. Basically; it prevents people from running into a brick wall; or from slamming on the brakes full-stop because they think they're going to. Speaking from my own experience, I think the 'full' solution would be kind of a hard sell for some of the development teams I'm involved with, whereas the outwardly deceptively simple API of just another config switch value can do a lot to mitigate concern. |
Thank you all for your feedback.
You are right, @rjgotten |
If they would land simultaneously, I still think there's value in there being an easy adoption mode that gets users going and can ween them off of babel-runtime based polyfills. For the full package -- i.e. not just ES polyfills but also Node; Web platform; etc. -- you'd still need to go 'all in' afterwards, but that can still be a 'stage 2' of a migration process. (And one that'll be considerably easier to sell, also to a non-technical management tier, imho.) |
Any news? |
Great! Would really love to have this feature. Not sure how ponyfills are implemented right now. Consider scenario when ponyfill is implemented using some of the language builtins, but if thise builtins are not avaialbe in target they are replaced by their ponyfills. Is it the case? Or ponyfills always implemented based on other ponyfills without making assumptions on evironement? |
That's up to the ponyfill that would be used. For example, an import _some from "./array.prototype.some";
export default function includes(array, item) {
return _some(array, val => val === item);
} Or it could assume that export default function includes(array, item) {
return array.some(val => val === item);
} |
Thanks for the example snippet. Anyway. I'd love to contribute. Do you see any blockers? |
I'm currently working on this privately, because it's a really big feature and I need the ability to commit/refactor whatever I want until it's ready to be shared publicly. I'll let you know when it's ready to be made public! |
A long time ago I wrote that we should discuss it. I wrote that it's over-complicated. Ok, I'll write what I mean here. Yes, we should simplify adding new plugins for polyfilling and we should extract But we don't need new conceptions like "polyfill provider" and "polyfill injector". Why add new entities? Moreover - entities based on A solution is very simple - we can allow defining targets at the top level of We will have {
"targets": [">1%"],
"presets": [
["@babel/preset-env"]
],
"plugins": [
"intl-polyfill",
"fetch-polyfill",
["corejs3", { "proposals": true }]
]
} Instead of {
"presets": [
["@babel/preset-env", { "targets": [">1%"] }]
],
"plugins": [
["@babel/inject-polyfills", {
"method": "usage-global",
"providers": [
"intl-polyfill",
"fetch-polyfill",
["corejs3", { "proposals": true }]
]
}],
]
} It's plain and, for me, - more simple and obvious. We should not limit the plugin developer to only proposed modes: "New developer experience"? The development of any serious plugin for polyfilling will go much further than suggested here. For the development of simple plugins, could be added pseudo-visitors or just helpers like proposed here. Serious polyfilling plugins should be written as usual plugins, the only moment that's required - as I wrote, passing targets as an option. |
@nicolo-ribaudo any feedback? |
@zloirock I can relate with your proposal, but does it require too big of a change from current implementation? @nicolo-ribaudo Some time has passed and no visible progress on this issue. |
Yeah sorry for postponing this, I will try to work again on this feature in January/February. Two quick comments:
|
My proposal is adding support of top-level |
I don't want to pollute the global scope and also I don't want to include so much and unnecessary polyfills while using babel-plugin-transform-runtime. I think this future is the one of the needed one on next releases.Any update on that? |
I made the I have not published the If anyone has feedback, please open issues in that repository! |
Thanks for the answer. My suggestion is that after you finish your job and open it to the usage of community please write detailed docs and maybe some medium.com articles about the past and future usage of that target/babel/transform runtime relation. Because I see that there might be some misunderstandings about the usage of polyfill, ponyfills, core.js,transform runtime plugin and babel. Some developers really have some confusions about the true usage of babel. It will be good to have detailed and official docs which tells the past usages and future usages of that things. |
Sure, proper docs and a blog post is one of the things I need to finish before the first release! |
I'm closing this issue since we have released If there is anythig more to discuss, please open an issue there 😉 |
In the last three years and a half,
@babel/preset-env
has shown its full potential in reducing bundle sizes not only by not transpiling supported syntax features, but also by not including unnecessarycore-js
polyfills.Currently Babel has three different ways to inject
core-js
polyfills in the source code:@babel/preset-env
'suseBuiltIns: "entry"
option, it is possible to inject polyfills for every ECMAScript functionality not natively supported by the target browsers;useBuiltIns: "usage"
, Babel will only inject polyfills for unsupported ECMAScript features but only if they are actually used in the input souce code;@babel/plugin-transform-runtime
, Babel will inject ponyfills (which are "pure" and don't pollute the global scope) for every used ECMAScript feature supported bycore-js
. This is usually used by library authors.Our position in the JavaScript ecosystem allows us to push these optimizations even further.
@babel/plugin-transform-runtime
has big advantages for some users overuseBuiltIns
, but it doesn't consider target environments: it's 2020 and probably very few people need to load anArray.prototype.forEach
polyfill.Additionally, why should we limit this ability to automatically inject only the necessary polyfill to
core-js
? There are also DOM polyfills, Intl polyfills, and polyfills for a myriad of other web platform APIs. Additionally, not everyone wants to usecore-js
: there are many other valid ECMAScript polyfills, which have different tradeoffs (e.g. source size vs spec compliancy) and may work better for some users.What if the logic to inject them was not related to the actual data about the available or required polyfills, so that they can be used and developed independently?
Concepts
Polyfill provider is a special kind of Babel plugin that injects is used to specify which JavaScript expressions need to be polyfilled, where to load that polyfill from and how to apply it. Multiple polyfill providers can be used at the same time, so that users can load, for example, both an ECMAScript polyfill provider and a DOM-related one.
Polyfill providers can expose three different methods of injecting the polyfills:
entry-global
, which reflects the currentuseBuiltIns: "entry"
option of@babel/preset-env
;usage-global
, which reflects the currentuseBuiltIns: "usage"
option of@babel/preset-env
;usage-pure
, which reflects the current polyfilling behavior of@babel/plugin-transform-runtime
.Every interested project should have their own polyfill provider: for example,
babel-plugin-polyfill-corejs3
or@ungap/babel-plugin-polyfill
.New user experience
Suppose a user is testing some ECMAScript proposals, they are localizing their application using
Intl
and they are usingfetch
. To avoid loading too many bytes of polyfills, they are ok with supporting only commonly used browsers, and they don't want to load unused polyfills.How would their new config look like?
New developer experience
In order to provide consistent APIs and functionalities to our users, we will provide utilities to:
We can provide those APIs in a new
@babel/helper-define-polyfill-provider
package.These new APIs will look like this:
The
createPolyfillProvider
function will take a polyfll plugin factory, and wrap it to create a proper Babel plugin. The factory function takes the same arguments as any other plugin: an instance of an API object and the polyfill options.It's return value is different from the object returned by normal plugins: it will have an optional method for each of the possible polyfill implementations. We won't disallow other keys in that object, so that we will be able to easily introduce new kind of polyfills, like
"inline"
.Every polyfilling method will take three parameters:
meta
object describing the built-in to be polyfilled:Promise
->{ kind: "global", name: "Promise" }
Promise.try
->{ kind: "property", object: "Promise", key: "try", placement: "static" }
[].includes
->{ kind: "property", object: "Array", key: "includes", placement: "prototype" }
foo().includes
->{ kind: "property", object: null, key: "includes", placement: null }
utils
object, exposing a few methods to inject imports in the current program.NodePath
of the expression which triggered the polyfill provider call. It could be anImportDeclaration
(forentry-global
), an identifier (or computed expression) representing the method name, or aBinaryExpression
for"foo" in Bar
checks.Polyfill providers will be able to specify custom visitors (like normal plugins): for exapmle,
core-js
needs to inject some polyfills foryield*
expressions.How does this affect the current plugins?
Implementing this RFC won't require changing any of the existing plugins.
We can start working on it as an experiment (like it was done for
@babel/preset-env
), and wait to see how the community reacts to it.If it will then success, we should integrate it in the main Babel project. It should be possible to do this without breaking changes:
useBuiltIns
implementation from@babel/preset-env
, and delegate to this plugin:useBuiltIns
is enabled,@babel/preset-env
will enable@babel/inject-polyfills
corejs
option, it will inject the correct polyfill provider plugin.regenerator
andcorejs
options from@babel/plugin-transform-runtime
: both should be implemented in their own polyfill providers.Open questions
Should the polyfill injector be part ofNo.@babel/preset-env
?targets
. On the other hand, it would be more complex but feasible even if it was a separate plugin.@babel/preset-env
. Currently@babel/plugin-transform-runtime
has this capability.@babel/helper-polyfill-provider
should be a separate package. Otherwise, we can just export the needed hepers from the plugin package.@babel/preset-env
could include it.core-js
polyfilling logic in@babel/preset-env
and@babel/plugin-transform-runtime
has been mostly maintained by Denis (@zloirock).regenerator
andcore-js
providers should probably be in the Babel org (or at least supported by us), since we have been officially supporting them so far.Annex A: The ideal evolution after this proposal
This proposal defines polyfill providers without requiring any change to the current Babel architecture. This is an important point, because will allow us to experiment more freely.
However, I think that to further improve the user experience we should:
targets
option to the top-level configuration. By doing so, it can be shared by different plugins, presets or polyfill providers.polyfills
option, similar to the existingpresets
andplugins
.By doing so, the above configuration would become like this:
# Annex B: The current relationship between Babel and
core-js
Babel is a compiler,
core-js
is a polyfill.A compiler is used to make modern syntax work in old browsers; a polyfill is used to make modern native functions work in old browsers. You usually want to use both, so that you can write modern code and run it in old browsers without problems.
However, compilers and polyfills are two independent units. There are a lot of compiler you can choose (Babel, TypeScript, Traceur, swc, ...), and a lot of polyfills you can choose (
core-js
,es-shims
,polyfill.io
, ...).You can choose them independently, but for historical reasons (and because
core-js
is a really good polyfill!) so far Babel has made it easier to usecore-js
.What does the Babel compiler do with
core-js
?Babel internally does not depend on
core-js
. What it does is providing a simple way of automatically generate imports tocore-js
in your code.Babel provides a plugin to generate imports to
core-js
in your code. It's then your code that depends oncore-js
.In order to generate those imports, we don't need to depend on
core-js
: we handle your code as if it was a simple string, similarly to how this function does:(well, it's not that simple! 😛)
Be careful though: even if Babel doesn't depend on
core-js
, your code will do!Is there any other way in which Babel directly depends on
core-js
?Kind of. While the Babel compiler itself doesn't depend on
core-js
, we provide a few runtime packages that might be used at runtime by your application that depend on it.@babel/polyfill
is a "proxy package": all what it does is importingregenerator-runtime
(a runtime helper used for generators) andcore-js
2. This package has been deprecated at least since the release ofcore-js
3 in favor of the direct inclusion of those two other packages.One of the reasons we deprecated it is that many users didn't understand that
@babel/polyfill
just importedcore-js
code, effectively not giving to the project the recognition it deserved.@babel/runtime
contains all the Babel runtime helpers.)@babel/runtime-corejs2
is@babel/runtime
"proxy files" tocore-js
. Imports to this package are injected by@babel/plugin-transform-runtime
, similarly to how@babel/preset-env
injects imports tocore-js
.@babel/runtime-corejs3
is the same, but depending oncore-js-pure
3 (which is mostlycore-js
but without attaching polyfills to the global scope).With the polyfill providers proposed in this RFC, we will just generate imports to
core-js-pure
when using@babel/plugin-transform-runtime
rather than using the@babel/runtime-corejs3
"proxy".Related issues
The text was updated successfully, but these errors were encountered: