-
Notifications
You must be signed in to change notification settings - Fork 75
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
Export mjs files so Node can natively import them. #88
Conversation
Thank you! The node people are really forcing .mjs on us huh. The one thing I’m worried about now: Will the move from .js -> .mjs mean that other tools like webpack and parcel which are extension aware break? I currently have no way to reason if this is a breaking change. I am very confused by all of it and will look for a similar package to determine what they do, because it’s very upsetting that different permutations of package.json properties and file extensions seem to produce different behaviors. |
Good thinking on parcel. I'd totally broken the parcel build when I tried this test #!/bin/bash
rm -rf testparcel
mkdir testparcel
cd testparcel
npm init -y
npm i parcel-bundler ../crank
cat <<EOF > index.html
<html>
<body>
<script src="index.js"></script>
</body>
</html>
EOF
cat <<EOF > index.js
/** @jsx createElement */
import {createElement} from "@bikeshaving/crank";
import {renderer} from "@bikeshaving/crank/dom";
renderer.render(<div>Hello world</div>, document.body);
EOF
cat <<EOF > .babelrc
{"presets":["@babel/preset-react"]}
EOF
npx parcel index.html --open I'd forgotten to update |
Per the |
… and that's somewhat regrettable because this PR will break the workaround for issue #89. On the bright side, it also fixes issue #89…but only on node 13.2 and up. (Node 13.1 and lower don't honor rm -rf wtfcjs
mkdir wtfcjs
cd wtfcjs
npm init -y
npm i ../crank
cat <<EOF > index.js
const {createElement} = require("@bikeshaving/crank");
const {renderer} = require("@bikeshaving/crank/html");
console.log(renderer.render(createElement("div", null, "Hello world")));
EOF
node index.js |
I believe I fixed breaking the workaround for #89 in the most recent push. |
We should be able to test that this works correctly in our CI tests at least. I’ll provide some more thoughts on this later today |
😱😱😱😱😱😱 |
Out of curiosity, node 13 is an intentionally unstable major and will be officially dead in a month, why put effort into targeting its already unsupported older .1 minor? As much as I liked commonjs, it didn't make into the standard and won't ever become one now, there will be fewer and fewer of such npm modules from now on. For maintenance conceptual simplicity, I'd keep only the standard esm code in the main crank package, with extension .js and type:module in package.json, like in one of the options the mentioned node doc suggests. And post a separate es5 crankcjs package for everyone irreparably stuck with a legacy runtime, right away with a deprecation warning. |
I’m not going to any special effort. I just noticed that Node 12 users will continue to suffer from #89 after this PR merges, but Node 14 users won’t. #89 has a workaround, which this PR temporarily broke, but I fixed that in my PR. If you’re working around #89 in Node 12 or 14, it will continue to work after this PR. |
Not that it needs to be done as part of this PR but I think there are two ways we could test this:
We could have scripts that runs in 12, 13, and 14 ensuring Crank loads correctly in each version. This all might be more work than it's worth though |
The tests I would run are:
|
So just to be clear there is no way for us to not use .mjs files? "type": "module" doesn’t work for arcane reasons? I’m trying to investigate this and I thought forcing cjs users to use the cjs directory would have been a reasonable compromise. I don’t think anyone seems to know what is going on. |
It's a little bold for me to claim that there's "no way" to do something, but I just pushed this branch https://github.com/dfabulich/crank/tree/buggy with this commit dfabulich@cbe135d that switches back from
|
I did some research on why my https://github.com/dfabulich/crank/tree/buggy branch is buggy, and I turned up a bug in Node! The core idea of this PR is to use the new-ish Node conditional exports system, associating paths with files depending on how they're requested ( "exports": {
".": {
"import": "./esm/index.js",
"require": "./cjs/index.js"
},
"./html": {
"import": "./esm/html.js",
"require": "./cjs/html.js"
},
"./dom": {
"import": "./esm/dom.js",
"require": "./cjs/dom.js"
}, Thus, if you The problem is that conditional exports don't require that the
I guess it's "nice" that
So, in practice, the As a result of this confusion, you might expect that
This was filed a couple of months ago, as nodejs/node#32137 "Misleading error that module does not provide export" |
But what I’m asking is, if we add "type": "module", would that not fix the .mjs stuff too? |
I feel faintly silly suggesting this, but I've got another branch https://github.com/dfabulich/crank/pull/new/explicit-cjs that works surprisingly well. At least, better than I'd expected. In that branch, instead of using This all works great in Node 14, both in CJS and ESM mode, thanks to the Unfortunately, the const {createElement} = require("@bikeshaving/crank/cjs/index.cjs");
const {renderer} = require("@bikeshaving/crank/cjs/html.cjs"); That workaround would continue to work in Node 14, though it would be unnecessary. This might be a better approach just because Parcel, Rollup, and Webpack seem to get a little weird around parcel-bundler/parcel#4155 "Use It does feel faintly more correct to automatically define |
I’m continuing to investigate this, but I can only do it in limited amounts because the state of esmodules support in node.js makes me so upset. The community told node maintainers over and over they wanted absolutely nothing to do with .mjs files and they kept pushing em on us, adding successive amounts of configuration across different versions of node which are poorly explained, error in cryptic ways for consumers, and depart from conventions established by package.json itself. It’s such a terrible situation and right now I’m thinking that I will refuse to support or output .mjs/.cjs files out of principle. I’m really fuming right now and I want to say lots of mean things. I can’t believe things could get this fucked up. What an absolute abdication of leadership. Sorry. Thanks for your help @dfabulich, but understand when I say that we as developers need to take a stand when it comes to complexity, especially complexity with regards to modules and configuration, and I can’t just merge this in without a lot of convincing even if things might be “broken.” |
Lemme try to calm you down a little. :-) MJS and CJS don't interop wellThe main thing that sucks is that ESM scripts can't natively I don't fully understand why this is, but I believe there's some subtle runtime interop issue around CommonJS caching(?), and that the Node modules team tried hard to make it work; they only reluctantly gave up. They updated the docs in November where previously they said:
But they removed the "Currently," and the line saying that "there are ongoing efforts." :-( The interop issue is nobody's faultBut why did this happen? At the end of the day, ESM was primarily designed for browsers, and designed together by the browser vendors (Google, Apple, Microsoft, and Mozilla) to minimize network overhead. The browser vendors gave only a casual thought to Node interop in this process. What you call "an absolute abdication of leadership," I'd call "prioritizing browser performance over Node interop." Browser performance is a really important problem! If Node has to undergo a painful transition period to make I certainly can't blame the Node modules team for not convincing three multi-billion-dollar corporations to tweak the design on their behalf, and neither do I blame the browser vendors for prioritizing browser performance over Node interop. The fact that MJS and CJS don't play well together is nobody's fault. Everybody was trying hard to do the right thing. But everything that sucks about this problem rolls downhill from that issue. Conditional exports are good, actuallyIf you start with the understanding that separating ESM from CJS is necessary, then I actually think the conditional exports Now, it does suck that there's no way to say to Node "these The only way to mix-n-match CJS and MJS today is via file extensions, either However! The It still sucks, thoughI could imagine someone saying that since the But no, you're right about at least one thing, Still, the problem isn't the file extension. The problem is that the ESM scripts and the CJS scripts don't get along; So let's merge this thingIf you agree with everything I've written here, I'd ask you to merge this PR and move on with our lives. If you want to "take a stand," at least identify some specific proposal issue in the https://github.com/nodejs/modules/issues repository to fight for. Say that fixing #87 is "blocked" on https://github.com/nodejs/modules/issues/99999 or whatever, and work through the problems in that issue. But if you don't have a specific proposal to fight for, if you're just waiting around hoping that someone else will lead, let's merge this thing while we wait. |
I looked it up. nodejs/modules#81
Some folks suggested just executing CJS imports before ESM imports, out of order, but:
|
Any thoughts since last week? |
@dfabulich I apologize for leaving this open for so long! I’ll make sure this or another solution gets pushed out by Sunday at latest. I’m currently exploring performance fixes, and as it turns out, using untranspiled generator objects is much much better than transpilation so I want to batch all these changes for the first breaking change release (0.2.0), where we ship crank uncompiled for esmodules, as well as shipping UMD builds (#45) and maybe mangling some methods so the resulting bundle output is smaller. As far as the configuration maze goes. I prefer:
in that order. I know, I know, I just want to see if it’s actually possible because I think it’s increasingly possible that node dies in the next half-decade and I don’t want to be stuck supporting these terrible file extensions. Of course, I will defer to your expertise on this matter and if you say something is not possible I will believe you (after trying for a couple hours to do it myself). If this means that people on CommonJS environments have to go out of their way to adjust paths to files or upgrade, I’m okay with that. ESModule support should be first-class. |
I claim that your option 1 ( Option 2 ( To be clear, the current draft of the PR does a fourth thing, Option X, But if your sense is that backwards compatibility is unimportant (because Crank isn't 1.0, and perhaps because deno ESM is the future?) then I can reroll this PR with the |
Yeah, I care less about breaking changes with regard to module formats and bundling, because those are easier upgrades than actual changes to framework logic, and it’s important to get this right before 1.0. And because esmodules are clearly the future, I think it’s better to have the non-standard file format be the cjs files.
One possible solution I thought while in the shower the other day that I have yet to try: copying the package.json into the cjs directory and changing the type. Whether that’s a good idea or not, who knows 😉 If you end up figuring out all of this stuff I would very much like to read a blog post or gist about it. If I write it all up it would probably end up too sweary and toxic. |
OMG that worked! But… I'm not exactly certain how to put this in a PR yet. https://github.com/dfabulich/crank/pull/new/subpackage-json That PR changes nothing about the build, but only changes The problem is, when you What's the best way to install Anyway, once I test script
The script mostly works on Node 12, except that Since that's not a regression, I'd say this is a big step forward! |
@dfabulich There is always a way 😄 I guess the best way to do it would be a rollup plugin, but I also wonder if we can’t just use fs.writeFileSync in rollup.config.js. I’m also wondering if we have to support the exports stuff if we use this solution? I’m looking at the exports stuff and it feels like a footgun in the sense that we have to make sure that files and exports always match in case we want to expose other files. |
@dfabulich Does #108 solve every permutation of file extension, node version, command-line flag and build system that we’re interested in? I have to go smoke a cigarette because I’m angry again at the state of things. |
This PR is dead; we're going with #108 instead. |
Fixes #87.
Based on https://nodejs.org/api/esm.html it seems like there's just two things we need to do here.
.mjs
extensionspackage.json
needs to have anexports
section, defining the ESM and CJS exports.I think it would be nice to add built-in automated tests of this stuff, but I have no clear idea of how to do that.
Here's the test script I used to verify that I did no harm and that I fixed the bug.