Skip to content

fix(vite-plugin-angular): hash JIT inline style virtual module ids - #2470

Merged
brandonroberts merged 2 commits into
betafrom
fix/2459-jit-inline-style-id-length
Aug 7, 2026
Merged

fix(vite-plugin-angular): hash JIT inline style virtual module ids#2470
brandonroberts merged 2 commits into
betafrom
fix/2459-jit-inline-style-id-length

Conversation

@brandonroberts

Copy link
Copy Markdown
Member

PR Checklist

In JIT mode, Angular's resource transformer emits inline component styles as angular:jit:style:inline;<base64 of the whole stylesheet>. The plugin rewrote only the prefix, leaving the full base64 in the module id. The bundler derives the emitted chunk name from that id, so a large inline stylesheet pushed the filename past the 255-byte limit and the build failed with ENAMETOOLONG / File name too long (os error 63).

Closes #2459

Affected scope

  • Primary scope: vite-plugin-angular
  • Secondary scopes: none

Recommended merge strategy for maintainer [optional]

  • Squash merge
  • Rebase merge
  • Other

What is the new behavior?

The base64 payload is hashed to 16 chars at rewrite time and kept in a lookup table (utils/jit-inline-styles.ts) that the JIT plugin's load hook reads back, so the virtual module id is a fixed length regardless of stylesheet size. The JIT plugin no longer re-hashes for the preprocessCSS filename — the id it receives is already bounded.

Emitted chunk before: _virtual_angular_jit_style_inline_<entire base64>.js
Emitted chunk after: _virtual_angular_jit_style_inline_db5785c80e45692c-Np9tqdeL.js

Test plan

  • nx format:check
  • pnpm build (nx build vite-plugin-angular)
  • pnpm test (nx test vite-plugin-angular — 39 files / 1238 tests pass, incl. new jit-inline-styles.spec.ts)
  • Manual verification

Manual verification used a minimal JIT app (Angular 22, Vite 8 / rolldown) with a ~20KB inline styles block and preserveModules so each module gets its own chunk name:

  • Before the fix: vite build fails with File name too long (os error 63).
  • After the fix: build succeeds, the emitted chunk name is bounded, and the full stylesheet is present in the output.
  • Dev server: the component module imports /@id/__x00__virtual:angular:jit:style:inline;db5785c80e45692c, which serves the complete stylesheet.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

The reported workaround (build.rollupOptions.output.chunkFileNames) is no longer needed. The fast-compile JIT path is unaffected — it keeps inline styles in the decorator metadata and never creates a virtual module for them.

🤖 Generated with Claude Code

Angular's JIT resource transformer emits inline component styles as
`angular:jit:style:inline;<base64 of the whole stylesheet>`. The plugin
rewrote only the prefix, so the full base64 stayed in the module id and
the bundler derived the emitted chunk name from it — a large inline
stylesheet pushed the filename past the 255-byte limit and the build
died with ENAMETOOLONG.

Hash the payload into the id at rewrite time and keep the base64 in a
lookup table the JIT plugin's `load` reads back, so the id stays a fixed
length regardless of stylesheet size.

Closes #2459

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@netlify

netlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit 25522e6
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/6a761a73b7e1f9000886668c
😎 Deploy Preview https://deploy-preview-2470--analog-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit 25522e6
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/6a761a73f6f249000847e023
😎 Deploy Preview https://deploy-preview-2470--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify

netlify Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit 25522e6
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/6a761a7304fa000008dc7dca
😎 Deploy Preview https://deploy-preview-2470--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular label Aug 7, 2026
@nx-cloud

nx-cloud Bot commented Aug 7, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 25522e6

Command Status Duration Result
nx run-many --target e2e --projects=analog-app-... ✅ Succeeded <1s View ↗
nx run-many --target test ✅ Succeeded 24s View ↗
nx run-many --target build --all --parallel=1 -... ✅ Succeeded 3m 10s View ↗
nx run-many --target lint --all --exclude=conte... ✅ Succeeded <1s View ↗
nx-cloud record -- pnpm prettier:check ✅ Succeeded 9s View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-07 17:53:30 UTC

