🦎 -> DEvolution -> 🦖
de-evolution gun, as seen in Mario Bros, to help you ship modern, and de-modernized bundles.
Why?
-
ship more modern, more compact and more fast code to 85+% of your customers
-
do not worry about transpiling node_modules - use as modern code as you can everywhere
-
don't be bound to the bundler
-
well, it's just faster than a
multi-compiler mode
and 100% customizable. -
🚀 fast - uses swc to be a blazing 🔥 fast!
-
📱 multi threaded - uses jest-worker to consume all your CPU cores
-
🗜 compact - uses terser without mangling to re-compress the result
-
🦎 optimized - uses rollup to handle polyfills
-
🦖 supports
core-js
2 and 3
TWO bundles to rule the world
- One for "esm"(modern) browsers, which you may load using
type=module
- Another for an "old"(legacy) browser, which you may load using
nomodule
Usage
modern
target and call it a "baseline".
1. Compile your code to the - Prefer preset-modules
{
"presets": [
"@babel/preset-modules"
]
}
- However, feel free to use preset-env with esmodules
{
"presets": [
["@babel/preset-env", {
"targets": {
"esmodules": true
},
"useBuiltIns": "usage"
}]
]
}
useBuiltIns
are optional, and plays well withincludesPolyfills
option in.devolutionrc
- sucrase is an option
sucrase
is much faster than babel(and swc), however is able to produce only "modern" code. However, this is what we need. If your code is not using babel plugins, and non-yet-supported by the browsers code - feel free to use it.
2. Fire devolution to produce de-modernized bundles
the first run would create
.devolutionrc.js
, which could be used to tune some details
yarn devolution from to
// like
yarn devolution dist dist
It will convert all files in dist
into esm
and es5
targets in the same dist
By default it will handle only files in the directory, not including subdirs. You might return array of files via
.devolutionrc
to handle all your files
public-path
, somewhere close to the script start
3 (Only webpack) setup __webpack_public_path__ = devolutionBundle + '/'; // devolutionBundle is a predefined variable
Parcel
will configure public path automatically.
Symlink
Then devolution
will symlink resources to "sub-bundles"
4. Ship the right script to the browser
Please dont use code like this
<script type="module" src="esm/index.js"></script>
<script type="text/javascript" src="ie11/index.js" nomodule></script>
It does not work well for the really "old" browsers - IE11 will download both bundles, but execute only the right one. This syntax would made things even worse for the legacy browsers.
Use feature detection to pick the right bundle:
var script = document.createElement('script');
var prefix = (!('noModule' in script)) ? "/ie11" : "/esm";
script.src = prefix + "/index.js"; // or main? you better know
document.head.appendChild(script);
This "prefix" is all you need.
4, again. SSR this time
However, it's much better to use Server Side logic to pick the right bundle - you might control which bundle should be shipped in which case.
But default - use the same browsers
as they are listed in .devolutionrc.js
targets
for esm
,
however - you might "raise the bar", shipping modern code only to Chrome 80+
,
or introduce more than two bundles - the "language" in the top ones could be the same, but polyfills set would be different.
import UA from 'browserslist-useragent'
export const isModernBrowser = (userAgent) => {
return UA.matchesUA(userAgent, {
_allowHigherVersions: true,
browsers: [
"Chrome >= 61",
"Safari >= 10.1",
"iOS >= 11.3",
"Firefox >= 60",
"Edge >= 16"
]
})
}
function renderApp(req, res) {
const userAgent = req.headers['user-agent'];
const bundleMode = isModernBrowser(userAgent) ? 'esm' : 'es5';
// send the right scripts
}
See Optimising JS Delivery for details
5. Done!
A few minutes to setup, a few seconds to build
Tuning
See .devolutionrc.js
, it contains all information you might look for
FAQ
Why two separate folders?
In the most articles, you might find online, ES5 and ES6 bundles are generated independently,
and ES5 uses .js
extension, while ES6 uses .mjs
.
That requires two real bundling steps as long as "hashes" of files and "chunk names", bundles inside runtime-chunk
would be different.
That's why we generate two folders - to be able just to use prefix, to enable switching between bundles just using
__webpack_public_path__
or parcel script location autodetection.
Drawbacks
- !! doesn't play well with script prefetching - you have to manually specify to prefetch
esm
version, not the "original" one. - may duplicate polyfills across the chunks. Don't worry much
(default) Targets for "esm"
- edge: "16+",
- firefox: "60+",
- chrome: "61+",
- safari: "10.1+", (2017+)
(default) Targets for "ie5"
- ie: "11-"
That's is the oldest living browser, and can be used as a base line.
SWC
SWC is much faster than babel, however not as stable and might produce broken code.
Controlled by useSWC
, disabled by default
Terser
There are two options, which control minification - useTerser
and useTerserForBaseline
.
You have to enable useTerser
if you enable useSWC
as long as it produces non minified code.
API
You may file devolution manually
import {devolute} from 'devolution';
devolute(sourceDist, destDist, options)
// for example
devolute(
'dist', // the default webpack output
'dist', // the same directory could be used as well
require('.devolutionrc')
)
License
MIT