Frontend Development 13 min read

Publish, Ship, and Install Modern JavaScript for Faster Applications

The article explains why adopting modern JavaScript (ES2017) and publishing packages with the new exports field, combined with proper bundler configurations such as Webpack or Rollup, dramatically reduces bundle size and improves performance while still supporting legacy browsers when needed.

ByteDance Web Infra
ByteDance Web Infra
ByteDance Web Infra
Publish, Ship, and Install Modern JavaScript for Faster Applications

More than 90% of browsers can run modern JavaScript, yet legacy code remains a major cause of web performance problems. Modern JavaScript refers to syntax supported by all current browsers, including classes, arrow functions, generators, block scoping, destructuring, rest/spread, object shorthand, and async/await (ES2017).

Legacy JavaScript avoids these features, often requiring a compilation step that increases bundle size by about 20% and adds runtime overhead, especially when polyfills and helpers are included.

Modern JavaScript on npm

Node.js now supports an exports field in package.json to declare a module’s entry point, indicating the package requires Node 12.8+ (ES2019) and can contain modern code.

{
  "exports": "./index.js"
}

Using only the exports field signals that consumers must handle any necessary transpilation.

Publish Modern Code Only

{
  "name": "foo",
  "exports": "./modern.js"
}
Note: This approach assumes every consumer’s build system can transpile dependencies, which is not always true.

Modern Code + Legacy Compatibility

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs"
}

Modern Code + Legacy Compatibility + ESM Optimisation

{
  "name": "foo",
  "exports": "./modern.js",
  "main": "./legacy.cjs",
  "module": "./module.js"
}

Bundlers like Webpack and Rollup can use the module field for tree‑shaking while still providing a legacy bundle.

Bundler Configuration

Webpack

Webpack 5 can target modern syntax:

module.exports = {
  target: ['web', 'es2017'],
};

It can also output an ES module bundle:

module.exports = {
  target: ['web', 'es2017'],
  output: { module: true },
  experiments: { outputModule: true },
};

Optimize Plugin

Transforms a modern bundle back to legacy code without re‑processing source files, allowing a single source to serve both modern and legacy browsers.

// webpack.config.js
const OptimizePlugin = require('optimize-plugin');
module.exports = {
  plugins: [new OptimizePlugin()],
};

BabelEsmPlugin

Works with @babel/preset‑env to generate a modern bundle for browsers that support ES modules.

// webpack.config.js
const BabelEsmPlugin = require('babel-esm-plugin');
module.exports = {
  module: {
    rules: [{ test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }]
  },
  plugins: [new BabelEsmPlugin()],
};

Transpiling node_modules

When modern dependencies expose an exports field, they can be automatically transpiled:

// webpack.config.js
module.exports = {
  module: {
    rules: [
      // first‑party code
      { test: /\.js$/i, loader: 'babel-loader', exclude: /node_modules/ },
      // modern dependencies
      {
        test: /\.js$/i,
        include(file) {
          let dir = file.match(/^.*[\/\\]node_modules[\/\\](@.*?[\/\\])?.*?[\/\\]/);
          try { return dir && !!require(dir[0] + 'package.json').exports; } catch (e) {}
        },
        use: { loader: 'babel-loader', options: { babelrc: false, configFile: false, presets: ['@babel/preset-env'] } }
      }
    ]
  }
};

Minifiers must be configured for modern syntax, e.g., { ecma: 2017 } for Terser.

Rollup

Rollup can generate both modern and legacy bundles in a single build.

// rollup.config.js
import { getBabelOutputPlugin } from '@rollup/plugin-babel';
export default {
  input: 'src/index.js',
  output: [
    // modern bundle
    { format: 'es', plugins: [getBabelOutputPlugin({ presets: [['@babel/preset-env', { targets: { esmodules: true }, bugfixes: true, loose: true }]] })] },
    // legacy bundle
    { format: 'amd', entryFileNames: '[name].legacy.js', chunkFileNames: '[name]-[hash].legacy.js', plugins: [getBabelOutputPlugin({ presets: ['@babel/preset-env'] })] }
  ]
};

Other Tools

Higher‑level bundlers such as Parcel, Snowpack, Vite, and WMR assume dependencies may contain modern syntax and handle transpilation automatically. Devolution can add legacy fallbacks to any build output.

Conclusion

ES2017 is currently the most widely supported modern JavaScript version. By using tools like npm, Babel, Webpack, and Rollup with the appropriate configuration, developers can publish modern code while still providing legacy bundles for older browsers.

Frontend DevelopmentWebpackRollupES2017Modern JavaScriptnpm exports
ByteDance Web Infra
Written by

ByteDance Web Infra

ByteDance Web Infra team, focused on delivering excellent technical solutions, building an open tech ecosystem, and advancing front-end technology within the company and the industry | The best way to predict the future is to create it

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.