Skip to content

Always run async loader hooks synchronously from the non-loader thread #59666

@joyeecheung

Description

@joyeecheung

This was an idea discussed in the loaders group a few times in the past as well as in the last collaboration summit. Opening an issue to track it properly.

This is related to #55782 and will allow us to

  1. Eliminate the races in ESM cache handling when require(esm) needs the linking to be synchronous but import esm may populate an asynchronous linking job in the cache. There were multiple band-aids applied to work around this, like esm: link modules synchronously when no async loader hooks are used #59519 and module: handle cached linked async jobs in require(esm) #57187, but to fully eliminate the races, this would be necessary to finish Tracking Issue: Syncify the ESM Loader #55782
  2. Go back to using the authentic require() in imported CJS instead of re-inventing a separate require() and eliminate its quirks, which currently includes
    1. The re-invented require lacks a lot of properties like require.cache, which is a documented caveat https://nodejs.org/api/module.html#caveat-in-the-asynchronous-load-hook
    2. The source code for CJS ended up being loaded twice
      // For backward compatibility reasons, we need to discard the source in
      // order for the CJS loader to re-fetch it.
      source = null;
    3. As a side-effect of the previous quirk, when the module format is CJS, the user hook has to also deal with source being null and usually also has to load it twice or more, like what's being done in import-in-the-middle, see ERR_INVALID_RETURN_PROPERTY_VALUE when using module.register and module.registerHooks #57327 (comment) - if they don't override the nullish value, the require in imported CommonJS won't trigger the hooks.
    4. It gets import in its conditions array, instead of require (because it's using ESM resolution)
    5. The resolve hook gets the full URL, not the specifier passed to require()
    6. Invoking the sync hooks twice for CJS, one from the re-invented require, one from the Module._load invocation in the evaluation step.

Refs: #57327
Refs: #55808
Refs: #52697
Refs: #56241
Refs: #55782

cc @nodejs/loaders

Metadata

Metadata

Assignees

No one assigned

    Labels

    loadersIssues and PRs related to ES module loaders

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions