rfcs
rfcs copied to clipboard
Support named exports in Node.js native ES2015 modules implementation
I would appreciate it if we kept the merits of .mjs and how Node.js chose to implement ES2015 modules out of the discussion here. We should just accept the fact that ES2015 modules are getting a native implementation in Node.js, this is what we have to work with, and focus on fixing React so that it works with the implementation we are getting.
It is unknown when a node will support ESM and what will be the form. https://github.com/nodejs/modules Modules team in node did not publish timeline and previous PRs in the node were postponed. This is already the third attempt of ESM in the node. IMO focus should be on browser ESM implementation that works now.
We also need to consider what to do with react-test-renderer/shallow entry point which currently exports a function in CommonJS.
We can do the same or just change exports to
import { TestRenderer, Shallow } from 'react-test-renderer';
I think the general thinking here is that we want to do something like this but it'd be a breaking change and only worth doing along with other breaking changes...?
I think the general thinking here is that we want to do something like this but it'd be a breaking change and only worth doing along with other breaking changes...?
The Node.js ESM implementation has been pushed back to October and has changed a bit in implementation. So the RFC is kind of on hold.
Theoretically this change should not be breaking since it only affects people writing .mjs files. Additionally Node has even changed the behaviour in a way that adding an index.mjs won't break current users using .mjs. That said there's a chance WebPack could completely screw up that plan. So perhaps it should be bundled with breaking changes anyways.
It's tangentially related. But I wonder if we should also revisit TypeScript. It's really annoying having to use import * as React from 'react'; instead of import React, {useState} from 'react';. Which could easily be solved with essentially a const React = {...}; modules.export = {...React, default: React};.
@dantman Default export is not the right way to import from react. React is a namespace. Logically it should not be default export. Default export also requires hacks like babel plugin to transform
React.createElement()
// to
const { createElement } = React
createElement()
When bundler may do this for free and even better will not generate const { createElement } = React in every file.
Treeshaking will not be significant with react but it still can be considered as a feature.
React should be a namespace. It currently is not.
Default export is not the right way to import from react.
Reactis a namespace.
You're going to have to rally for JSX to be changed then. Because the default is React.createElement and React must be defined in context.
Treeshaking will not be significant with react but it still can be considered as a feature.
Treeshaking will be completely nonexistent. React is bundled into a single file and uses an object export.
You're going to have to rally for JSX to be changed then. Because the default is React.createElement and React must be defined in context.
Not at all. import * as React from 'react'; is universal solution. Default export can be provided as a temporary fix.
Treeshaking will be completely nonexistent. React is bundled into a single file and uses an object export.
I'm talking about the time when react will have esm and named exports.
I'm talking about the time when react will have esm and named exports.
React's single file bundles were an intentional choice, even if React ever gets ESM it will likely still use single file bundles.
@dantman Wait, I didn't say anything about react bundles. I like them.
My point was that react should not have default export because it increases user bundle a lot or requires babel plugins to solve the problem and still with trade offs.
How does this proposal hold up against the latest shipped Node implementation?
How does this proposal hold up against the latest shipped Node implementation?
There seem to be some changes to the ESM handling. The general idea of the RFC seems to still be valid, but about 10% needs a few tweaks for the ESM changes.
Scanning the new https://nodejs.org/api/esm.html docs here are the things that stand out:
- File extensions are now mandatory, so the wrapper would need to explicitly use
.mjsand the suggestion of./indexfor the main field would be removed. .jsfiles in a package can also be ESM if you include"type": "module"in the package.json but we're not changing the default (unless you want to switch to ESM code for React 17 with optionally a CommonJS .cjs fallback for compatibility with older node) so we'd do the opposite and explicitly declare"type": "commonjs".- Instead of placing an
index.jsandindex.mjsnext to each other, package.json has a newexportsfield. It supports conditional exports to directrequireandimportto different files, so this is how you'd setup that instead of an extension-less main.- It's not relevant here, but if
exportsgains wider adoption it could mean the disappearance of needing to includelib/dist/es/modulefolders in imports.
- It's not relevant here, but if
- The proposal I made of using an .mjs wrapper file to export named identifiers appears to be the recommended approach for packages vulnerable to the dual package hazard (where one dep doing
importand another doingrequirewould result in two instances of React being imported). So the general idea of the RFC seems to be validated by this. - The ESM docs propose a
browserconditional export variant (like the variants for import/require/node). If tooling adopts this (whatever tooling people would be using to generate browser native type=module packages) then this could possibly be a solution in the future for supporting browser native module packages without dropping the compatibility with CommonJS-only versions of node. (Basically export a full module version of React and only target it to browsers. This avoids the dual package hazard because Node still uses the wrapper not vulnerable and a browser that only supports ESM won't import the CJS version).
@gaearon Is there any desire to implement this anytime soon? If so I can revisit this now and update the RFC. Otherwise I can revisit it the next time there is new news on Node's ESM implementation (changes from experimental to stable, finishes the loader API, other toolkits adopt the ESM behaviours, or a number of React users indicate they wish to use ESM).
How does this proposal hold up against the latest shipped Node implementation?
The import specifiers in ESM .mjs files must contain the full filenames, including extensions (except when using a bare specifier to import from main package exports).
E.g. when importing ESM into ESM:
- import { Foo } from './Foo'
+ import { Foo } from './Foo.mjs'
E.g. when importing CJS into ESM:
- import Foo from './Foo'
+ import Foo from './Foo.js'
@gaearon Is there any desire to implement this anytime soon?
"Soon" is relative but it's always helpful to have an up-to-date plan in case it seems like the right moment to start the work. In particular, I'm worry about how to be compatible with Node, bundlers, Flow/TS, and everything else.
This RFC seems to be getting some attention at the moment. I’d like to direct any folks who are interested in it to the Node.js documentation, and in particular the “Dual CommonJS/ES module packages” section which provides a more complete and up-to-date picture: https://nodejs.org/dist/latest-v14.x/docs/api/packages.html#packages_dual_commonjs_es_module_packages
There is also an ongoing discussion in https://github.com/facebook/react/issues/11503.
Our latest thinking was that we'd drop default exports altogether. Named only.
Our latest thinking was that we'd drop default exports altogether. Named only.
Perhaps this should be deprecated starting React 19?