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

Support native ESM with .mjs #537

Closed
2 of 4 tasks
jaydenseric opened this issue Mar 7, 2018 · 2 comments
Closed
2 of 4 tasks

Support native ESM with .mjs #537

jaydenseric opened this issue Mar 7, 2018 · 2 comments
Labels
feature New addition or enhancement to existing solutions has-reproduction ❤ Has a reproduction in a codesandbox or single minimal repository

Comments

@jaydenseric
Copy link

jaydenseric commented Mar 7, 2018

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
@ghost ghost added feature New addition or enhancement to existing solutions has-reproduction ❤ Has a reproduction in a codesandbox or single minimal repository labels Mar 7, 2018
@jaydenseric
Copy link
Author

jaydenseric commented Mar 7, 2018

If you run into issues trying to use default imports for CJS dependencies, here is a workaround (see babel/babel#7294 for context).

Before (in test.mjs):

import { ApolloLink } from 'apollo-link' // Broken, because it's CJS
console.log(ApolloLink)

After:

import apolloLinkDefault, * as apolloLinkExports from 'apollo-link'
const apolloLink = apolloLinkDefault || apolloLinkExports
console.log(apolloLink.ApolloLink)

Note that with this fallback approach, versions of Webpack may emit harmless but annoying warnings.

Fixing the dependency to support native ESM is far preferable to these ugly workarounds, but sometimes you can't wait.

@ghost ghost added feature New addition or enhancement to existing solutions has-reproduction ❤ Has a reproduction in a codesandbox or single minimal repository labels Mar 7, 2018
@JoviDeCroock
Copy link
Contributor

At this point we are exporting an ES5 module bundle, which can easily be renamed to a .MJS bundle if it ever is supported. At this point we're getting there but not quite yet.

Closing this issue for now.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature New addition or enhancement to existing solutions has-reproduction ❤ Has a reproduction in a codesandbox or single minimal repository
Projects
None yet
Development

No branches or pull requests

2 participants