Skip to content
This repository was archived by the owner on Aug 4, 2021. It is now read-only.

Conversation

@Rich-Harris
Copy link
Contributor

Via rollup/rollup#866. Interop between CommonJS and ES modules continues to be a total bloody nightmare. I think our current approach is incomplete but I'm not sure what the best one is. Stream of consciousness ahead as I try to get my thoughts straight.

There are three situations this plugin needs to handle: ES imports CJS, CJS imports CJS, CJS imports ES.

Right now there's interop fudging at export time and at require time. It causes problems when CJS files require other CJS files that were generated by a transpiler, as is the case in rollup/rollup#866react-apollo/index.js does things like this:

var ApolloProvider_1 = require('./ApolloProvider');
exports.ApolloProvider = ApolloProvider_1.default;

This plugin borks things up because it massages the exports of ApolloProvider on the assumption that the importer will be an ES module, not another CJS module.

Taking each situation in turn:

ES imports CJS

import foo from './commonjs/foo.js';
import { bar } from './commonjs/bar.js';

This case is relatively straightforward – foo.js just exports the value of module.exports (or module.exports.default if it's a transpiler-generated module – tbh we should probably only do that if exports.__esModule === true, because that's how transpilers mark their territory). bar.js has a named bar export in addition (either automatically, because the plugin detects an exports.bar = whatever' assignment, or manually because we've used options.namedExports.

CJS imports CJS

const x = require( './commonjs/foo.js' );

It seems fair to say that in this situation, no export massaging should occur – x should simply be the value of module.exports in foo.js. That's not what currently happens, meaning that when a CommonJS module expects to find a .default property on a required module, things blow up.

Unfortunately, there's no (robust, reliable) way for this plugin transformer to know whether its consumer will be ES or CJS (or both!).

CJS imports ES

const y = require( './es/bar.js' );

Because of this requirement, all require statements result in namespace imports rather than default imports – since we don't know if bar.js exports default or not, we have to import a namespace and futz around with it. This is really bad news, because it results in more code and complexity and prevents tree-shaking.


The approach in 61c6e00 fixes rollup/rollup#866 but in a horrible way – by adding a .default property to the .default property, so that an importer of the transformed module will find what it expects whether it's ES or CJS. (It doesn't currently hang any named properties off the default export, so isn't a complete solution in any case.)

There has to be a better way. I'm wondering about something involving virtual 'proxy' modules that do the interop stuff – i.e. an ES module imports the transformed module directly, whereas a CJS module imports the proxy. Or vice versa! Though my thinking hasn't progressed far beyond that right now.

If anyone can see anything obvious I'm missing let me know... you might save me from being driven insane by this stuff.

@Rich-Harris
Copy link
Contributor Author

Closing in favour of #92

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

React ApolloProvider component is undefined

2 participants