Skip to content

Angular Vite: Resolve global styles against the workspace root - #35780

Closed
fALKENdk wants to merge 1 commit into
storybookjs:nextfrom
fALKENdk:fix/angular-vite-styles-workspace-root
Closed

fALKENdk wants to merge 1 commit into
storybookjs:nextfrom
fALKENdk:fix/angular-vite-styles-workspace-root

Conversation

@fALKENdk

@fALKENdk fALKENdk commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Closes #

What I did

angularOptionsPlugin resolves two path options, and it was using a different root for each one.

stylePreprocessorOptions.includePaths uses
angularBuilderContext?.workspaceRoot ?? userConfig?.root ?? process.cwd(), which is right. The
styles transform a few lines further down used resolvedConfig?.root ?? process.cwd(). That is
the same Vite config object, but it skips the builder context. So when a builder context is
present, SCSS include paths resolved against the workspace root while global styles resolved
against the project directory.

In any workspace where those two are not the same directory, the configured stylesheet never
loads. Nothing fails, the styles are just missing.

There were three problems in that transform:

  1. The root was wrong. styles entries are workspace-relative, same as in angular.json.
  2. The expanded form threw. The schema allows { input, bundleName, inject }, but entries were
    pushed through as-is and then called with .startsWith(...), so an object entry gave
    TypeError: extraImport.startsWith is not a function.
  3. The test for "is this a path or a package name" was startsWith('.') || startsWith('src').
    That gets both directions wrong. theme/global.css came out as a bare import and never
    resolved, and srcery/index.css was treated as a path.

The transform now turns every entry into its object form, resolves all of them against
workspaceRoot, and drops the guessing. The schema already tells us what each entry is, so there
is nothing to infer. Entries with inject: false are skipped, because Angular emits those as
standalone bundles instead of injecting them into the page. zone.js is appended as its own
import rather than going into the same list and relying on the old check to leave it alone.

workspaceRoot is now worked out once in config() and shared, which is what stops the two
options from disagreeing again.

Behaviour change

If you are in a multi-project workspace and worked around the old behaviour by writing
project-relative styles paths, those need to become workspace-relative, matching the schema,
angular.json, and stylePreprocessorOptions.includePaths. Single-project workspaces are not
affected, because there the two roots are the same directory.

Checklist for Contributors

Testing

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

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

Seven cases added to preset.test.ts: workspace-relative paths, src-prefixed and dot-relative
paths, the expanded { input } form, inject: false, the fallback to the Vite root when there is
no builder context, zone.js only showing up when not zoneless, and modules other than the preview
being left alone. They fail on the old code.

Manual testing

  1. yarn task --task sandbox --start-from auto --template angular-vite/default-ts
  2. In the sandbox, make the project root different from the workspace root: set
    "root": "projects/app" in angular.json, move src/ to projects/app/src/, and update
    sourceRoot.
  3. Add theme/global.css at the workspace root with body { background: deeppink }.
  4. Add "styles": ["theme/global.css"] to the storybook target.
  5. ng run app:storybook

On the old code the background does not change. The entry starts with neither . nor src, so it
comes out as import 'theme/global.css' and never resolves. With this change the background is
pink.

Then change the entry to [{ "input": "theme/global.css" }] and run it again. On the old code that
throws TypeError: extraImport.startsWith is not a function. With this change it does the same
thing as the string form.

Documentation

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

@github-actions

github-actions Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor
Fails
🚫

PR is not labeled with one of: ["ci:normal","ci:merged","ci:daily","ci:docs"]

🚫

PR is not labeled with one of: ["qa:needed","qa:skip","qa:success"]

🚫 This PR needs an approving review from a Storybook Core or Developer Experience team member before it can be merged.

Generated by 🚫 dangerJS against 58de2dd

@coderabbitai

coderabbitai Bot commented Aug 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 98e9c8bf-5657-42fa-bd59-58f8dc41b26d

📥 Commits

Reviewing files that changed from the base of the PR and between c88236e and 58de2dd.

📒 Files selected for processing (2)
  • code/frameworks/angular-vite/src/preset.test.ts
  • code/frameworks/angular-vite/src/preset.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • code/frameworks/angular-vite/src/preset.ts
  • code/frameworks/angular-vite/src/preset.test.ts

Walkthrough

