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

Break infinite loops in this.resolve #4000

Merged
merged 5 commits into from Mar 19, 2021
Merged

Conversation

lukastaegert
Copy link
Member

@lukastaegert lukastaegert commented Mar 17, 2021

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
  • technically yes, but I would consider the previous behaviour as very hard to handle anyway and would push this in a minor release

List any relevant issue numbers:

Description

This will change the logic when using skipSelf in this.resolve. With this patch, this will not only skip the current plugin for this call of this.resolve but when one of the other plugins calls this.resolve again from its resolveId hook with the exact same source and importer, the initial plugin will be skipped as well.

Example:

plugins: [
  {
    name: 'first',
    async resolveId(source, importer) {
      const resolved = await this.resolve(source, importer, {skipSelf: true});
      if (resolved.id === 'foo1') return 'bar1';
    }
  },
  {
    name: 'second',
    async resolveId(source, importer) {
      const resolved = await this.resolve(source, importer, {skipSelf: true});
      if (resolved.id === 'foo2') return 'bar2';
    }
  }
]

What would happen without this patch is that when a pair of (source, importer) is resolved, it is passed to the first plugin, which calls this.resolve to pass it to the second plugin, which again calls this.resolve to pass it back to the first plugin.

With this patch, the pair of (source, importer) is passed to the first plugin, which calls this.resolve to pass it to the second plugin (the first is skipped by skipSelf) and core. As the call of this.resolve in the second plugin uses the same source and importer as the call the first plugin wanted skipped, this is skipping now both plugins, passing it directly to Rollup core.

@rollup-bot
Copy link
Collaborator

rollup-bot commented Mar 17, 2021

Thank you for your contribution! ❤️

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

npm install rollup/rollup#break-infinite-this-resolve

or load it into the REPL:
https://rollupjs.org/repl/?circleci=14601

@codecov
Copy link

codecov bot commented Mar 17, 2021

Codecov Report

Merging #4000 (cfb9910) into master (e2ae914) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #4000   +/-   ##
=======================================
  Coverage   97.24%   97.24%           
=======================================
  Files         191      192    +1     
  Lines        6742     6755   +13     
  Branches     1968     1971    +3     
=======================================
+ Hits         6556     6569   +13     
  Misses         99       99           
  Partials       87       87           
Impacted Files Coverage Δ
src/ModuleLoader.ts 100.00% <100.00%> (ø)
src/utils/PluginContext.ts 100.00% <100.00%> (ø)
src/utils/PluginDriver.ts 100.00% <100.00%> (ø)
src/utils/resolveId.ts 94.73% <100.00%> (ø)
src/utils/resolveIdViaPlugins.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 e2ae914...cfb9910. Read the comment docs.

{
name: 'second',
async resolveId(source, importer) {
const { id } = await this.resolve(source, importer, { skipSelf: true });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like at the moment the source/importer never change, so the skip logic being scoped to the source/importer combo might not have coverage?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cheated a little here. The secret sauce is in the lines below "to make this more interesting". But maybe I should actually assert the return values.

@@ -697,7 +697,7 @@ Use Rollup's internal acorn instance to parse code to an AST.
#### `this.resolve(source: string, importer?: string, options?: {skipSelf?: boolean, custom?: {[plugin: string]: any}}) => Promise<{id: string, external: boolean, moduleSideEffects: boolean | 'no-treeshake', syntheticNamedExports: boolean | string, meta: {[plugin: string]: any}} | null>`
Resolve imports to module ids (i.e. file names) using the same plugins that Rollup uses, and determine if an import should be external. If `null` is returned, the import could not be resolved by Rollup or any plugin but was not explicitly marked as external by the user.

If you pass `skipSelf: true`, then the `resolveId` hook of the plugin from which `this.resolve` is called will be skipped when resolving.
If you pass `skipSelf: true`, then the `resolveId` hook of the plugin from which `this.resolve` is called will be skipped when resolving. When other plugins themselves also call `this.resolve` in their `resolveId` hooks with the *exact same `source` and `importer`* while handling the original `this.resolve` call, then the `resolveId` hook of the original plugin will be skipped for those calls as well. The rationale here is that the plugin already stated that it "does not know" how to resolve this particular combination of `source` and `importer` at this point in time. If you do not want this behaviour, do not use `skipSelf` but implement your own infinite loop prevention mechanism if necessary.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 still pretty confusing, but I'm not sure how it could be any clearer 😆

Does feel like it fits the responsibility of skipSelf.

@lukastaegert lukastaegert merged commit d3e6e6d into master Mar 19, 2021
@lukastaegert lukastaegert deleted the break-infinite-this-resolve branch March 19, 2021 05:37
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

3 participants