@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 687ab95a-90b5-4be3-8470-b881dd5c8563

📥 Commits

Reviewing files that changed from the base of the PR and between b4ad6ef and 25522e6.

⛔ Files ignored due to path filters (1)
  • .agents/skills/handhold-pr/SKILL.md is excluded by none and included by none
📒 Files selected for processing (1)
  • AGENTS.md

📝 Walkthrough

Walkthrough

The change adds shared utilities that map encoded JIT inline styles to truncated SHA-256 virtual module IDs. The Vite plugin uses these IDs when rewriting inline stylesheet references. The JIT loader extracts the hash, retrieves the encoded stylesheet, and preprocesses it. Tests cover ID length, successful lookup, and unknown hashes. The agent skills list also gains a handhold-pr entry.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning AGENTS.md adds an unrelated handhold-pr skill entry that is outside the linked issue's vite-plugin-angular fix. Remove the unrelated AGENTS.md change or link it to a separate issue before merging.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title uses the supported vite-plugin-angular scope and accurately describes hashing JIT inline style virtual module IDs.
Description check ✅ Passed The description clearly explains the ENAMETOOLONG issue, the implementation, affected scope, and verification results.
Linked Issues check ✅ Passed The changes hash inline style IDs, retain a hash-to-style lookup, resolve styles during load, and add focused tests for issue #2459.

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

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts`:
- Around line 767-772: Update the JIT inline-style rewrite in
packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts lines 767-772 to
allow percent-encoded characters in the payload match, ensuring
toJitInlineStyleId hashes the complete URI-escaped stylesheet. Add a regression
test in packages/vite-plugin-angular/src/lib/utils/jit-inline-styles.spec.ts
lines 10-28 covering a payload containing %3D, %2B, or %2F through the rewrite
and loader flow; both sites require changes.

In `@packages/vite-plugin-angular/src/lib/utils/jit-inline-styles.spec.ts`:
- Around line 10-28: Extend the JIT inline style tests to cover URI-escaped
Base64 payloads rather than only raw encoded styles. Exercise the escaped
payload through the rewrite and loader path in angular-jit-plugin.ts, then
verify it produces the expected bounded ID and resolves back to the original
encoded styles using toJitInlineStyleId and getJitInlineStyles.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 81fc7206-452e-4d35-9c9a-d7fe924bc375

📥 Commits

Reviewing files that changed from the base of the PR and between 7b57799 and b4ad6ef.

📒 Files selected for processing (4)
  • packages/vite-plugin-angular/src/lib/angular-jit-plugin.ts
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts
  • packages/vite-plugin-angular/src/lib/utils/jit-inline-styles.spec.ts
  • packages/vite-plugin-angular/src/lib/utils/jit-inline-styles.ts

Comment on lines +767 to +772
// The emitted id carries the base64 of the entire stylesheet,
// and the bundler derives chunk names from it — hash it so a
// large inline style can't produce an over-long filename (#2459).
data = data.replace(
/angular:jit:style:inline;/g,
'virtual:angular:jit:style:inline;',
/angular:jit:style:inline;([A-Za-z0-9+/=]*)/g,
(_match, encodedStyles) => toJitInlineStyleId(encodedStyles),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Handle URI-escaped Base64 in the JIT inline-style contract.

The loader decodes URI-escaped payloads, but the producer regex excludes %. The rewrite can create an ID with a valid hash followed by an unmatched escaped suffix. The lookup then fails and the JIT inline style does not load.

  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts#L767-L772: include % in the payload match so the hash covers the complete encoded stylesheet.
  • packages/vite-plugin-angular/src/lib/utils/jit-inline-styles.spec.ts#L10-L28: add a regression test for %3D, %2B, or %2F through the rewrite and loader flow.

Based on the PR objectives, the rewrite must retain the complete stylesheet payload. As per coding guidelines, “Include tests which validate behavior for any new functionality added.”

📍 Affects 2 files
  • packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts#L767-L772 (this comment)
  • packages/vite-plugin-angular/src/lib/utils/jit-inline-styles.spec.ts#L10-L28
🤖 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 `@packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts` around lines 767
- 772, Update the JIT inline-style rewrite in
packages/vite-plugin-angular/src/lib/angular-vite-plugin.ts lines 767-772 to
allow percent-encoded characters in the payload match, ensuring
toJitInlineStyleId hashes the complete URI-escaped stylesheet. Add a regression
test in packages/vite-plugin-angular/src/lib/utils/jit-inline-styles.spec.ts
lines 10-28 covering a payload containing %3D, %2B, or %2F through the rewrite
and loader flow; both sites require changes.

Source: Coding guidelines

Comment on lines +10 to +28
it('keeps the id bounded for large stylesheets (#2459)', () => {
const encodedStyles = Buffer.from(
`.a { color: red; }`.repeat(1000),
).toString('base64');

const id = toJitInlineStyleId(encodedStyles);

expect(id.startsWith(JIT_INLINE_STYLE_PREFIX)).toBe(true);
expect(id.length).toBeLessThan(255);
});

it('resolves the hashed id back to the encoded styles', () => {
const encodedStyles = Buffer.from(`.b { color: blue; }`).toString('base64');

const id = toJitInlineStyleId(encodedStyles);
const hash = id.split('style:inline;')[1];

expect(getJitInlineStyles(hash)).toBe(encodedStyles);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test the URI-escaped JIT style payload.

Lines 10 through 28 pass raw Base64 to the utility. They do not cover the URI-escaped payload handled by angular-jit-plugin.ts. Add regression coverage that sends an escaped payload through the rewrite and loader path.

As per coding guidelines, “Include tests which validate behavior for any new functionality added.”

🤖 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 `@packages/vite-plugin-angular/src/lib/utils/jit-inline-styles.spec.ts` around
lines 10 - 28, Extend the JIT inline style tests to cover URI-escaped Base64
payloads rather than only raw encoded styles. Exercise the escaped payload
through the rewrite and loader path in angular-jit-plugin.ts, then verify it
produces the expected bounded ID and resolves back to the original encoded
styles using toJitInlineStyleId and getJitInlineStyles.

Source: Coding guidelines

@nx-cloud nx-cloud Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Important

At least one additional CI pipeline execution has run since the conclusion below was written and it may no longer be applicable.

Nx Cloud has identified a possible root cause for your failed CI:

We classified this failure as an environment state issue rather than a code change. The tests-vitest-angular:test task fails because @analogjs/vitest-angular has not been built in this environment, making its sub-path exports (e.g. setup-testbed) unresolvable — this is entirely unrelated to the PR's changes in vite-plugin-angular. Running nx build vitest-angular before the test suite would resolve the failure.

No code changes were suggested for this issue.

Trigger a rerun:

Rerun CI

Nx Cloud View detailed reasoning on Nx Cloud ↗


🎓 Learn more about Self-Healing CI on nx.dev

Captures the flow for taking a PR from failing checks to green: pull the
real CI output (Nx distributed execution hides task logs behind the
summary table that `--log-failed` shows), classify each failure against
the branch diff and the base branch before touching anything, then fix,
re-run once, or escalate.

Documents the two failure modes that read as real but aren't: Nx DTE
ordering flakes on workspace subpath exports, and local-only failures
from a Node version drifting off `.node-version`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the scope:repo Repository metadata and tooling label Aug 7, 2026
@brandonroberts
brandonroberts merged commit dc2216e into beta Aug 7, 2026
20 checks passed
@brandonroberts
brandonroberts deleted the fix/2459-jit-inline-style-id-length branch August 7, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:repo Repository metadata and tooling scope:vite-plugin-angular Changes in @analogjs/vite-plugin-angular

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[vite-plugin-angular] Inline component styles produce unbounded virtual module ids, breaking builds with ENAMETOOLONG

1 participant