The Angular Vite plugin now derives a workspace root during configuration, resolves SCSS and preview style paths from that root, supports Angular style objects, filters inject: false entries, and conditionally adds zone.js. Tests cover these paths and transformation cases.

Changes

Angular Vite preview style handling

Layer / File(s) Summary
Workspace root and SCSS resolution
code/frameworks/angular-vite/src/preset.ts
angularOptionsPlugin derives the workspace root from builder context, Vite root, or the current directory. SCSS paths use this root.
Preview style transformation and tests
code/frameworks/angular-vite/src/preset.ts, code/frameworks/angular-vite/src/preset.test.ts
Preview styles support string and object entries, exclude inject: false entries, normalize workspace-relative paths, and conditionally include zone.js. Tests cover builder inputs, zoneless mode, path resolution, and non-preview modules.

Possibly related PRs

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
code/frameworks/angular-vite/src/preset.test.ts (1)

15-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a spy mock with behavior configured in beforeEach.

The factory mock bypasses spy: true and does not use vi.mocked(). The helper changes its behavior through shared state. Use a top-level vi.mock(import('storybook/internal/common'), { spy: true }), then configure vi.mocked(findConfigFile) in beforeEach.

  • code/frameworks/angular-vite/src/preset.test.ts#L15-L19: Replace the factory mock with a spy mock.
  • code/frameworks/angular-vite/src/preset.test.ts#L158-L179: Set and reset findConfigFile mock behavior in beforeEach and afterEach.

As per coding guidelines, use vi.mock() with the spy: true option, use vi.mocked(), and implement mock behaviors in beforeEach blocks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@code/frameworks/angular-vite/src/preset.test.ts` around lines 15 - 19,
Replace the factory mock in code/frameworks/angular-vite/src/preset.test.ts
lines 15-19 with a top-level spy mock for storybook/internal/common. In the
beforeEach block at lines 158-179, configure vi.mocked(findConfigFile) with the
required previewPath behavior, and reset or restore that mock in afterEach;
remove the shared mocks state and use vi.mocked() consistently.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/frameworks/angular-vite/src/preset.test.ts`:
- Around line 187-189: Update the expected global-style import strings in the
angularOptionsPlugin tests to wrap each resolve(WORKSPACE_ROOT, ...) result with
normalizePath, covering the indicated assertions. Keep the existing import paths
and expectations unchanged apart from applying the same normalization used by
the implementation.

---

Nitpick comments:
In `@code/frameworks/angular-vite/src/preset.test.ts`:
- Around line 15-19: Replace the factory mock in
code/frameworks/angular-vite/src/preset.test.ts lines 15-19 with a top-level spy
mock for storybook/internal/common. In the beforeEach block at lines 158-179,
configure vi.mocked(findConfigFile) with the required previewPath behavior, and
reset or restore that mock in afterEach; remove the shared mocks state and use
vi.mocked() consistently.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 69dca22e-e4de-4310-8a67-6e0d1a56831c

📥 Commits

Reviewing files that changed from the base of the PR and between c88236e and b374582.

📒 Files selected for processing (2)
  • code/frameworks/angular-vite/src/preset.test.ts
  • code/frameworks/angular-vite/src/preset.ts

Comment thread code/frameworks/angular-vite/src/preset.test.ts Outdated
@valentinpalkovic valentinpalkovic self-assigned this Aug 6, 2026
@fALKENdk
fALKENdk force-pushed the fix/angular-vite-styles-workspace-root branch from b374582 to 58de2dd Compare August 6, 2026 07:38
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@ndelangen

Copy link
Copy Markdown
Member

Closing this as superseded.

The bug was real: styles resolved against the Vite project root, and the . / src check treated real paths as packages. That already landed in #35974. #35998 then had to walk it back a bit, because joining every entry onto the workspace root breaks package specifiers like dso-toolkit/dist/dso.css. Angular tries the file on disk first, then node_modules.

This PR still does the unconditional join, so merging it would undo #35998.

Object { input } entries are already handled on next. The one bit this has that we still don't is skipping inject: false. If that still matters, a small follow-up on current next is the right shape. Not this diff.

@ndelangen ndelangen closed this Aug 24, 2026
@github-project-automation github-project-automation Bot moved this from Ready for work to Done in Core Team Projects Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants