Skip to content

Conversation

@JReinhold
Copy link
Contributor

@JReinhold JReinhold commented Oct 6, 2025

What I did

SB 10 tries to do as little as possible when loading presets. This requires that users write Node-compliant code as much as possible, including having explicit extensions when importing other files, as has always been the requirement with pure Node. However many users relied on our previous preset loading mechanism previously, that used a bundler to support extensionless imports.

This PR adds support for extensionless imports in TS-based preset files.

It includes a warning to the user, prompting them to add an explicit extenion for future compatibility.

This does not solve the case for JS-based files, as we don't preprocess these at all. We could start doing that, but that would be a big performance penalty as we would also be preprocessing all files within imported packages, etc. so I'd rather not do this. Therefore I've added a migration note instead.

Hopefully we can rely on Node's type stripping in the future and not do any preprocessing at all - this is why we want to warn users today and start migrating them over to explicit extensions.

Checklist for Contributors

Testing

  1. Apply the below diff in the monorepo by copying it and running pbpaste | git apply:
diff --git a/code/.storybook/deep.ts b/code/.storybook/deep.ts
new file mode 100644
index 00000000000..b5ea9cebb9d
--- /dev/null
+++ b/code/.storybook/deep.ts
@@ -0,0 +1,3 @@
+export const bar = 'bar';
+
+console.log('deep');
diff --git a/code/.storybook/main.ts b/code/.storybook/main.ts
index 5b1be319141..c5f836e6766 100644
--- a/code/.storybook/main.ts
+++ b/code/.storybook/main.ts
@@ -6,6 +6,7 @@ import { defineMain } from '@storybook/react-vite/node';
 import react from '@vitejs/plugin-react';
 
 import { BROWSER_TARGETS } from '../core/src/shared/constants/environments-support.ts';
+import { foo } from './test-import';
 
 const currentFilePath = fileURLToPath(import.meta.url);
 const currentDirPath = dirname(currentFilePath);
@@ -13,7 +14,7 @@ const currentDirPath = dirname(currentFilePath);
 const componentsPath = join(currentDirPath, '../core/src/components/index.ts');
 const managerApiPath = join(currentDirPath, '../core/src/manager-api/index.mock.ts');
 const imageContextPath = join(currentDirPath, '../frameworks/nextjs/src/image-context.ts');
