microbundle icon indicating copy to clipboard operation
microbundle copied to clipboard

Mixed default and named exports changes default export

Open itsmichaeldiego opened this issue 5 years ago • 16 comments

Hey everyone! I am having the following issue, if I export like this:

import GoogleMap from './google_map';

export default GoogleMap;

It will build:

module.exports = GoogleMap;

However, when including named exports:

import GoogleMap from './google_map';

export { namedExport1, namedExport2 } from './named_exports;

export default GoogleMap;

it changes to:

exports.namedExport1 = namedExport1;
exports.namedExport2 = namedExport2;
...
exports.default = GoogleMap;

Which results in the following error:

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.

I am pretty sure you will ask why we do need default exports, in my case I've a library that is used by a lot of people, I switched to microbundle a bit ago and realized later on this was buggy and it won't work with SSR unless we do module.exports, thing is even if I want to change the way we import / export to work with microbundle, I need to provide backwards compatibility or something...

I think this might be related to #264 and #306 but I am not completely sure if that's the case.

itsmichaeldiego avatar Aug 31 '20 03:08 itsmichaeldiego

Hiya. This is done intentionally. Here's what you can do to produce CommonJS and UMD bundles that don't have the issue:

{
  "scripts": {
    "build": "microbundle -f cjs,umd src/index.cjs.js && microbundle -f es,modern src/index.js"
  }
}
// index.js
import GoogleMap from './google_map';
export { namedExport1, namedExport2 } from './named_exports;
export default GoogleMap;
// index.cjs.js
import GoogleMap from './google_map';
import { namedExport1, namedExport2 } from './named_exports;
Object.assign(GoogleMap, { namedExport1, namedExport2 });
export default GoogleMap;

Now require('your-lib') === GoogleMap, but require('your-lib').namedExport1 === namedExport1.

I might add an option to do this automatically.

developit avatar Aug 31 '20 13:08 developit

Works like a charm, thanks for the quick response!

Would be awesome if you add an option to do this automatically! 🎉

Feel free to close the issue, unless you want to keep it for that option you want to add.

itsmichaeldiego avatar Sep 01 '20 03:09 itsmichaeldiego

@developit any implications why we don't do this behaviour by default if it's umd/cjs? Any reasons that make this a big nono?

wardpeet avatar Sep 02 '20 17:09 wardpeet

TBH I don't think there would be any negative effects, it just hasn't been implemented.

developit avatar Sep 02 '20 23:09 developit

I might add an option to do this automatically.

@developit would love this

quantizor avatar Sep 06 '20 04:09 quantizor

same lol

developit avatar Nov 05 '20 19:11 developit

I've been getting a lot of bug reports on how this works in markdown-to-jsx: https://github.com/probablyup/markdown-to-jsx/issues/333

https://github.com/probablyup/markdown-to-jsx/blob/676e53ff33036fd0527a9ea79cd478a74ea20513/index.cjs.tsx#L1-L3

Webpack doesn't seem to be able to resolve this

quantizor avatar Nov 05 '20 21:11 quantizor

Couldn't you do this for both? Rather than having two different files?

import GoogleMap from './google_map';
import { namedExport1, namedExport2 } from './named_exports;
Object.assign(GoogleMap, { namedExport1, namedExport2 });
export default GoogleMap;

paulsmithkc avatar Dec 15 '20 17:12 paulsmithkc

Unfortunately this work around breaks typescript's understanding of the exports

evanpurkhiser avatar Jan 06 '21 00:01 evanpurkhiser

Unfortunately this work around breaks typescript's understanding of the exports

Can use JSDoc for it.

/*
* @type {SomeClass}
*/
const exported = OtherClass
export default exported

asmyshlyaev177 avatar Jan 06 '21 01:01 asmyshlyaev177

// index.cjs.js
import GoogleMap from './google_map';
import { namedExport1, namedExport2 } from './named_exports;
Object.assign(GoogleMap, { namedExport1, namedExport2 });
export default GoogleMap;

Can't this shadow/replace namedExportX properties of GoogleMap if they exist?

Would the following work?

import GoogleMap from './google_map';
import { namedExport1, namedExport2 } from './named_exports;
export default GoogleMap;
const default = GoogleMap;
export { default, namedExport1, namedExport2 };

or

import GoogleMap from './google_map';
import { namedExport1, namedExport2 } from './named_exports;
export default GoogleMap;
export { GoogleMap as default, namedExport1, namedExport2 };

or

import GoogleMap from './google_map';
export { namedExport1, namedExport2 } from './named_exports;
export default GoogleMap;
export const default = GoogleMap;

Should work as long as default is not a name you care about?!

EDIT: OK. This does not work since default is not a valid identifier.

export default GoogleMap;
export const default = GoogleMap;

is not a allowed as default is a reserved keyword https://mathiasbynens.be/notes/reserved-keywords

wardpeet avatar Mar 26 '21 07:03 wardpeet

It breaks type acquisition in our case :( https://github.com/olesku/eventhub-jsclient/issues/9

qwelias avatar May 19 '21 11:05 qwelias

@qwelias you should be able to use an intermediary type definition to fix this:

// types.d.ts

import EventHub from './eventhub.ts';

export default EventHub;
export = EventHub;

FWIW TypeScript has a setting that fixes this ("commonjsInterop" or something to that effect), however that's the crux of the issue: TypeScript doesn't have a logical behaviour for modules that are situationally CommonJS or ESM. The only generalized solution is to explicitly define the root export.

developit avatar Jun 03 '21 00:06 developit

Glad I found this! https://github.com/developit/microbundle/issues/712#issuecomment-683794530

I set up a demo repo if anyone is curious to see this in action https://github.com/DavidWells/microbundle-cjs-default-export-fix/

DavidWells avatar Aug 05 '21 20:08 DavidWells