-
-
Notifications
You must be signed in to change notification settings - Fork 31.6k
module: expose resolveLoadAndCache
API
#55756
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
base: main
Are you sure you want to change the base?
Conversation
Review requested:
|
lib/internal/modules/helpers.js
Outdated
@@ -392,6 +392,24 @@ ObjectFreeze(compileCacheStatus); | |||
const constants = { __proto__: null, compileCacheStatus }; | |||
ObjectFreeze(constants); | |||
|
|||
async function resolveLoadAndCache(specifier, base = 'data:', importAttributes = kEmptyObject, conditions) { | |||
const cascadedLoader = require('internal/modules/esm/loader').getOrInitializeCascadedLoader(); | |||
// TODO: this should hit the cache (and populate it if it finds nothing) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is already doing that, no? I see gets and sets
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or, it appears to do for ModuleLoader
but not CustomizedModuleLoader
. Is that what you mean?
}); | ||
return { | ||
__proto__: null, | ||
format, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When --experimental-strip-types
is enabled and performing this operation on a .ts
file, this format
may contains commonjs-typescript
and module-typescript
which are not documented at https://nodejs.org/api/module.html#loadurl-context-nextload as final formats.
I think we should convert commonjs-typescript
and module-typescript
to commonjs
and module
respectively. Alternatively, document the other two and make them public.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we probably want to document it, as it's certainly going to be helpful for the ecosystem to know which files are TypeScript
5600325
to
2c153f8
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #55756 +/- ##
==========================================
+ Coverage 89.66% 90.21% +0.55%
==========================================
Files 630 630
Lines 186389 186437 +48
Branches 36286 36612 +326
==========================================
+ Hits 167124 168198 +1074
+ Misses 12049 11060 -989
+ Partials 7216 7179 -37
🚀 New features to boost your workflow:
|
Module.constants = constants; | ||
Module.enableCompileCache = enableCompileCache; | ||
Module.findPackageJSON = findPackageJSON; | ||
Module.findSourceMap = findSourceMap; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like unrelated changes.
doc/api/module.md
Outdated
relative, it is resolved relative to `parentURL`. | ||
* `parentURL` {string|URL|undefined} If you want to resolve `specifier` relative to a base | ||
URL, such as `import.meta.url`, you can pass that URL here. If not provided, | ||
the `resolve` step will be skipped. **Default:** {undefined}. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a fan of implicitly skip resolve
when parentURL
is undefined
. This also contradicts with the API name resolve, load, and cache
.
We have several conventions in APIs about how to resolve the parentURL
:
new Worker(filename)
resolves with base toprocess.cwd()
,module.register(specifier[, parentURL])
resolves with base todata:
ifparentURL
is not set.
I think as part of module loader API, it is better to be explicit, e.g. throw if parentURL
is missing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
module.register(specifier[, parentURL])
resolves with base todata:
ifparentURL
is not set.I think as part of module loader API, it is better to be explicit, e.g. throw if
parentURL
is missing.
Note that this way of doing things is consistent with module.register
, i.e. it will throw if specifier
is not a full URL and parentURL
is missing (because load
only accepts full URLs).
Not a fan of implicitly skip
resolve
whenparentURL
isundefined
. This also contradicts with the API nameresolve, load, and cache
.
The name is not great, I'm certainly fine changing it to something else. IMO being able to skip resolve
is important as it allows to get results guaranteed to be true for the lifetime of the process (at least until we expose a way to clear the load cache), I'm open to suggestions if you think we can make a clearer API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I'm concerned about is that using a full specifier
URL and an implicit parentURL
to indicate skipping resolve
is not alike with import
:
import 'test:./foobar'
With import statements or import calls, a resolve hook will be invoked with a full specifier
URL and implicit parentURL
.
export function resolve(specifier, context, next) {
if (specifier.startsWith('test:')) {
// Translate custom protocol and use the default loader.
return next(specifier.slice(5), context);
}
return next(specifier, context);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot make it work like import
, unless we define it on import.meta
, but I get what you're saying. What do you think about going back to using data:
as default parentURL
, and expose some symbol to explicitly skip the resolve call if needed?
// does not go through `resolve` hook:
resolveLoadAndCache('full://module/url', module.SKIP_RESOLVE_CALL);
// or maybe
resolveLoadAndCache('full://module/url', Symbol.for('nodejs.skipResolveCall'));
// does go through `resolve` hook with `data:` as `parentURL`:
resolveLoadAndCache('full://module/url');
// does go through `resolve` hook just like `import`/`import()` would:
resolveLoadAndCache('full://module/url', import.meta.url);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree that this API does not infer parentURL
implicitly to the caller module (it is not practical anyway). I think a possible alternative to Symbol based API is separating the motivation of invoking each of the loader hook into individual APIs:
interface ModuleLoaderAPI {
resolve(specifier, parentURL, attributes): Promise<{ url, format }>;
load(url, attributes): Promise<{ format, source }>;
resolveAndLoad(specifier, parentURL, attributes): Promise<{ url, format, source }>;
}
In this way, the API intention is explicit.
Co-authored-by: Chengzhong Wu <[email protected]> Co-authored-by: Chemi Atlow <[email protected]>
2c153f8
to
e04c622
Compare
Opening as draft as I'd like to get some feedback on the API design first. /cc @nodejs/loaders
Use case for this is to get "final" format of the module that would result from importing a specific specifier. The cache part is primordial as otherwise you'd have no guarantee that the result would be accurate (if there's a user loader involved, we have no reason to believe it would be stable over time). However, caching a call that's sometimes async, sometimes sync is more or less impossible in JS, and on top of that there are loaders out there that rely on resolve NOT being cached (e.g the test runner
.mock
util), so instead I decided it'd be more useful to provide a way to skip theresolve
call.