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

Feature: Retrievable module metadata #104

Closed
GeoffreyBooth opened this issue May 20, 2018 · 31 comments
Closed

Feature: Retrievable module metadata #104

GeoffreyBooth opened this issue May 20, 2018 · 31 comments
Labels

Comments

@GeoffreyBooth
Copy link
Member

It is possible to retrieve metadata about a module being imported via an import statement.

Use case 13.

@Jamesernator
Copy link

I'm not sure what this feature is. Is this just asking for capabilities like import.meta or is it asking for the ability to get metadata about another module?

If it's the later this could (perhaps weirdly) be solved by import.meta again by exposing a function to get the metadata for another module e.g.:

// Would get the metadata of another file resolved
// in the same way as import() 
import.meta.metadataFor('./foo.mjs');

@benjamingr
Copy link
Member

Hey, I think you might be missing the use cases document - https://docs.google.com/document/d/10BBsIqdAXB9JR2KUzQGYbCiVugYBnxE4REBakX29yyo/edit

This use case by @jkrems

Robin reads the source of one of their projects and finds an import statement. They want to quickly find the file being imported. After a few steps they find the imported file in their file system.
(q: Is this runtime? Debug? Static? require.resolve)

This can for example be done by import.meta.url or other stuff (so your assumption was correct).

Please don't hesitate to speak up if there is anything unclear about the discussion here.

@ljharb
Copy link
Member

ljharb commented May 21, 2018

import.meta.resolve seems like it might be particularly helpful.

@jkrems
Copy link
Contributor

jkrems commented May 21, 2018

They want to quickly find the file being imported. After a few steps they find the imported file in their file system.

The use case as written was about everyday reading/editing of the code. E.g. it was about a person reading the code being able to quickly find the file if they'd switch to a terminal or a file browser. With browser ESM that's super easy (right now) but CommonJS scores much lower here.

@SMotaal
Copy link

SMotaal commented May 22, 2018

Sorry for going off on a tangent and in detail

I found that part very interesting as there is possibly unique opportunities to be considered:

(q: Is this runtime? Debug? Static? require.resolve)

Normally, module resolution systems are either a) browser (per spec) or node (per config) runtime or b) runtime-emulating with or without a mapping layer for development. In other words, at the core of every non-runtime module resolution implementation is some logic that emulates to some degree the target runtime, and those implementations may or may not include additional layers for mapping between source and target specifiers.

Given that most development workflows today involve at least one such implementation. Add to that the fact that each implementation (even if flawless) often involves additional configuration points. It is easy to imagine how all these different systems exponentially complicate the development experience, limit it or sometimes even break previously existing projects. Just consider how the adoption of something as widely accepted as ES modules has been impacted across the various tools.

Node is in a very unique position at the moment when redesigning the module system in that it can take over the most basic module resolution layers and provide a Module Resolution API shared across runtime and development use cases. Essentially, the module system would be clearly broken down to a) resolution and b) evaluation or execution. The latter subsystem is strictly employed by Node's runtime, where it would depend on a single instance of the former following the specific runtime target configuration.

In that sense, I can imagine that other node-based development tools (ie Babel, CoffeeScript, TypeScript... etc) can also create separate instances of the Module Resolver with a specific configuration profile for some runtime target. And at the same time, debugger simply use the same instance of the Module Resolver used by Node's runtime or in more exotic cases simply decorate or extend that instance which can be useful for overloading, hot reloading, or VM.

This divide can lend itself very nicely across a number of features.

@GeoffreyBooth
Copy link
Member Author

Node is in a very unique position at the moment when redesigning the module system in that it can take over the most basic module resolution layers and provide a Module Resolution API shared across runtime and development use cases.

This sounds like a great suggestion. Do you mind adding one or more use cases to the use cases doc that cover this? And in the meeting, assuming a feature gets created to correspond with those use cases, this can get is own feature issue.

@GeoffreyBooth
Copy link
Member Author

The use case as written was about everyday reading/editing of the code.

@jkrems That actually ended up as a separate feature: #103. That’s just how it was in the features document; in discussion I guess the one use case got spun out into two features. Maybe this one wasn’t intended from the use case, but it seems a reasonable feature request on its own.

@SMotaal
Copy link

SMotaal commented May 23, 2018

@GeoffreyBooth after the much needed hours to catch up on what I missed over the past three weeks (away due to family emergency) I just added 4 use cases that kind of beg the feature — it's hard to not be biased when you have feature in mind.

And in the meeting, assuming a feature gets created to correspond with those use cases, this can get is own feature issue

So what's next? I have dedicated time today and really want to get back on track with our efforts.

@bmeck
Copy link
Member

bmeck commented May 23, 2018

If we put something on import.meta that doesn't match with browsers I would request that we namespace it to something that browsers can agree not to use like import.meta.node.resolve if import.meta.resolve cannot be isomorphic.

@benjamingr
Copy link
Member

@bmeck it would be way cooler if we could make .resolve work on the client - I can totally see the appeal in that.

If we can't, then if we go with import.meta.require then import.meta.require.resolve might be appropriate.

@ljharb
Copy link
Member

ljharb commented May 23, 2018

With or without transparent interop, fwiw, import.meta.require would still be very useful (because the language lacks a way to synchronously import things conditionally and/or dynamically), so import.meta.require.resolve also would make sense

@benjamingr
Copy link
Member

With or without transparent interop, fwiw, import.meta.require would still be very useful (because the language lacks a way to synchronously import things conditionally and/or dynamically)

