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

Import named vs default from CommonJS packages #260

Closed
GeoffreyBooth opened this issue Feb 2, 2019 · 7 comments
Closed

Import named vs default from CommonJS packages #260

GeoffreyBooth opened this issue Feb 2, 2019 · 7 comments

Comments

@GeoffreyBooth
Copy link
Member

In the discussion regarding dynamic modules and CommonJS named exports, and whether or not it’s worth shipping support for import statements of CommonJS packages’ default exports if we can’t also support import of named exports, I thought it might be useful to research just how common import statements of named exports versus default exports are. I took my research repo consisting of the 941 packages on the public NPM registry that contain a "module" field and I analyzed those packages’ import statements.

Terminology

Import specifiers come in three types, per Babel’s AST:

  • An ImportSpecifier is the shuffle in import { shuffle } from 'lodash'.
  • An ImportDefaultSpecifier is the _ in import _ from 'lodash'.
  • An ImportNamespaceSpecifier is the * as _ in import * as _ from 'lodash'.

The import source is the 'lodash' in the above examples. For the sake of simplicity, I’m only analyzing imports of package entry points (so not 'lodash/shuffle.js' or './shuffle.js'). The import source is considered ESM if its package.json has a "module" field, or CommonJS otherwise.

Results

Where the source (e.g. 'lodash') is an ESM package:

Specifier Type Count %
ImportSpecifier 4,234 54%
ImportDefaultSpecifier 3,483 44%
ImportNamespaceSpecifier 171 2%

Where the source (e.g. 'lodash') is a CommonJS package:

Specifier Type Count %
ImportSpecifier 14,816 44%
ImportDefaultSpecifier 17,751 52%
ImportNamespaceSpecifier 1,480 4%

With regards to the CommonJS named vs default export debate:

  • 52% of all import specifiers (specifiers, not entire import statements) are importing a CommonJS package’s default export: import _ from 'lodash'.
  • 44% are importing named exports from a CommonJS package: import { shuffle } from 'lodash'.

So if we were to allow ImportDefaultSpecifiers and ImportNamespaceSpecifiers but not ImportSpecifiers, which is the option currently being discussed, in this corpus at least we would cause 44% of all ImportSpecifiers to throw errors. An import statement importing named exports often imports several at a time (e.g. import { shuffle, throttle } from 'lodash') so the percentage of import statements using ImportSpecifiers is lower than 44% of all specifiers being ImportSpecifiers, but the percentage of such import statements is surely still significant. A very large percentage of users will likely expect import statements like import { shuffle } from 'lodash' to work, as such statements are commonplace in user ESM code.

@devsnek
Copy link
Member

devsnek commented Feb 2, 2019

@GeoffreyBooth

I can't quite tell, so just to be 100% sure:

import { A, B } from 'C'

Do you count this as one instance of using an import specifier or two instances of using an import specifier? If it is the latter, I'd like to request a recount.

@GeoffreyBooth
Copy link
Member Author

Do you count this as one instance of using an import specifier or two instances of using an import specifier?

Two counts. I was counting specifiers, not statements.

I made a separate count of import statements:

  • 5,882 import statements of ESM package bare specifiers ('lodash')
  • 25,854 import statements of CommonJS package bare specifiers ('underscore')

So 17,751 ImportDefaultSpecifiers across 25,854 import statements of CommonJS packages means 69% of import statements used an ImportDefaultSpecifier.

@ljharb
Copy link
Member

ljharb commented Feb 2, 2019

What about jsnext:main, or packages that didn’t choose to provide a nonstandard package.json field but still used babel? I’d bet if you looked st the github repos for packages that had babel in dev deps youd get a much larger sample size.

@GeoffreyBooth
Copy link
Member Author

This was a quick-and-dirty analysis. Obviously if I wanted to be more thorough I could have been. You’re welcome to fork the repo and improve it if you’d like. This sample size of 941 packages included 96,923 JavaScript (.js or .mjs) files.

I was only curious to see if the usage of named exports of CommonJS packages was significant. My hunch was that it would be infrequent, as many CommonJS packages expect to only be imported as the main, like import request from 'request'. From this quick scan, however, my hunch seems pretty clearly to have been wrong. Depending on whether you count specifiers or statements, something like 20% to 40% of the time named exports are used. Whether that’s a low enough percentage for the group to write off that use case is up to the group, but it’s clearly a high enough number that a lot of users will notice that code like import { shuffle } from 'underscore' doesn’t work.

@jdalton
Copy link
Member

jdalton commented Feb 2, 2019

@GeoffreyBooth Thank you for doing all of this data work! So nice! 🤝

@ljharb
Copy link
Member

ljharb commented Feb 2, 2019

@GeoffreyBooth makes sense, thanks. I thoroughly expected that; any form of interop we fail to provide is going to cause massive pain for users.

It would also be interesting to rerun your data to see if anyone is using export * from where there's a circular dependency.

@MylesBorins
Copy link
Contributor

Based on what we've upstreamed I think we can close this discussion for now. Please reopen if I am mistaken

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

No branches or pull requests

5 participants