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

ESM modules and json importing [feature request] #20494

Closed
damianobarbati opened this issue May 3, 2018 · 33 comments
Closed

ESM modules and json importing [feature request] #20494

damianobarbati opened this issue May 3, 2018 · 33 comments
Labels
esm Issues and PRs related to the ECMAScript Modules implementation. feature request Issues that request new features to be added to Node.js.

Comments

@damianobarbati
Copy link

damianobarbati commented May 3, 2018

Will it be possible in the future to import .json files as we used to required .json files?

import pkg from './package'
import { name, version } from './package'
@targos targos added feature request Issues that request new features to be added to Node.js. esm Issues and PRs related to the ECMAScript Modules implementation. labels May 3, 2018
@targos
Copy link
Member

targos commented May 3, 2018

The current ESM implementation already allows to import a JSON as default (first line).
I suppose we could add named imports, if it's something that users ask for.

/cc @nodejs/modules

@devsnek
Copy link
Member

devsnek commented May 3, 2018

named exports from json doesn't make a whole lot of sense to me. named imports are not and shouldn't be used in place of destructuring.

@bmeck
Copy link
Member

bmeck commented May 3, 2018

I have some concerns with JSON imports that I can list here:

  1. Imported names (and properties of the Namespace Object) must be valid JS identifiers, things like en-US are invalid, so you need to still use the default.
  2. Primitives don't really make sense for named imports. However, string length is potentially useful??
  3. Arrays could only read the length which we cannot make a live binding (unless we use Proxies).
  4. If we want the bindings to be live since we need to always export a default (and therefore allow mutation of the original JSON) per number 1 we need to wrap it in a proxy and not track additional properties, just like esm: provide named exports for builtin libs #20403 does.
  5. Since number 1 is required we cannot import a default property from JSON.

@ljharb
Copy link
Member

ljharb commented May 3, 2018

a json file should only have a default export; named imports are decidedly NOT destructuring, and allowing this would spread that confusion.

@jdalton
Copy link
Member

jdalton commented May 3, 2018

Named exports work for the cases they do and don't for the cases they don't. Users don't care about the cases named exports don't work since they can use alternative import forms to grab them. It's a convenience thing. JSON is just another module.exports value. This will shake out naturally if CJS named exports support expands, from just builtin modules, to a wider scope.

@bmeck
Copy link
Member

bmeck commented May 3, 2018

@jdalton I don't understand the major wins vs just using destructuring which is also static so tree shaking isn't a problem. The edge cases are weird. Adding weirdness for the sake of convenience I think needs to have a more compelling reason than what exists in this thread currently to me personally.

@jdalton
Copy link
Member

jdalton commented May 3, 2018

JSON support isn't something that has to be explicitly expanded or deliberated on. It shakes out naturally, with no extra work, with wider CJS named exports support as seen with Babel and esm packages.

@ljharb
Copy link
Member

ljharb commented May 3, 2018

That it’s no extra work doesn’t mean it’s a good idea. Even if named exports from CJS are able to become a thing, i don’t think that should be made to apply to json imports.

@jdalton
Copy link
Member

jdalton commented May 3, 2018

Named exports for CJS is an interop enhancement. As such it doesn't seem like the place to start green-fielding and diminishing value.

@bmeck
Copy link
Member

bmeck commented May 3, 2018

@jdalton I don't see it as no extra work / naturally shaking out from how JSON works? I'm not sure I understand how this affects my concerns above.

@jdalton
Copy link
Member

jdalton commented May 3, 2018

In CJS Node supports loading .json files from require. It parses the JSON and bolts the value on to a module.exports. So it's just another module.exports value (nothing special about it).

CJS named exports work in the cases they can work. It's an interop enhancement which means, yes, some cases like property names that can't be translated into identifiers won't be supported as a named export and that's fine. That isn't something supported by Babel or others anyways.

@bmeck
Copy link
Member

bmeck commented May 3, 2018

@jdalton we are not talking about CJS named exports here. I disagree on your course of discussion unless we tie these features together.

@devsnek
Copy link
Member

devsnek commented May 3, 2018

with cjs, everything is an object

with esm, everything is well-known values

json is, by definition, a representation of an object.

saying that "because we do it in cjs we should do it in esm" is in my opinion a very silly stance because they are inherently different.

@jdalton
Copy link
Member

jdalton commented May 3, 2018

@bmeck

we are not talking about CJS named exports here. I disagree on your course of discussion unless we tie these features together.

You may not have thought you were talking about CJS named exports, but that's what it is and more importantly that's how the user will see it. JSON is just another module.exports value – so support, if any, will shake out of the CJS named exports work.

@devsnek

with cjs, everything is an object

A module.exports value does not have to be an object.

saying that "because we do it in cjs we should do it in esm" is in my opinion a very silly stance because they are inherently different.

When the feature discussed is CJS interop then it totally makes sense to bring up.

@ljharb
Copy link
Member

ljharb commented May 3, 2018

I think it’s reasonable to bring up. I think it’s a choice we could certainly make. I agree that the implementation would shake out “for free” along with named imports for CJS, and that non-identifier-names would simply not be named-importable, and that would be fine.

However, conceptually, i do not think json is the same as a CJS module, and i think that it would be a mistake to let the consumer of a json file “conveniently” but sloppily pretend that their json file is exporting more things than “the single json-parsed value”.

@jdalton
Copy link
Member

jdalton commented May 3, 2018

The arm wrestle between green-fielding and interop is ongoing for sure. None that will get settled in this thread.

@bmeck
Copy link
Member

bmeck commented May 3, 2018

You may not have thought you were talking about CJS named exports, but that's what it is

That is debatable but I side on JSON not being CJS. If we introduce named exports we might also need to move it off of the CJS require.extensions['.json'] implementation detail we have right now in order to keep order of evaluation.

It seems like this feature could be added after shipping an implementation of ESM, and I'm still not sure the convenience is worth prioritizing. We can add this to the modules meeting agenda, but probably cannot do anything while we still debate use cases.

@jdalton
Copy link
Member

jdalton commented May 3, 2018

it seems like this feature could be added after shipping an implementation of ESM

It could also be bundled with however wider CJS named exports ends up being handled.
(configurable opt-ins-or-outs or whatever happens on that front).

We can add this to the modules meeting agenda

I don't think there is any urgency around this to warrant a specific agenda item.

but probably cannot do anything while we still debate use cases.

I think it'll likely work out of the use case / feature process on its own.

@GeoffreyBooth
Copy link
Member

GeoffreyBooth commented May 3, 2018

Just to contribute a use case, one pattern I see often is require('./package.json'). It’s a pretty convenient way to get a package’s current version while staying DRY. Real-world example.

@bmeck
Copy link
Member

bmeck commented May 3, 2018

@GeoffreyBooth I don't think anyone wants to prevent loading JSON here.

@damianobarbati
Copy link
Author

@GeoffreyBooth yeah, that's the typical scenario: getting package.json configuration straight forward.

@andyearnshaw
Copy link

Webpack supports named exports for JSON:

import { version } from './package.json';

console.log(version);

Presumably, it does this to simplify tree shaking; the unused portions of the JSON can be dropped. I could see it being useful if Node mimicked this behaviour, mostly for code that is designed to run on both the server and the client. It would also serve to make porting to natively supported .mjs easier; devs would not have to refactor such instances in their code just to get it to run on Node.

@justinfagnani
Copy link

mostly for code that is designed to run on both the server and the client.

Code that runs in browsers cannot import JSON, only JS modules.

@devsnek
Copy link
Member

devsnek commented Nov 28, 2018

i'm not convinced this should be in core. this seems like a good situation for some sort of named-exports-from-json loader.

@guybedford
Copy link
Contributor

It's worth noting we still don't even have any consensus on the minimal implementation that ".json" imports should be supported (to rather possibly be handled by asset loading techniques).

@andyearnshaw
Copy link

mostly for code that is designed to run on both the server and the client.

Code that runs in browsers cannot import JSON, only JS modules.

I was referring to code compiled by webpack for browsers (which I expect will still be a thing for a long time to come), but there's also WICG/webcomponents#770 for native support.

@aduh95
Copy link
Contributor

aduh95 commented Jun 19, 2019

Hasn't this issue been addressed by #26745?

@targos
Copy link
Member

targos commented Jun 19, 2019

Yes, thanks @aduh95

@kalinchernev
Copy link

I landed at this issue while looking for an easy way to achieve what has been originally requested:

import pkg from './package'
import { name, version } from './package'

After trying a few approaches, I came up with following, without dependencies or feature flags:

import { readFile } from 'fs/promises'

const pkg = JSON.parse(await readFile(new URL('./package', import.meta.url)))
const { name, version } = pkg;

@MylesBorins
Copy link
Contributor

@kalinchernev you may find the following a bit better.

import { createRequire } from 'module';
const require = createRequire(import.meta.url);

const { name, version } = require('./package');

@kalinchernev
Copy link

Thanks @MylesBorins!
Actually jest 26.6.3 Runtime.loadEsmModule does not fail with unexpected reserved word with the approach you suggested, so it's better.

@TimDaub
Copy link

TimDaub commented Mar 5, 2021

I was quite surprised that it's not possible to import .json files with .mjs. I consider this a bug.

@targos
Copy link
Member

targos commented Mar 5, 2021

It's not a bug. It's an experimental feature currently behind a flag (--experimental-json-modules). See #37375.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
esm Issues and PRs related to the ECMAScript Modules implementation. feature request Issues that request new features to be added to Node.js.
Projects
None yet
Development

No branches or pull requests