Given top-level-await just made stage 2 I don't really see this as a problem.

I realize I changed my mind from last week - but then again last week I didn't know it was making progress and this week it's stage 2 so there's that.

@ljharb
Copy link
Member

ljharb commented May 23, 2018

Top-level await doesn't change this discussion in the slightest, imo - TLA is no different than a setTimeout that reassigns a property on module.exports or that reassigns an exported let.

@benjamingr
Copy link
Member

@ljharb I'm sorry, I don't understand that - would you mind explaining or showing some code that demonstrates this issue?

@ljharb
Copy link
Member

ljharb commented May 23, 2018

@benjamingr

// CJS
module.exports.foo = 3;
module.exports.bar = undefined;
fetch('some JS file').then(() => {
  module.exports.bar = 1;
  module.exports.foo = 4;
});
// ESM without TLA
export let foo = 3;
export let bar;
fetch('some JS file').then(() => {
  bar = 1;
  foo = 4;
});
// ESM with TLA
export let foo = 3;
await fetch('some JS file');
export let bar = 1; // this will be hoisted above the `await`, but not evaluated until here
foo = 4;

All three of these modules start out exporting "foo" set to 3, and once the JS file is fetched, change to export it set to 4. Adding top-level await doesn't have any impact on blocking, or on mutable exports, or on the shape of module.exports or the module namespace object.

@targos
Copy link
Member

targos commented May 23, 2018

@ljharb

As I understand how top-level await should work, it is different because the importer of foo will never observe the original value 3. Am I wrong?

@ljharb
Copy link
Member

ljharb commented May 23, 2018

@targos you are wrong; the instant the TLA occurs, the importer resumes, so they will see identical behavior in all three cases.

@benjamingr
Copy link
Member

benjamingr commented May 23, 2018

All three of these modules start out exporting "foo" set to 3, and once the JS file is fetched, change to export it set to 4. Adding top-level await doesn't have any impact on blocking, or on mutable exports, or on the shape of module.exports or the module namespace object.

So the request here is explicitly for importing modules completely synchronously?

Would you mind motivating this with a real-world use case? I think that TLA addresses the use cases we discussed so far. Maybe I just missed one.

If there is another use case that requires the synchronous behavior we should add it to the list.

@ljharb
Copy link
Member

ljharb commented May 23, 2018

@benjamingr there's already use cases in the doc that require synchronous import of CJS as a part of transparent interop; this specific issue i believe would be addressed by import.meta.require.resolve.

@MylesBorins
Copy link
Contributor

MylesBorins commented May 23, 2018

I just followed up with @domenic and the current intention of the TLA proposal is that the parent module would not execute until after the child resolved

In the below case, the module would log 4, not 3

import { foo } from './dep';
console.log(foo);

dep.js:

export let foo = 3;
await fetch('some JS file');
foo = 4;

@ljharb
Copy link
Member

ljharb commented May 23, 2018

@MylesBorins that's very confusing to me, and that's something we need to address separately in that proposal.

@bmeck
Copy link
Member

bmeck commented May 23, 2018

@ljharb evaluation from TLA blocks dependents until end of source text is reached. It does not let dependents evaluate immediately after the first await is reached.

@giltayar
Copy link

@ljharb - I believe top-level await significantly changes the ESM landscape (funny how the feature is "just" called "top-level await"). And all because of default export. Assume a module with this code (and assume fetch is like the browser fetch):

export default await fetch('http://....').then(response => response.text())

This module MUST be loaded asynchronously—and all of it's top-level await-s waited upon—before continuing with the parent module execution.

Why does this significantly change the ESM landscape? Because, from what I can see, this module cannot be transpiled to CJS!. No amount of babeling will make this work.

Top-level await transforms ESM from "could be synchronous" to "must be asynchronous". And this is huge. It is finally realising the benefits of ESM's asynchronicity.

@benjamingr
Copy link
Member

This module MUST be loaded asynchronously—and all of it's top-level await-s waited upon—before continuing with the parent module execution.

Are you sure this wouldn't transpile to exporting undefined (or a TDZ) and then replacing the value of the live-binding with the value of the response text?

@ljharb
Copy link
Member

ljharb commented May 25, 2018

TLA advanced to stage 2 prior to my having this understanding; I’ll revisit this with the committee next time it comes up.

@jdalton
Copy link
Member

jdalton commented May 25, 2018

This thread morphed into a bit more on TLA. We should open a new issue to discuss TLA specifically or move discussion to the TLA repo.

@giltayar
Copy link

Are you sure this wouldn't transpile to exporting undefined (or a TDZ) and then replacing the value of the live-binding with the value of the response text?

Yes, I asked @MylesBorins about it in one of the modules meetings, when we discussed top-level await, because I knew that the answer to that would determine whether it was just top level await, or a whole breaking change from the babel-model of synchronous loading.

@guybedford
Copy link
Contributor

Could we change the title of this feature here to "Provide resolver API" perhaps? As it sounds like it is being confused with module metadata which actually doesn't seem to have its own feature issue currently.

@guybedford
Copy link
Contributor

This seems like a duplicate of #103 to me, as they both reference the same use case?

@demurgos
Copy link

demurgos commented Jul 18, 2018

See #103 (comment)

This issue is about getting programmatic access to metadata of imported modules.
#103 is about human developers being able to reproduce the resolution algorithm.

@guybedford
Copy link
Contributor

@demurgos if you look at the original post of both, they come from the same use case. The exact text of the use case is copied in #104 (comment).

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

Successfully merging a pull request may close this issue.