-
+console.log(foo);
 const config = defineMain({
   stories: [
     './bench/*.stories.@(js|jsx|ts|tsx)',
diff --git a/code/.storybook/test-import.ts b/code/.storybook/test-import.ts
new file mode 100644
index 00000000000..833d627445b
--- /dev/null
+++ b/code/.storybook/test-import.ts
@@ -0,0 +1,4 @@
+import { bar } from './deep';
+
+export const foo = 'foo';
+console.log(bar);
  1. Run yarn storybook:ui and verify that it works and shows warnings

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features

    • Relative imports are now rewritten to include explicit file extensions, improving ESM compatibility and adding deprecation warnings for extensionless imports.
  • Documentation

    • Contributor guidelines expanded with code quality, testing, and logging guidance.
    • Migration notes: JS preset/config files must use explicit relative import extensions.
  • Tests

    • Added comprehensive tests covering resolution and extension-appending across import/export and dynamic import scenarios.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 6, 2025

📝 Walkthrough

Walkthrough

Adds extension resolution and rewriting for relative imports: introduces supportedExtensions, resolveWithExtension, and addExtensionsToRelativeImports; integrates rewriting into the TS→ESM loader output; adds tests for these functions; updates contributor Copilot instructions and migration notes about required explicit extensions in JS preset/config files.

Changes

Cohort / File(s) Summary
Loader core changes
code/core/src/bin/loader.ts
Adds supportedExtensions and exported functions resolveWithExtension(importPath, currentFilePath) and addExtensionsToRelativeImports(source, filePath); rewrites transformed TS→ESM output to include explicit extensions for relative imports; emits deprecation warnings when resolving extensionless imports.
Loader tests
code/core/src/bin/loader.test.ts
New comprehensive test suite for resolveWithExtension and addExtensionsToRelativeImports; mocks filesystem and logging; verifies resolution behavior for .ts/.js, deprecation messages, handling of static/dynamic imports/exports, comments, and multi-statement files.
Docs: Copilot instructions
.github/copilot-instructions.md
Appends Code Quality Checks, Testing Guidelines, and Logging sections to contributor instructions; includes a minor end-of-file formatting tweak.
Migration notes
MIGRATION.md
Adds note that JS-based preset/config files must use explicit extensions for relative imports, documents partial TypeScript compatibility, and aligns guidance with Node ESM requirements.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Loader as Loader entry
  participant TS as TS transformer
  participant Rewriter as addExtensionsToRelativeImports
  participant Resolver as resolveWithExtension
  participant FS as FileSystem (existsSync)
  participant Log as Logger

  Loader->>TS: transform(source, filePath)
  TS-->>Loader: transformedSource
  Loader->>Rewriter: addExtensionsToRelativeImports(transformedSource.code, filePath)
  loop for each relative import/export
    Rewriter->>Resolver: resolveWithExtension(importPath, filePath)
    alt import already has extension
      Resolver-->>Rewriter: original importPath
    else try supported extensions
      Resolver->>FS: existsSync(candidatePath)?
      alt found
        FS-->>Resolver: true
        Resolver->>Log: warn (deprecation + resolved path)
        Resolver-->>Rewriter: importPath + ext
      else not found
        FS-->>Resolver: false
        Resolver->>Log: warn (unresolved)
        Resolver-->>Rewriter: original importPath
      end
    end
  end
  Rewriter-->>Loader: sourceWithExtensions
  Loader-->>Loader: return result (code = sourceWithExtensions)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jeppe/support-extensionless-imports

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 38b157d and c0224d3.

📒 Files selected for processing (3)
  • .github/copilot-instructions.md (2 hunks)
  • code/core/src/bin/loader.test.ts (1 hunks)
  • code/core/src/bin/loader.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
code/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx

Files:

  • code/core/src/bin/loader.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)

**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together

Files:

  • code/core/src/bin/loader.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Adhere to ESLint and Prettier rules across all JS/TS source files

Files:

  • code/core/src/bin/loader.test.ts
  • code/core/src/bin/loader.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Fix type errors and prefer precise typings instead of using any or suppressions, consistent with strict mode

Files:

  • code/core/src/bin/loader.test.ts
  • code/core/src/bin/loader.ts
🧬 Code graph analysis (1)
code/core/src/bin/loader.test.ts (1)
code/core/src/bin/loader.ts (2)
  • resolveWithExtension (34-62)
  • addExtensionsToRelativeImports (68-90)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Core Unit Tests, windows-latest

@nx-cloud
Copy link

nx-cloud bot commented Oct 6, 2025

View your CI Pipeline Execution ↗ for commit 6f3f65f

Command Status Duration Result
nx run-many -t build --parallel=3 ✅ Succeeded 48s View ↗

☁️ Nx Cloud last updated this comment at 2025-10-10 07:44:41 UTC

@storybook-app-bot
Copy link

storybook-app-bot bot commented Oct 6, 2025

Package Benchmarks

Commit: 6f3f65f, ran on 10 October 2025 at 07:29:10 UTC

No significant changes detected, all good. 👏

@ndelangen ndelangen self-requested a review October 6, 2025 08:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (2)
code/core/src/bin/loader.ts (1)

19-28: Prefer TypeScript extensions before JavaScript when resolving.

This issue was already flagged in a previous review. When both .ts and .js files exist side by side (common in preset repos), resolving .js first will bypass the loader and execute the potentially stale compiled artifact. The order should prioritize TypeScript extensions first, then fall back to JavaScript if no TS file is found.

code/core/src/bin/loader.test.ts (1)

13-13: Reset mocks between tests to keep assertions accurate.

This issue was already flagged in a previous review. The mock call history is retained between tests, which will cause later assertions expecting zero calls to fail. Add an afterEach(() => vi.clearAllMocks()) after the describe block starts.

🧹 Nitpick comments (2)
code/core/src/bin/loader.ts (1)

40-44: Consider deferring deprecation until a file is found.

The deprecation message appears even when the file cannot be resolved, which may confuse users (they see a warning about an import that will fail anyway). Consider moving the deprecate call inside the loop when a file is actually found, or structuring the message to distinguish "resolved with extension" from "unresolvable."

code/core/src/bin/loader.test.ts (1)

186-248: Consider adding test for imports inside template literals.

While the test at lines 242-247 covers comments, there's no test verifying that imports inside template literals or strings are not modified. This could be a useful edge case to validate.

Example test case:

it('should not modify imports inside template literals', () => {
  const source = `const code = \`import foo from './utils';\`;`;
  const result = addExtensionsToRelativeImports(source, '/project/src/file.ts');
  
  // Should not be modified since it's inside a template literal
  expect(result).toBe(source);
});
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 522d5f1 and 1b74b96.

📒 Files selected for processing (2)
  • code/core/src/bin/loader.test.ts (1 hunks)
  • code/core/src/bin/loader.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Adhere to ESLint and Prettier rules across all JS/TS source files

Files:

  • code/core/src/bin/loader.ts
  • code/core/src/bin/loader.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Fix type errors and prefer precise typings instead of using any or suppressions, consistent with strict mode

Files:

  • code/core/src/bin/loader.ts
  • code/core/src/bin/loader.test.ts
code/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx

Files:

  • code/core/src/bin/loader.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)

**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together

Files:

  • code/core/src/bin/loader.test.ts
🧠 Learnings (8)
📚 Learning: 2025-09-25T09:21:27.284Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-25T09:21:27.284Z
Learning: Applies to **/*.{ts,tsx} : Fix type errors and prefer precise typings instead of using any or suppressions, consistent with strict mode

Applied to files:

  • code/core/src/bin/loader.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid direct function mocking without vi.mocked()

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mock implementations outside of beforeEach blocks

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock implementations should be placed in beforeEach blocks

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid inline mock implementations within test cases

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to access and implement mock behaviors

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.197Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.197Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Keep mock implementations simple and focused

Applied to files:

  • code/core/src/bin/loader.test.ts
🧬 Code graph analysis (1)
code/core/src/bin/loader.test.ts (1)
code/core/src/bin/loader.ts (2)
  • resolveWithExtension (34-58)
  • addExtensionsToRelativeImports (64-86)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (3)
code/core/src/bin/loader.ts (1)

98-114: LGTM!

The loader integration correctly applies extension rewriting after esbuild transformation, ensuring that the final ESM output has explicit extensions for Node.js compatibility.

code/core/src/bin/loader.test.ts (2)

91-97: LGTM!

The beforeEach block properly sets up the default mock behavior for the test suite, following the coding guidelines.


99-184: LGTM!

These test cases properly rely on the mock setup in beforeEach and cover a comprehensive range of import scenarios. The test coverage is thorough and well-organized.

Comment on lines +277 to +286
it('should handle multi-line dynamic imports', () => {
const source = `const module = await import(
'./utils'
);`;
const result = addExtensionsToRelativeImports(source, '/project/src/file.ts');

expect(result).toBe(`const module = await import(
'./utils.ts'
);`);
});
Copy link
Member

@ndelangen ndelangen Oct 6, 2025

Choose a reason for hiding this comment

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

Should there be a little bit more coverage on off patterns with import()?

import(`./foo`); // backticks

import('./foo' + foo);

import(foo + 'bar');

import(`${foo}/bar`);

import(`./${foo}/bar`);

const [] = [
  import('./foo'),
  import('./bar'),
]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added support for backticks, but supporting interpolation with runtime values doesn't make much sense, since we'd now have to start looking up those runtime values - probably with AST parsing - do figure out what the final file path is to lookup. that's not worth it imo.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (1)
code/core/src/bin/loader.ts (1)

66-85: Multiline import statements will not be rewritten.

As previously noted, the regex patterns do not match multiline import/export statements because [^'"]+ and [^$]+don't match newlines. Real examples exist in the codebase (e.g.,code/addons/vitest/src/preset.ts`). While single-line imports will work, multiline imports will remain extensionless and may fail at runtime in Node.js ESM.

🧹 Nitpick comments (1)
code/core/src/bin/loader.ts (1)

40-44: Consider deduplicating deprecation warnings.

The deprecation message is emitted every time resolveWithExtension encounters an extensionless import. In large preset configurations or when the same import appears multiple times, this could result in significant log spam. Consider caching which import paths have already triggered a warning (e.g., using a Set) to emit each unique deprecation only once.

Example implementation:

+const warnedImports = new Set<string>();
+
 export function resolveWithExtension(importPath: string, currentFilePath: string): string {
   // If the import already has an extension, return it as-is
   if (path.extname(importPath)) {
     return importPath;
   }
 
-  deprecate(dedent`One or more extensionless imports detected: "${importPath}" in file "${currentFilePath}".
+  const cacheKey = `${currentFilePath}:${importPath}`;
+  if (!warnedImports.has(cacheKey)) {
+    warnedImports.add(cacheKey);
+    deprecate(dedent`One or more extensionless imports detected: "${importPath}" in file "${currentFilePath}".
     For maximum compatibility, you should add an explicit file extension to this import.
     Storybook will attempt to resolve it automatically, but this may change in the future.
     If adding the extension results in an error from TypeScript, we recommend setting moduleResolution to "bundler" in tsconfig.json
     or alternatively look into the allowImportingTsExtensions option.`);
+  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf66625 and 21e0bcd.

📒 Files selected for processing (2)
  • code/core/src/bin/loader.test.ts (1 hunks)
  • code/core/src/bin/loader.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/bin/loader.test.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Adhere to ESLint and Prettier rules across all JS/TS source files

Files:

  • code/core/src/bin/loader.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Fix type errors and prefer precise typings instead of using any or suppressions, consistent with strict mode

Files:

  • code/core/src/bin/loader.ts
🧠 Learnings (1)
📚 Learning: 2025-09-25T09:21:27.284Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-09-25T09:21:27.284Z
Learning: Applies to **/*.{ts,tsx} : Fix type errors and prefer precise typings instead of using any or suppressions, consistent with strict mode

Applied to files:

  • code/core/src/bin/loader.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (1)
code/core/src/bin/loader.ts (1)

100-115: LGTM!

The integration correctly applies extension rewriting after esbuild transformation, ensuring that the rewriting operates on ESM JavaScript output rather than TypeScript input. The sequence (read → transform → rewrite → return) is appropriate for this use case.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
code/core/src/bin/loader.test.ts (3)

11-12: Add spy: true option to vi.mock() calls.

The coding guidelines require using vi.mock() with the spy: true option for all package and file mocks in Vitest tests.

As per coding guidelines

Apply this diff:

-vi.mock('node:fs');
-vi.mock('storybook/internal/node-logger');
+vi.mock('node:fs', { spy: true });
+vi.mock('storybook/internal/node-logger', { spy: true });

14-14: Add afterEach to reset mocks between tests.

Without resetting mocks between tests, later specs expecting zero calls to logger.warn will fail because the mock retains invocations from previous tests.

As per coding guidelines

Apply this diff to add mock reset after the vi.mock calls:

 vi.mock('node:fs');
 vi.mock('storybook/internal/node-logger');

+afterEach(() => {
+  vi.clearAllMocks();
+});
+
 describe('loader', () => {

23-99: Move mock implementations to beforeEach blocks.

Multiple tests in the resolveWithExtension describe block have inline mock implementations (lines 27-29, 48-50, 61, 74, 88-90), which violates the coding guidelines. Mock implementations should be placed in beforeEach blocks for consistency and maintainability.

As per coding guidelines

Note that the addExtensionsToRelativeImports tests (lines 101-351) already follow this pattern correctly with a beforeEach block at lines 102-107.

🧹 Nitpick comments (1)
code/core/src/bin/loader.test.ts (1)

120-124: Consider using it.each for parameterized tests.

The forEach pattern works, but Vitest's it.each provides better test isolation and clearer test output when failures occur.

Optional refactor:

-    it('should not modify imports that already have extensions', () => {
-      const testCases = [
+    it.each([
         { input: `import foo from './test.js';`, expected: `import foo from './test.js';` },
         { input: `import foo from './test.ts';`, expected: `import foo from './test.ts';` },
         { input: `import foo from '../utils.mjs';`, expected: `import foo from '../utils.mjs';` },
         {
           input: `export { bar } from './module.tsx';`,
           expected: `export { bar } from './module.tsx';`,
         },
-      ];
-
-      testCases.forEach(({ input, expected }) => {
+    ])('should not modify imports that already have extensions: $input', ({ input, expected }) => {
         const result = addExtensionsToRelativeImports(input, '/project/src/file.ts');
         expect(result).toBe(expected);
         expect(deprecate).not.toHaveBeenCalled();
-      });
     });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21e0bcd and 6f3f65f.

📒 Files selected for processing (1)
  • code/core/src/bin/loader.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
code/**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

code/**/*.{test,spec}.{ts,tsx}: Place all test files under the code/ directory
Name test files as *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx

Files:

  • code/core/src/bin/loader.test.ts
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)

**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access mocked functions
Implement mock behaviors in beforeEach blocks
Mock all required dependencies that the test subject uses
Mock implementations should be placed in beforeEach blocks
Each mock implementation should return a Promise for async functions
Mock implementations should match the expected return type of the original function
Use vi.mocked() to access and implement mock behaviors
Mock all required properties and methods that the test subject uses
Avoid direct function mocking without vi.mocked()
Avoid mock implementations outside of beforeEach blocks
Avoid mocking without the spy: true option
Avoid inline mock implementations within test cases
Avoid mocking only a subset of required dependencies
Mock at the highest level of abstraction needed
Keep mock implementations simple and focused
Use type-safe mocking with vi.mocked()
Document complex mock behaviors
Group related mocks together

Files:

  • code/core/src/bin/loader.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Adhere to ESLint and Prettier rules across all JS/TS source files

Files:

  • code/core/src/bin/loader.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Fix type errors and prefer precise typings instead of using any or suppressions, consistent with strict mode

Files:

  • code/core/src/bin/loader.test.ts
🧠 Learnings (14)
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid direct function mocking without vi.mocked()

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mock implementations outside of beforeEach blocks

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Mock implementations should be placed in beforeEach blocks

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid inline mock implementations within test cases

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to access and implement mock behaviors

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.197Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.197Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Keep mock implementations simple and focused

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking without the spy: true option

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.197Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.197Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use type-safe mocking with vi.mocked()

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use vi.mocked() to type and access mocked functions

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Avoid mocking only a subset of required dependencies

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Place all mocks at the top of the test file before any test cases

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.196Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.196Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Implement mock behaviors in beforeEach blocks

Applied to files:

  • code/core/src/bin/loader.test.ts
📚 Learning: 2025-09-17T08:11:47.197Z
Learnt from: CR
PR: storybookjs/storybook#0
File: .cursor/rules/spy-mocking.mdc:0-0
Timestamp: 2025-09-17T08:11:47.197Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Group related mocks together

Applied to files:

  • code/core/src/bin/loader.test.ts
🧬 Code graph analysis (1)
code/core/src/bin/loader.test.ts (1)
code/core/src/bin/loader.ts (2)
  • resolveWithExtension (34-58)
  • addExtensionsToRelativeImports (64-88)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: normal
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (2)
code/core/src/bin/loader.test.ts (2)

102-107: LGTM! Good use of beforeEach for default mock setup.

This beforeEach correctly establishes a default mock behavior (all .ts files exist) for the test suite, allowing individual tests to focus on specific scenarios. This follows the coding guidelines for mock setup in Vitest tests.


109-351: Excellent test coverage.

The test suite comprehensively covers various import/export patterns including static imports, dynamic imports, different quote styles, multi-line imports, template literals, and edge cases. The tests validate both transformation scenarios and cases where imports should remain unchanged (absolute imports, existing extensions, template interpolation).

@JReinhold JReinhold merged commit 5a3dbff into next Oct 13, 2025
57 of 58 checks passed
@JReinhold JReinhold deleted the jeppe/support-extensionless-imports branch October 13, 2025 13:31
@github-actions github-actions bot mentioned this pull request Oct 14, 2025
13 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants