-
-
Notifications
You must be signed in to change notification settings - Fork 158
useBuiltIns: figure out how to remove polyfills that aren't actually used in the codebase #84
Comments
So here is an idea (atleast in terms of webpack). //cc @patrickkettner of Modernizr who this could be highly relevant for. On demand async polyfill chunksBackground: ContextModules with webpackSince webpack is a static build tool, there is no real dynamic requires, etc. However, there is a static solution to this dynamic nature. Given the following builtins are statically located somewhere:
One can pass an expression to a dependency statement. Specifically function asyncLoadPoly(builtInUniqueName) {
return `import('./builtins' + builtInUniqueName)`
} webpack will create a separate chunk for each possible module that could be loaded from that folder. This will then return a Promise and one can load that specific poly at runtime: if (someLogicAboutSpecificJSFeature) {
asyncLoadPoly('somePoly1.js').then(
return module.default;
)
} IdeaUse this pattern to "dynamically" require x polyfills based on a series of fast logic operations that async load async polyfill chunks as they are needed through out the web application using the above technique. Challenges
Wins
Really this is just one idea that we could leverage, however it could be powerful if we determine the right logic cases up front and what users are targeting. |
@TheLarkInn on demand chunks means that the build step is faster, but runtime it has to be blocking, so more waiting for the client. @hzoo how can you analyze for method usages (e.g. |
Well to create context modules it may be slower but I guess the blocking part really is about where the polyfill is needed for which parts of your code. |
So the problem could be reduced to usage detection, then replace the single top-file import of I guess this would be possible with ponyfills. Another solution could be to make the polyfills as lazy chunk loaders so that the first time it is used runtime it gets the actual polyfill and replace itself. |
Something like featuresToPolyfill.map(({ object, feature }) => {
object.prototype[feature] = (...args) => {
object.prototype[feature] = syncFetchPolyfill(object, feature)
return object.prototype[feature](...args)
}
}) |
@EnoahNetzach So for
|
@hzoo exactly, that's the problem |
Is it really a problem though (it is if we want to do the best thing possible, but were just talking about an incremental improvement here)? My point is that doing that is better than not doing the check at all. It just doesn't save as much if there are false positives. |
Oh, I get it. But what if someone uses something like this? function troll() { return 'includes'; }
obviousArray[troll()](); |
Exactly! better than worse-case So that's different lol. It's just a straightforward AST check and we aren't going to support something like that - just add a disclaimer since it's not normal to do that. Or more that people that are willing to use this will want to make sure they aren't doing anything like that. |
Or maybe leave the option to forcefully import something, if known: import `babel-polyfill/a`
import `babel-polyfill/b`
x[f()]() where the programmer knows that |
We do a similar thing (add a disclaimer to the docs) for the |
We can include polyfills in the whitelist/blacklist options if necessary |
@TheLarkInn unrealistically. |
@hzoo as I wrote, the main problem - we should also parse all used dependencies and should know all required features at the moment of transformation. |
Yeah I don't think we have a way to do that yet standalone unless we do something like make the preset as 2 passes and then write the used polyfills into a /tmp directory and read it in the 2nd pass (and pass to useBuiltIns) |
Would it be crazy to consider building out UA specific chunks, and have a special loader that does a UA sniff for which polyfills to load? Kind of like a local polyfill.io? |
I don't think so - it seems like a lot of folks are interested? |
to be clear, I think UA specific builds is dangerous. Feature specific builds (i.e. target es3, es5, etc) is great, but saying here is a chrome-build, here is a IE-build, etc is footguns waiting to be fired |
@patrickkettner not for IE. It's a terminal browser that is no longer being updated with a static set of feature. |
I think the main win is targeting non-evergreen browsers (ie 11, safari 9)? Otherwise many would be supporting something like |
Runtime dynamic loading of polyfills should never be done automatically. The best case scenario would be to have a generated "polyfill-loader.js" file with an It is also not safe to execute any javascript until that If your main JS is critical for rendering, you will also get a flash of unrendered content until the polyfills finish loading and it's safe to run JS. At least sync JS doesn't have the potential to show a broken page until it finishes loading (depending on where you place the |
After all I think babel should just have to be able to eliminate dead imports, probably by a simple ast transform per file, then bundlers should have the honor to decide what's really needed when. It's not a transpiler work to figure out the shape of what the client needs (e.g. chunks), but to give to instruments down the line the tools necessary to make this decision, otherwise I see a duplication of concerns (and compatibility issues). |
Not sure how that would work? Currently we would import the polyfill once and the issue was that you might not use that method/polyfill in the whole app (so you can't do a per file check) |
Hey there, Babel folks! I've been working on something that basically does this kind of static analysis, and I just made the code public since you all are discussing solving this problem. It's called I think it could be used on individual modules pre-bundling, as the bundler should deal effectively with multiple files that all import the same polyfill. Unfortunately, my plugin probably doesn't work well with this project's current strategy of expanding It's still very new (just been working on it a week or two), but it's got some tests and I think covers many (most?) basic cases. If y'all want, I'd be happy to offer it under a compatible license. Please let me know what you think, and thanks for all your great work! |
@hzoo the transformer could remove the "global" The so-imported module would just have to pollute the global with polyfills. |
Sorry for the double post, but I've been thinking about this problem for the last day or so, and I think I don't understand something that was said above:
I'm probably missing something, but it's not clear to me why this is the case. I think that the output of this preset should be adding the "exactly correct" set of imports, which is the intersection of (builtins used by the code) ∩ (builtins NOT supported natively by env). It seems to me that as long as the preset adds this exact set of imports, it could be applied at either the file level or at the codebase level. Am I missing something here? If I'm right, then the preset needs to do a subtraction step to make the intersection. This could be accomplished a few ways:
Am I on the right track here? Thanks for you consideration! |
I'm also looking for the same functionality as @romulof. |
I ended up disabling |
@romulof Would you care to explain how you did it? Thanks! |
I leave all my Babel config inside webpack config, and all build parameters are processed by env vars using https://github.com/af/envalid. Here's it's config object. I think it's pretty self-explanatory: {
cacheDirectory: env.isDev,
presets: [
'react',
['env', {
debug: true,
modules: false, // Using webpack 2
targets: {
// browserlist var is shared with autoprefixer
browsers: browserlist,
},
exclude: [
// Leave regenerator to transform-runtime
'transform-regenerator',
],
}],
],
plugins: compact([
'lodash',
'transform-decorators-legacy',
'transform-class-properties',
'transform-export-extensions',
'transform-object-rest-spread',
env.isProduction && 'transform-react-remove-prop-types',
env.isProduction && 'transform-react-constant-elements',
env.isProduction && 'transform-react-inline-elements',
env.isProduction && 'transform-dev-warning',
['transform-runtime', {
// Don't use regenerator in dev builds. Hard to debug.
regenerator: env.isProduction,
}],
env.isDev && 'react-hot-loader/babel',
env.isDev && 'transform-react-jsx-self',
env.isDev && 'transform-react-jsx-source',
]),
} Right now this is working for me, but might not apply to all cases. |
@romulof Nice! Why are you not using {
"plugins": ["transform-decorators-legacy", "other common plugins"], // common plugins
"env": {
"production": {
"plugins": ["regenerator"] // plugins for prod
},
"development": {
"plugins": ["react-hot-loader/babel"] // plugins for dev
}
}
} |
It's just to make the whole Webpack config uniform. In the end it works the same way. PS: |
Ok, got it working more or less. But My babel configuration: {
"presets": [
["env", {
"modules": false,
"loose": true,
"debug": true,
"targets": {
"browsers": "chrome 52",
}
}]
],
"plugins": [
["transform-runtime", {
"helpers": false,
"polyfill": true,
"regenerator": true,
"moduleName": "babel-runtime"
}],
["external-helpers"]
]
} If it is relevant in any way, I'm bundling with |
|
Ok I think i'm going to try what @aickin was talking about. It's actually just what transform-runtime already does, except we are now going to add the polyfill instead of rewriting to reference babel-runtime. So we just add the used imports and then remove the ones provided by the env. Will post an example later, still wip |
Ok I made #241 only issue not covered in #84 (comment) is regarding the rest of node_modules (3rd party). Workaround is a disclaimer of the option and to use Please check the tests and leave feedback! |
Merged ^, testing in v2.0.0-alpha.5 |
Closing in favor of a new issue I made: #284 since we kinda got this working but need some more work for 3rd party modules |
Goal: only include necessary polyfills
Don't include if supported environments support it natively #20
gets transformed to
Don't include if you have blacklisted it yourself (regenerator for example) #60
Don't include if the codebase doesn't actually use the builtIn (this)
The problem with the first heading ^ is that even if you remove the polyfills supported natively in the environment, you might not actually use them in your codebase anyway.
Possible solution: run an analyzer (separate babel pass like transform-runtime) on the whole codebase and remove imports that aren't used anywhere.
EDIT: A big problem atm is that Babel runs per file. So unless we understand the whole codebase it sounds like something webpack could do? cc @TheLarkInn @keyanzhang
The text was updated successfully, but these errors were encountered: