Skip to content

Latest commit

 

History

History
198 lines (136 loc) · 3.04 KB

readme.md

File metadata and controls

198 lines (136 loc) · 3.04 KB

Goal

Goal is to collect the list of ESM-CJS interoperability issues known to Vite's team.

In order to make Vite's native SSR API rock solid.

Observation 1

TypeScript transpiles ESM to CJS like this:

// ESM

export default 'hi'
export const msg = 'hello'
// CJS

"use strict";
exports.__esModule = true;
exports.msg = void 0;
exports["default"] = 'hi';
exports.msg = 'hello';

TODO: I don't know the purpose of the line exports.msg = void 0;; seems superfluous?

Observation 2

In CJS, exports always denotes the default export.

// hi.js (CJS)

exports["default"] = 'hi';
exports.msg = 'hello';
// CJS

const moduleDefaut = require('./hi.js')
const { msg } = require('./hi.js')

console.log(moduleDefaut)
console.log(msg)

Prints:

{ default: 'hi', msg: 'hello' }
hello

This means that the ESM default lives at moduleDefaut.default. (See previous section about how TypeScript transpiles ESM to CJS.)

Observation 3

Node.js doesn't support the __esModule compatibility layer.

// hi.mjs (ESM)

export default 'hi'
export const msg = 'hello'
const imported = await import('./hi.mjs')
console.log(imported)

prints:

{ default: 'hi', msg: 'hello' }

Whereas

// hi.ts (ESM, TypeScript)

export default 'hi'
export const msg = 'hello'

which TypeScript transpiles to

// hi.js (CJS)

"use strict";
exports.__esModule = true;
exports.msg = void 0;
exports["default"] = 'hi';
exports.msg = 'hello';
const imported = await import('./hi.js')
console.log(imported)

Prints:

{
  __esModule: true,
  default: { __esModule: true, msg: 'hello', default: 'hi' },
  msg: 'hello'
}

Node.js is working on supporting __esModule, see nodejs/node#40891 and nodejs/node#40902. But this will never be Node.js's default behavior as it would break existings app.

Also note how exports["default"] is overwritten with exports, leading to our next observation.

Observation 4

When loading CJS from ESM, the default export is overwritten.

exports["default"] = 'hi';
exports.msg = 'hello';
// CJS
const moduleExports = require('./hi.js')
console.log(moduleExports)

Prints:

{ default: 'hi', msg: 'hello' }

While

// ESM
const moduleExports = await import('./hi.js')
console.log(moduleExports)

prints:

{
  default: { default: 'hi', msg: 'hello' },
  msg: 'hello'
}

Which makes sense considering our previous observations.

Observation 5

When loading CJS from ESM, non-statically-analysable exports are missing.

// CJS

exports.msg = 'hello';
const key = 'msg2';
exports[key] = 'bonjour';
// ESM

const moduleExports = await import('./hi.js')
console.log(moduleExports)

Prints:

{
  default: { msg: 'hello', msg2: 'bonjour' },
  msg: 'hello'
}

Note how msg is present at the root while msg2 is missing. The only way to access msg2 is over default.msg2, whereas msg can be accessed directly.