Skip to content
This repository was archived by the owner on Apr 14, 2023. It is now read-only.
This repository was archived by the owner on Apr 14, 2023. It is now read-only.

Support native ESM with .mjs #537

@jaydenseric

Description

@jaydenseric

This affects every Apollo package, not just Apollo links.

Node.js with --experimental-modules can run ESM in .mjs files. Soon ESM will work without a flag.

At the moment, all Apollo packages can not be imported in a native ESM environment using named imports (e.g. import { ApolloLink } from 'apollo-link'). You have to use default imports, which is unintuitive and defeats tree shaking compilers.

So that both CJS and ESM environments can consume a package properly, sibling .js (CJS) and .mjs (ESM) files with the same names need to be published. Node.js will load the relevant file, looking up .mjs before .js for main if --experimental-modules is enabled. This is also how resolution works in Webpack v4 and other tools.

package.json entries need to look like this:

{
  "main": "index",
  "module": "index.mjs"
}

Note that all imports within the .mjs files must be valid, i.e. there can be no named imports from CJS dependencies. A solution is to convert violating imports to use default instead. As this does not tree shake and degrades bundle size, the ultimate solution is to add propper native ESM support upstream.

There are other benefits being realised over time to using the .mjs extension for ESM, even for source files. For example, Babel can now can set the mode automatically.

My advice is to drop the TypeScript compiler, and use Babel v7 for everything as it now supports TS. It works better with .mjs, and will result in faster builds as you don't have to run TS and then Babel. This is an opportunity to ensure every package is setup with @babel/preset-env to ensure only the required transpilation happens for a certain level of environment support. It should be configured to only include helpers and polyfills accordingly, and as imported dependencies so they are not duplicated in each package in a consumer's bundle. Some packages are using Rollup; this can be removed. For best results bundling and minification should happen exclusively in a consumer's project.

A few related packages that provide .mjs for inspiration:

Related Slack discussion.

Expected Behavior

In test.mjs:

import { ApolloLink } from 'apollo-link'
console.log(ApolloLink)

Running node --experimental-modules test.mjs should log:

{ [Function: ApolloLink]
  empty: [Function: empty],
  from: [Function: from],
  split: [Function: split],
  execute: [Function: execute] }

Actual Behavior

test.mjs:1
import { ApolloLink } from 'apollo-link'
         ^^^^^^^^^^
SyntaxError: The requested module does not provide an export named 'ApolloLink'
    at ModuleJob._instantiate (internal/loader/ModuleJob.js:86:19)
    at <anonymous>

A simple reproduction

See above.

Issue Labels

  • has-reproduction
  • feature
  • blocking
  • good first issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew addition or enhancement to existing solutionshas-reproduction❤ Has a reproduction in a codesandbox or single minimal repository

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions