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

Add "resolveDependencies" option to "this.load" #4358

Merged
merged 1 commit into from Jan 22, 2022

Conversation

lukastaegert
Copy link
Member

@lukastaegert lukastaegert commented Jan 20, 2022

This PR contains:

  • bugfix
  • feature
  • refactor
  • documentation
  • other

Are tests included?

  • yes (bugfixes and features will not be merged without tests)
  • no

Breaking Changes?

  • yes (breaking changes will not be merged unless absolutely necessary)
  • no

List any relevant issue numbers:

Description

This is the third important addition I want to add to the plugin system around this.load. This will make it possible to load and analyze entire dependency sub-graphs from within a single plugin hook.

This PR adds a flag resolveDependencies to this.load that will make this.load wait until importedIds and dynamicallyImportedIds for the module in question have been resolved. Thus it is no longer necessary to wait for moduleParsed to get this information.

Note that I still think that the previous restriction of NOT waiting for this information is still a good default IMO because when waiting for this hook in resolveId, it is very easy to accidentally create a dead-lock where the hook waits for its own completion.

Here is a slightly artificial example (that I also want to put into the docs), which uses this to find out which modules will be loaded by a dynamic import and inject a proxy module that just logs all those modules when the dynamic import is triggered. More useful examples could involve scanning for CSS dependencies in a sub-graph to e.g. rewrite a dynamic import in the transform hook (just make sure to not wait for loading the module that contains the dynamic import if there is a cycle, or create a dead-lock between two modules dynamically importing each other. For those cases, you probably need to create a registry which modules are currently waiting for dependency graphs to be resolved and for those modules, manually resolve the non-dynamic imports yourself instead of waiting for them.).

// The leading \0 instructs other plugins not to try to resolve, load or
// transform our proxy modules
const DYNAMIC_IMPORT_PROXY_PREFIX = '\0dynamic-import:';

export default function dynamicChunkLogsPlugin() {
  return {
    name: 'dynamic-chunk-logs',
    async resolveDynamicImport(specifier, importer) {
      // Ignore non-static targets
      if (!(typeof specifier === 'string')) return;
      // Get the id and initial meta information of the import target
      const resolved = await this.resolve(specifier, importer);
      // Ignore external targets. Explicit externals have the "external"
      // property while unresolved imports are "null".
      if (resolved && !resolved.external) {
        // We trigger loading the module without waiting for it here
        // because meta information attached by resolveId hooks, that may
        // be contained in "resolved" and that plugins like "commonjs" may
        // depend upon, is only attached to a module the first time it is
        // loaded.
        // This ensures that this meta information is not lost when we later
        // use "this.load" again in the load hook with just the module id.
        this.load(resolved);
        return `${DYNAMIC_IMPORT_PROXY_PREFIX}${resolved.id}`;
      }
    },
    async load(id) {
      // Ignore all files except our dynamic import proxies
      if (!id.startsWith('\0dynamic-import:')) return null;
      const actualId = id.slice(DYNAMIC_IMPORT_PROXY_PREFIX.length);
      // To allow loading modules in parallel while keeping complexity low,
      // we do not directly await each "this.load" call but put their
      // promises into an array where we await them via an async for loop.
      const moduleInfoPromises = [this.load({ id: actualId, resolveDependencies: true })];
      // We track each loaded dependency here so that we do not load a file
      // twice and also do  not get stuck when there are circular
      // dependencies.
      const dependencies = new Set([actualId]);
      // "importedIdResolutions" tracks the objects created by resolveId
      // hooks. We are using those instead of "importedIds" so that again,
      // important meta information is not lost.
      for await (const { importedIdResolutions } of moduleInfoPromises) {
        for (const resolved of importedIdResolutions) {
          if (!dependencies.has(resolved.id)) {
            dependencies.add(resolved.id);
            moduleInfoPromises.push(this.load({ ...resolved, resolveDependencies: true }));
          }
        }
      }
      // We log all modules in a dynamic chunk when it is loaded.
      let code = `console.log([${[...dependencies]
        .map(JSON.stringify)
        .join(', ')}]); export * from ${JSON.stringify(actualId)};`;
      // Namespace reexports do not reexport default exports, which is why
      // we reexport it manually if it exists
      if (this.getModuleInfo(actualId).hasDefaultExport) {
        code += `export { default } from ${JSON.stringify(actualId)};`;
      }
      return code;
    }
  };
}

cc @yyx990803, @patak-dev, @antfu, @marvinhagemeister for implementation in Vite and WMR

@github-actions
Copy link

github-actions bot commented Jan 20, 2022

Thank you for your contribution! ❤️

You can try out this pull request locally by installing Rollup via

npm install rollup/rollup#this-load-resolve-dependencies

or load it into the REPL:
https://rollupjs.org/repl/?pr=4358

@codecov
Copy link

codecov bot commented Jan 20, 2022

Codecov Report

Merging #4358 (e6b1d49) into master (e88edfd) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #4358   +/-   ##
=======================================
  Coverage   98.69%   98.69%           
=======================================
  Files         205      205           
  Lines        7334     7339    +5     
  Branches     2086     2089    +3     
=======================================
+ Hits         7238     7243    +5     
  Misses         36       36           
  Partials       60       60           
Impacted Files Coverage Δ
src/ModuleLoader.ts 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update e88edfd...e6b1d49. Read the comment docs.

@lukastaegert lukastaegert force-pushed the module-info-exports branch 2 times, most recently from c2f0e1c to 7466889 Compare January 22, 2022 06:00
Base automatically changed from module-info-exports to master January 22, 2022 06:21
@lukastaegert lukastaegert merged commit c568a10 into master Jan 22, 2022
@lukastaegert lukastaegert deleted the this-load-resolve-dependencies branch January 22, 2022 06:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant