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

Cure ES Module Madness #203

Merged
merged 5 commits into from
Dec 4, 2020

Conversation

kemitchell
Copy link
Contributor

@kemitchell kemitchell commented Dec 3, 2020

This PR attempts to resolve various issues and errors affecting bin script execution and loading via import in Node.js. It doesn't touch the existing conditional-import or module bunlder entry point configurations in package.json at root.

Overall, the blunt instrument at work here is package.json files with "type":"module" set. In order to make clear to Node.js what is an old CommonJS module and what is a new ES Module, we have just a few choices:

  • Use .cjs for CommonJS and .mjs for ES Modules. But old versions of Node don't know about this convention.
  • Pass flags to Node. But again, only recent versions of Node know the latest flags. And we generally don't control how Node.js is invoked, outside our own tests and other development scripts.
  • Set type to "commonjs" or "module" in package.json, at package root, in subdirectories, or both. Newer versions of Node will look at the nearest package.json and load .js files according to the type specifier. Cluttering, but effective.

For maximum compatibility and minimum renaming things, I've opted to add package.json files in several sudirectories. The package.json at root continues to have "type":"commonjs". But that is overridden for lib, test, and bench, which have been rewritten as ES Modules. I did not add such package.json files to dist or dingus. dist/commonmark.js is the entry point when folks use require to load this package, so it is and should be treated as CommonJS.

As for the bin script, which will always be run by Node.js, and not a browser, I don't see any practical advantages to ES Modules right now. If and when this package drops support for Node.js <= 13, the bin script can be safely rewritten with import. But for now, require works fine and doesn't cause compat problems.

Preloading the esm package via a Node.js flag might work in many cases, but it's not necessary. Downloading and loading esm is a kludge for those on old versions of Node.js, and wholly unnecessary for those on 14, 15, or later. As a general matter, I like to keep packages out of the decision making process about whether and how to configure module loading for any module but the one the package offers. That's something that users of the package can decide on and configure for themselves and all their dependencies.

I've included more detailed discussions in commit messages.

A number of JavaScript files were rewritten as ES Modules, but their
extensions remained `.js`. That extension is ambiguous to newer
version of the Node.js runtime, which can load both CommonJS modules
and ES Modules.

We could disambiguate by renaming CommonJS modules to `.cjs` files and
ES Modules to `.mjs` files, per Node.js convention. Alternatively,
we can add `package.json` files with `type` properties to the
various subdirectories. Setting `type` to `"module"` tells Node.js
to interpret `.js` files in that directory and below as ES Modules.
Otherwise, Node.js falls back on the `package.json` at root, which
currently sets `type` to `"commonjs"`. I've done the latter here, to
avoid renames in Git history.
This reverts commit 9bcf45b.

Node.js version 14, which supports ES Modules without any flag or the
`esm` package, is currently in long-term support. But a great many
folks still run older version of Node.js that either don't support ES
Modules at all or hide that support behind a feature flag.

We could instruct Node.js to load the bin script as an ES Module via
a flag in shebang, but that flag won't be available on older versions
of Node.js. Meanwhile, this bin script doesn't stand to gain anything
from moving to ES Modules. When something runs the bin script, it's
going to be Node.js, not a browser. So we might as well use `require`
and stop worrying about ES Modules.
`lib/index.js` is now specifically the loading entry point for
loading as an ES Module via `import`. Using `require('../')` instead
will ensure that Node.js follows the loading configuration in
`package.json`.
This might help bundlers reduce bundle size by specifying specific
imports. I haven't tested it empirically, but I understand this is
best practice for loading with `import` anyway.
@kemitchell
Copy link
Contributor Author

Ref: #201

@kemitchell
Copy link
Contributor Author

@jgm, by the by: Greetings from North Oakland.

Hope this module madness hasn't gotten to you too bad. It really is a mess, and none of it's the fault of this package.

@kemitchell
Copy link
Contributor Author

kemitchell commented Dec 3, 2020

Ref: #199, #195

Comment on lines -80 to +83
if (os.platform() === "win32") {
inps.push(fs.readFileSync(0, 'utf-8'));
} else {
inps.push(fs.readFileSync('/dev/tty', 'utf-8'));
}
} else {
for (i = 0; i < files.length; i++) {
var file = files[i];
inps.push(fs.readFileSync(file, 'utf8'));
}
files = ['/dev/stdin'];
}

for (i = 0; i < files.length; i++) {
var file = files[i];
inps.push(fs.readFileSync(file, 'utf8'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a reason for removing the platform-specific handling here?
cf. discussion in #198.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason for removing the platform stuff in the bin script. Looks like it was done in the same commit as ESM-ification: 9bcf45b. So git revert rolled those changes back, too.

I can replace them, or you can fee free to. Whatever's easiest for you.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure why this got included with the other changes.
Well, I guess I can add them back after mrging.

@jgm
Copy link
Member

jgm commented Dec 4, 2020

Thanks a lot for this!

@jgm
Copy link
Member

jgm commented Dec 4, 2020

@kemitchell do you see what is causing the CI failures here?
https://github.com/commonmark/commonmark.js/actions/runs/401134231

@kemitchell
Copy link
Contributor Author

@jgm, CI on Node 10 and 11 is failing because the tests were ported to ES Modules, and those versions of Node can't parse them. Something else is going on on Node 13.

@kemitchell
Copy link
Contributor Author

PR coming.

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

Successfully merging this pull request may close these issues.

2 participants