@zumper/polyfill
v0.11.0
Published
Prebuilt polyfills for use in Zumper's React applications
Downloads
34
Readme
Zumper polyfill
Package containing prebuilt polyfills for use in Zumper's React applications. The polyfill is broken into three versions to ensure that we deliver a more appropriate pollyfill based on the user's browser.
This polyfill is designed to work differently than @babel/polyfill
and react-app-polyfill
. Instead of including @zumper/polyfill
at the top of your entrypoint, you need to run code on the client to figure out which polyfill should load before initializing your app.
Instead of having just one polyfill, we decided to build four polyfills so that browsers with more capabilities will get a smaller file. This is similar in concept to what polyfill.io offers. However, instead of supporting an infinite number of polyfill combinations, we chose the groupings that seemed to balance file size well enough to suit our needs.
- A Grade: 1.5 kB gzipped, latest Chrome, Firefox and Safari
- B Grade: 10.2 kB gzipped, the latest features; requires very few polyfills.
- C Grade: 19.3 kB gzipped, fairly new; needs more polyfills.
- D Grade: 33.1 kB gzipped, needs the most polyfills.
Install
yarn add @zumper/polyfill
API
whichPolyfill
We include a convenience function that will perform feature tests in the browser and report which polyfill is best suited for that browser. This ensure that you don't ship unnecessary polyfills to browsers that don't need them.
<!-- don't do it this way in your app -->
<script src="https://unpkg.com/@zumper/polyfill/dist/whichPolyfill.min.js"></script>
<script>
var grade = whichPolyfill()
// choose the right polyfill for this browser
var polyfill = document.createElement('script')
polyfill.src =
'https://unpkg.com/@zumper/polyfill/dist/polyfill.' + grade + '.js'
document.body.appendChild(polyfill)
</script>
NOTE: Do not include the scripts directly from unpkg.com. It won't be very fast. See below for recommendations on how to integrate whichPolyfill
in production.
whichBundle
We also include a convenience function that will perform feature tests in the browser and report which bundle is best suited for that browser.
This is useful for serving different app bundles for modern and legacy browsers. It is independent of whichPolyfill
because it detects support for syntax, which cannot be polyfilled.
An a-grade browser can support things like async
functions natively where-as a d-grade browser would throw syntax errors. Using whichBundle
allows you to serve a different build of your app to different browsers.
<!-- don't do it this way in your app -->
<script src="https://unpkg.com/@zumper/polyfill/dist/whichBundle.min.js"></script>
<script>
var grade = whichBundle()
// choose the right bundle for this browser
var bundle = document.createElement('script')
bundle.src = 'https://example.com/js/bundle.' + grade + '.js'
document.body.appendChild(bundle)
</script>
NOTE: Do not include the scripts directly from unpkg.com. It won't be very fast. See below for recommendations on how to integrate whichBundle
in production.
Creating multiple builds
In your package.json
you will want to specify a "browserslist"
. Here you can see examples for each of the browser grades. Notice that Edge doesn't qualify as a b-grade browser because it doesn't support some key features based on @babel/preset-env
's plugin data. Notice also that we list d-grade browsers as "defaults" to maintain compatibility with build tools that expect a single browserslist.
"browserslist": {
"a-grade": [
"last 2 chrome versions",
"last 2 firefox versions",
"last 1 safari version",
"last 1 ios version"
],
"b-grade": [
"chrome 61",
"firefox 60",
"ios 12.2",
"safari 12.1"
],
"c-grade": [
"chrome 49",
"firefox 46",
"ios 10.3",
"safari 10.1",
"edge 14"
],
"defaults": [
"chrome 38",
"ie 11",
">0.2%",
"not dead",
"not op_mini all"
]
}
If you are using create-react-app you can create multiple targeted builds using BROWSERSLIST_ENV
.
Here is an example that would create two builds targeting b-grade and d-grade browsers.
# uses "defaults"
npx react-scripts build
# uses b-grade
BROWSERSLIST_ENV=b-grade npx react-scripts build
NOTE: Zumper uses a customized version of create-react-app that offers improved support for multiple builds.
Usage
Unlike other polyfills, @zumper/polyfill
takes some additional setup. It is designed to work with Zumper's code building strategy and may not suit all use cases. If you are looking for a simple polyfill solution, try @babel/polyfill
.
Including the polyfills in your build
We ship prebuilt polyfills in the dist
folder. Rather than specifying the polyfills as endpoints in your webpack config, it is recommended to use copy-webpack-plugin
to copy the prebuilt versions to your output.
const CopyWebpackPlugin = require('copy-webpack-plugin')
const config = {
plugins: [
new CopyWebpackPlugin([
{
from: './node_modules/@zumper/polyfill/dist/polyfill.*.min.js',
to: './build/js/',
transformPath: (targetPath) =>
targetPath.replace(
/(.*)node_modules[/]@zumper[/]polyfill[/]dist[/](.*[.]min[.]js)$/,
'$1$2'
),
},
]),
],
}
Embedding the whichPolyfill
function in your HTML
We also ship a whichPolyfill
function in the dist
folder. This function will identify the right polyfill for the current browser using feature detection.
It is possible to read the minified polyfill directly from the node_modules
folder.
Our recommendation is to embed the script directly into your rendered HTML to ensure the fastest performance.
NOTE: a similar approach should be used to embed whichBundle
in your page.
const readWhichPolyfill = () => {
// locate the minified file
const whichPolyfillPath = path.join(
path.dirname(require.resolve('@zumper/polyfill')),
'..',
'dist',
'whichPolyfill.min.js'
)
return fs.readFileSync(whichPolyfillPath)
}
// read the minified script to a string
let whichPolyfill
try {
whichPolyfill = readWhichPolyfill()
} catch (error) {
Log.debug(error)
whichPolyfill = "window.whichPolyfill = function() { return 'd-grade' };"
}
// presumably you'd add this to your node server response somewhere
export default `<script>${whichPolyfill}</script>`
Loading your bundles after loading the right polyfill
At Zumper, we use @loadable/component
to know which bundles to load. We generate our final HTML on the server and add those bundles to the output.
Below is an example of the bottom of a typical document.
- Use
<link rel="preload" />
to get the bundles downloading ASAP - Delay loading the polyfill until the HTML has finished loading
- Choose the right polyfill to load
- Delay loading the bundles until the polyfill has loaded
<!-- 1. use preload for all of the bundles -->
<link
rel="preload"
href="https://cdn.example.com/js/runtime-main.js"
as="script"
/>
<link rel="preload" href="https://cdn.example.com/js/main.js" as="script" />
<link rel="preload" href="https://cdn.example.com/js/homepage.js" as="script" />
<link rel="preload" href="https://cdn.example.com/css/main.css" as="style" />
<link
rel="preload"
href="https://cdn.example.com/css/homepage.css"
as="style"
/>
<script>
// ... embedded whichPolyfill.js (see above)
</script>
<script>
'use strict'
!(function () {
var bundles = [
'https://cdn.example.com/js/runtime-main.js',
'https://cdn.example.com/js/main.js',
'https://cdn.example.com/js/homepage.js',
]
var loadBundles = function () {
bundles.forEach(function (src) {
var bundle = document.createElement('script')
bundle.src = src
bundle.async = false
bundle.crossOrigin = 'anonymous'
document.body.appendChild(bundle)
})
}
var loadPolyfill = function () {
var polyfill = document.createElement('script')
var timeout = setTimeout(function () {
console.warn('Polyfill timed out!')
}, 1000)
// 3. Choose the right polyfill to load
polyfill.src =
'https://cdn.example.com/js/polyfill.' + whichPolyfill() + '.min.js'
polyfill.async = false
polyfill.crossOrigin = 'anonymous'
// 4. Delay loading the bundles until the polyfill has loaded
polyfill.onload = function () {
clearTimeout(timeout)
loadBundles()
}
polyfill.onerror = function (error) {
console.error('Polyfill load error!', error)
}
document.body.appendChild(polyfill)
}
// 2. Delay loading the polyfill until the HTML has finished loading
document.addEventListener('DOMContentLoaded', function () {
var raf =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.setTimeout
raf(loadPolyfill)
})
})()
</script>
Alternate import usage
We also publish cjs and es builds in the lib
and es
folders respectively. Feel free to let us know if those are useful to you!
// es version
import aGradePolyfill from '@zumper/polyfill/es/polyfill.a-grade'
import bGradePolyfill from '@zumper/polyfill/es/polyfill.b-grade'
// cjs version
import bGradePolyfill from '@zumper/polyfill/lib/polyfill.b-grade'
// umd version
import dGradePolyfill from '@zumper/polyfill/dist/polyfill.d-grade'
// by default we export the d-grade polyfill
import alsoDGradePolyfill from '@zumper/polyfill'
About the polyfills
We use @babel/polyfill
(which in turn uses core-js
) to generate each of the polyfill files. We also include raf
, whatwg-fetch
, intersection-observer
, smoothscroll-polyfill
and a requestIdleCallback
shim.
We essentially build the @babel/polyfill
four times with different settings for @babel/preset-env
each time.
A Grade
- last 2 chrome versions
- last 2 firefox versions
- last 1 safari version
- last 1 ios version
B Grade
Support for IntersectionObserver
, ES6 Modules, and URLSearchParams is required to be a b-grade browser.
- chrome: 61
- firefox: 60
- ios: 12.2
- safari: 12.1
- edge: 17 (considered c-grade for
whichBundle
)
C Grade
These are the first browsers to support both class
and fetch
.
- chrome: 49
- firefox: 46
- ios: 10.3
- safari: 10.1
- edge: 14
D Grade
These are all of the browsers that are more-or-less the same as IE 11.
- ie 11
- chrome 38
- > 0.2%
- not dead
- not op_mini all
Missing Polyfills?
We exclude a large number of polyfills that we didn't see much use for. A future version of this polyfill may make it possible to configure this list to suit your needs. In our experience, many of the polyfills for things like regular expressions, math operations and international dates are not very useful (for our projects).