How to Deploy ES2015+ JavaScript in Production Without Unnecessary Polyfills
This article explains how modern browsers that support ES modules can load native ES2015+ JavaScript while older browsers receive a compiled ES5 fallback, detailing conditional polyfill loading, webpack and Babel configurations, script tag usage, performance benefits, and practical implementation steps.
Most frontend developers love using new JavaScript features such as async/await, classes, and arrow functions. Although all modern browsers can run ES2015+ code, many still transpile to ES5 with polyfills to support older browsers.
This situation is sub‑optimal; ideally we would only send the code that each browser can execute.
By using feature detection together with the <script type=\"module\"> tag we can conditionally load polyfills, but new syntax can cause parsing errors in browsers that do not understand it, making detection tricky.
Fortunately, browsers that support <script type=\"module\"> also support most ES2015+ syntax (async, classes, arrow functions, fetch, promises, Map, Set, etc.). The only thing we need to provide for non‑supporting browsers is a fallback bundle.
Implementation
If you already use a bundler such as Webpack or Rollup, keep your existing configuration and add a second configuration that outputs an ES2015+ bundle without transpiling to ES5 or adding polyfills.
When using babel-preset-env, configure it to target browsers that support modules, so Babel will skip unnecessary transformations.
Example Webpack configuration for the legacy (ES5) bundle:
module.exports = {
entry: { 'main-legacy': './path/to/main.js' },
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'public')
},
module: {
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [[
'env', {
modules: false,
useBuiltIns: true,
targets: { browsers: ['> 1%', 'last 2 versions', 'Firefox ESR'] }
}
]]
}
}
}]
}
};Configuration for the modern (ES2015+) bundle:
module.exports = {
entry: { main: './path/to/main.js' },
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'public')
},
module: {
rules: [{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: [[
'env', {
modules: false,
useBuiltIns: true,
targets: { browsers: ['Chrome >= 60', 'Safari >= 10.1', 'iOS >= 10.3', 'Firefox >= 54', 'Edge >= 15'] }
}
]]
}
}
}]
}
};After building, you will have two files:
main.js (ES2015+)
main-legacy.js (ES5)
In your HTML, load the appropriate bundle conditionally:
<!-- Browsers with ES module support load this file -->
<script type=\"module\" src=\"main.js\"></script>
<!-- Older browsers load the legacy file -->
<script nomodule src=\"main-legacy.js\"></script>Note that Safari 10 does not support the nomodule attribute; a small inline script can work around this.
Considerations
Modules are loaded with defer semantics, so code that must run earlier should be split.
Modules always execute in strict mode; non‑strict code must be loaded separately.
Global var and function declarations behave differently inside modules.
Example Project
The author provides a webpack-esnext-boilerplate that demonstrates code‑splitting, dynamic imports, and asset fingerprinting.
Performance Impact
Version
Size (minified)
Size (minified + gzipped)
ES2015+ (main.js)
80 KB
21 KB
ES5 (main-legacy.js)
175 KB
43 KB
Version
Parse/eval time (individual runs)
Parse/eval time (avg)
ES2015+ (main.js)
184 ms, 164 ms, 166 ms
172 ms
ES5 (main-legacy.js)
389 ms, 351 ms, 360 ms
367 ms
Even on a modest device the ES2015+ bundle parses and executes roughly half as fast as the ES5 fallback, and its size is less than a third after gzip.
Widespread use of polyfills inflates bundle size; data from HTTPArchive shows a rapid increase in sites that include babel-polyfill, core-js, or regenerator-runtime.
Conclusion
Using <script type=\"module\"> together with nomodule provides a practical way to ship native ES2015+ code to modern browsers while still supporting legacy ones, reducing unnecessary bytes and improving performance. Publishing ES2015+ modules to npm is now feasible and beneficial for both developers and users.
Further Reading
ES6 Modules in Chrome M61+
ECMAScript modules in browsers
ES6 Modules in Depth
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JD.com Experience Design Center
Professional, creative, passionate about design. The JD.com User Experience Design Department is committed to creating better e-commerce shopping experiences.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
