Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CommonJS builds, output.exports = 'defualt', and ignoring extraneous named-exports #3345

Open
ELLIOTTCABLE opened this issue Jan 19, 2020 · 7 comments

Comments

@ELLIOTTCABLE
Copy link

Expected Behavior / Situation

I've got an ES6 module that exports both several intermediate values and constructors, and a default value that's a few publicly-documented selections thereof:

export class AutomatonElement // ...
export class AutomatonStack // ...
export class Checkpoint // ...
export class Expression // ...
export class ExpressionEval // ...
// and so on

export default {
   Checkpoint,
   Expression,
}

I expected that my Rollup ESM-build would behave the same (including both individual-exports, if the consumer wished to import them; as well as the actual "function bag" that's the public API of available classes and functions); while Rollup's CJS-build would only include the "function bag" defined as the default-value.

My configuration looks something like this:

export default {
   input: 'src/interface.js',
   output: [
      {
         file: 'dist/interface.js',
         format: 'cjs',
         exports: 'default',
      },
      {
         file: 'dist/interface.esm.js',
         format: 'esm',
      },
   ],
}

Actual Behavior / Situation

The 'esm' build behaves as expected, but trying to build the 'cjs' build as above produces either — with exports: 'default':

[!] Error: "default" was specified for "output.exports", but entry module "src/interface.ts" has the following exports: AutomatonElement, AutomatonStack, Checkpoint, Expression, ExpressionEval, LexBuffer, LexError, LocatedToken, OCamlError, ParseError, Parser, Position, Script, Token, default

… or without:

The following entry modules are using named and default exports together:
src/interface.js

Consumers of your bundle will have to use chunk['default'] to access their default export, which may not be what you want. Use `output.exports: 'named'` to disable this warning

Modification Proposal

I propose, for format: 'cjs' builds, either that output.exports: 'default' completely ignore named-exports as long as a default-export is provided; or a new value for output.exports be provided that explicitly ignores named-exports as long as a default-export is provided.

Hope this made sense! Rollup is exactly what I needed to solve a major packaging problem of mine, and I'm super-happy that it exists; keep up the good maintenance work. <3

See also, #1961 — includes more discussion on the how/why one might want to mix exports; although it does not address specifically this situation, where one explicitly wishes to provide named-exports to ESM builds, but only the default-value to CJS builds.

@TrySound
Copy link
Member

What is the point of ignoring named exports. Can you just not export those classes?

@ELLIOTTCABLE
Copy link
Author

I'm not sure what you mean — I am exporting them? Or do you mean something else by 'export?' (=

@jnordberg
Copy link

I think this proposal is a good idea, I'd go even further and say that a value for output.exports should be added that lets you assign named exports to the default, call it default-unsafe-assign or something. What the people saying "default is bad practice" in #1961 are missing is that that's the only way to achieve a consistent API for all bundle types.

Consider the following library:

export default class MainClass {
    doSomething() { ... }
}
export function utilityFunction() { ... }

Now to import this you write:

// module
import MyLib from 'my-lib'
let instance = new MyLib()
// commonjs
const MyLib = require('my-lib').default
let instance = new MyLib()
// global
let instance = new MyLib.default()

Now instead with the default assigned as the module root you could write:

// module
import MyLib from 'my-lib'
let instance = new MyLib()
// commonjs
const MyLib = require('my-lib')
let instance = new MyLib()
// global
let instance = new MyLib()

Notice how the let instance = new MyLib() is same in every example.

There is ways to work around this but none that isn't "worse practice" than using default exports. I could assign my utility function to MainClass and have a single default export or use solely named exports (which in many cases is unnecessarily verbose) and use import * as MyLib both of which defeats the purpose of having modules.

@TrySound
Copy link
Member

TrySound commented Apr 6, 2020

This is default rollup output when you do not have named exports. With named exports it's not clear how interop should be handled. And look with named only exports you have consistent imports

// module
import { MyLib } from 'my-lib';
let instance = new MyLib()
// commonjs
const { MyLib } = require('my-lib');
let instance = new MyLib()
// global
const { MyLib } = myLib;
let instance = new MyLib()

@jnordberg
Copy link

That's probably a better workaround with the current bundler 👍

With named exports it's not clear how interop should be handled.

It's clear, just not safe if you're for some reason exporting numbers as your default :) That's why I suggested to call the option default-unsafe-assign

@Twipped
Copy link

Twipped commented May 12, 2020

Could it also be another output toggle, like output.ignoreNamed or something? I found this issue because my library exports a function as a default, and I have the named exports also attached to that function, so for my cjs build I'm fine just ignoring the rest.

As it sits currently I'll probably have to make a separate entry point with a single export, just to get around this.

@mnrendra
Copy link

mnrendra commented Mar 6, 2024

Hi, all, you can solve this issue by using:
@mnrendra/rollup-plugin-mixexport

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants