Frontend Development 15 min read

Understanding the ESModule Specification: Server and Browser Implementations, Third‑Party Dependency Challenges, and Next‑Generation Web App Development

This article explains the ESModule standard, compares its server‑side (Node.js) and browser implementations, discusses compatibility issues with CommonJS, examines the difficulties of using third‑party dependencies in browsers, and outlines emerging tools such as Snowpack and Vite that leverage native ESModules.

ByteDance Web Infra
ByteDance Web Infra
ByteDance Web Infra
Understanding the ESModule Specification: Server and Browser Implementations, Third‑Party Dependency Challenges, and Next‑Generation Web App Development

1 ESModule Specification

Overview

Before ES6, the community created several module loading solutions, the most popular being CommonJS for servers and AMD for browsers (via third‑party libraries). ES6 introduced a native module system that satisfies the majority of CommonJS and AMD needs, becoming a universal solution for both browsers and servers.

After ES6 defined the ESModule syntax, browsers began to support it natively, and Node.js also added support.

// commonjs
const axios = require('axios')

// amd
require(['axios'], function (axios) {})

// esmodule
import axios from './node_modules/axios/axios.js'
Note: The latest ECMAScript proposal has been accepted; import maps can now manage module import paths.

Server‑Side Implementation

CommonJS

Before ESModule, Node.js used its built‑in Module to implement the CommonJS system, which is not an official JavaScript standard and is unsupported by browsers.

ESModule

Node.js needed to adopt the ESModule standard while preserving compatibility with the existing CommonJS ecosystem, because most third‑party packages are still written in CommonJS.

Compatibility Issues

Because CommonJS and ESModule have fundamental design conflicts, many compatibility problems remain; readers are encouraged to search for detailed discussions.

Node.js >= v13 resolves the conflict by using the .mjs extension for ESModules and the .js extension for CommonJS, while also allowing "type": "module" in package.json to treat .js files as ESModules.

// esmA/index.mjs
export default null

// esmB/index.js
export default null

// esmB/package.json
{
    "type": "module"
}

Browser Implementation

Modern Browser Support

All major browsers (except IE) have implemented native ESModule loading for the past two‑three years.

Example repository: https://github.com/mdn/js-examples/tree/master/modules/basic-modules

2 Third‑Party Dependency Dilemma

Node.js Module Resolution

Node.js allows importing third‑party packages by name without a relative path, automatically resolving the node_modules directory and the entry point defined in package.json .

const axios = require('axios') // => automatically resolves node_modules path
const axios = require('/User/xxxx/workspace/node_modules/axios') // => resolves package.json entry
const axios = require('/User/xxxx/workspace/node_modules/axios/axios.js')

The core resolver performs automatic path concatenation, entry field lookup, and extension/index completion.

Webpack Module Resolution

Webpack bundles all third‑party dependencies into a single bundle.js . Historically it injected Node.js‑style CommonJS handling so that developers could use the same syntax in both Node and browser builds.

However, Webpack’s CommonJS emulation is a separate implementation that mimics Node’s behavior for compatibility.

Browser Module Resolution

Browsers do not support native CommonJS loading; even with ESModule support, they cannot directly consume CommonJS packages, prompting the need for new tooling.

3 Next‑Generation Web App Development Model

To avoid the incompatibility of CommonJS packages with native ESModules, tools convert CommonJS modules to ESModules.

CommonJS to ESModule Converters

JSPM (https://jspm.dev/@babel/core)

Skypack (formerly Pika) (http://cdn.skypack.dev/@babel/core)

ESM.sh (http://esm.sh/@babel/core)

These tools use bundlers such as Rollup or esbuild to rewrite module syntax and rewrite import paths.

const axios = reuqire('axios')
module.exports = axios
// =>
import axios from '/esm/axios.js'
export default axios

In an HTML file you can then load the transformed module directly:

However, using many third‑party packages without bundling leads to a “request explosion” – e.g., loading a UI library like Ant Design may trigger thousands of network requests.

Snowpack & Vite

These tools split third‑party dependencies from source code, bundling dependencies only once and letting the browser handle them via native ESModule loading, dramatically improving dev‑server start‑up time and hot‑module replacement performance.

4 Adoption Challenges

Despite the benefits, ecosystem maturity, incomplete CommonJS‑to‑ESModule conversion, and dynamic import semantics hinder widespread adoption.

Ecosystem Issues

Many features available in Webpack are not yet supported by newer tools.

Conversion Problems

Most npm packages still ship only CommonJS versions; converting them to ESModules introduces complex edge cases, such as handling dynamic exports, named vs default exports, and legacy compatibility code (e.g., exports.__esModule ).

// moduleA.js (CommonJS dynamic export)
init()
function init() {
    exports.a = 1
}
// index.js
require('./moduleA') // => { a: 1 }

// ESModule static export (valid)
export const a = 1
// ESModule static export (invalid – dynamic)
init()
function init() {
    export const a = 1
}

In CommonJS, exports === module.exports blurs the distinction between named and default exports, whereas ESModules enforce explicit named and default export semantics.

// module.js (ESModule)
export const a = 1
export default { a: 2 }
// index.js
import { a } from './module.js' // => 1
import a from './module.js' // => { a: 2 }
import * as a from './module.js' // => { a: 1, default: { a: 2 } }

Proposed Solutions

The ByteDance Web Infra team is actively working on advancing ESModule adoption and will share further solutions in upcoming posts.

Follow the ByteDance Web Infra public account for updates.

frontendJavaScriptNode.jsWebpackbrowserViteESModule
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.