Skip to content

fix(css): resolve Sass tsconfig path aliases - #2824

Draft
james-elicx wants to merge 1 commit into
mainfrom
codex/fix-sass-tsconfig-alias
Draft

fix(css): resolve Sass tsconfig path aliases#2824
james-elicx wants to merge 1 commit into
mainfrom
codex/fix-sass-tsconfig-alias

Conversation

@james-elicx

@james-elicx james-elicx commented Aug 6, 2026

Copy link
Copy Markdown
Member

Summary

  • intentionally expose tsconfig path mappings to application Sass as a vinext/Vite migration capability
  • support exact keys, one wildcard anywhere, longest-prefix matching, and ordered target fallbacks
  • keep project mappings out of dependency Sass under node_modules
  • preserve user Sass importer order and precedence, including single-object importer configuration

This is intentionally broader than current Next.js webpack behavior: Next.js does not resolve tsconfig paths from Sass. The implementation is a vinext migration convenience for applications that already share aliases between source modules and stylesheets.

Regression coverage

  • adds a real App Router Sass fixture exercised through Vite dev and a production createBuilder().buildApp() build
  • covers exact fallback targets, a wildcard in the middle of both the key and replacement, longest-prefix selection, paths containing spaces, and external CSS URLs
  • copies fixture packages beneath node_modules and verifies dependency Sass resolves its own package instead of consuming the application alias
  • verifies user importers remain ordered ahead of generated tsconfig importers
  • the fixture fails on unmodified main because Vite does not apply the mappings to Sass

Validation

  • vp test run tests/sass-tsconfig-paths.test.ts tests/sass-options.test.ts tests/tsconfig-path-alias-resolution.test.ts tests/tsconfig-paths-vite8.test.ts (51 passed)
  • focused vp check for all touched source, test, JSON, and Sass files
  • vp run vinext#build

Independent review: clean on exact head c59f0c1e2 after the amended implementation and literal wildcard-capture regression.

@pkg-pr-new

pkg-pr-new Bot commented Aug 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2824
npm i https://pkg.pr.new/create-vinext-app@2824
npm i https://pkg.pr.new/@vinext/types@2824
npm i https://pkg.pr.new/vinext@2824

commit: c59f0c1

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared c59f0c1 against base 3c5cea4 using alternating same-runner rounds. Next.js was unchanged and skipped.

1 improved · 0 regressed · 5 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 134.6 KB 134.6 KB ⚫ -0.0%
Client entry size (gzip) vinext 122.2 KB 122.1 KB ⚫ -0.0%
Dev server cold start vinext 2.99 s 2.90 s 🟢 -3.1%
Production build time vinext 3.23 s 3.22 s ⚫ -0.4%
RSC entry closure size (gzip) vinext 113.9 KB 113.8 KB ⚫ -0.0%
Server bundle size (gzip) vinext 191.4 KB 191.4 KB ⚫ -0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

@james-elicx
james-elicx force-pushed the codex/fix-sass-tsconfig-alias branch from 98bd893 to 625c601 Compare August 6, 2026 10:45
Comment thread packages/vinext/src/plugins/sass.ts Fixed
@james-elicx
james-elicx force-pushed the codex/fix-sass-tsconfig-alias branch from 625c601 to 131eb9c Compare August 6, 2026 10:48
@james-elicx
james-elicx force-pushed the codex/fix-sass-tsconfig-alias branch from 131eb9c to c59f0c1 Compare August 6, 2026 10:49
@james-elicx

Copy link
Copy Markdown
Member Author

/bigbonk review for issues

@ask-bonk ask-bonk 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.

Review: fix(css): resolve Sass tsconfig path aliases

Reviewed head c59f0c1. Ran tests/sass-options.test.ts, tests/sass-tsconfig-paths.test.ts, tests/tsconfig-path-alias-resolution.test.ts, tests/tsconfig-paths-vite8.test.ts (all pass) and vp check on the touched source (clean). The implementation is solid and the regression coverage is thorough. No blocking issues found.

CodeQL alert (sass.ts — "replaces only the first occurrence of *")

This alert appears stale. The current head uses replacement.replaceAll("*", () => matchedStar) (sass.ts:152), not .replace(...), so all * occurrences are replaced. The function-replacer form is also the correct choice — it prevents $&/$1 in matchedStar from being interpreted as replacement patterns, which the test at tests/sass-options.test.ts:355-357 (@palette-$&-tokens) explicitly guards. Recommend dismissing the alert as "won't fix / already addressed" once the branch is re-scanned.

Correctness notes (all fine, documenting the reasoning)

  1. Multiple * in a replacement + replaceAllreplaceAll("*", ...) would substitute every * in a target, but hasZeroOrOneAsterisk (index.ts:804) guarantees each materialized target has at most one * before it reaches the importer, so this is safe in the real code path. The standalone createSassTsconfigPathImporters doesn't re-enforce this, but its only production caller feeds it filtered data.

  2. matchedStar === null implies no literal * in replacement — when find has no star, the (findHasStar || !target.includes("*")) filter (index.ts:834) drops any star-bearing target, so returning replacement verbatim for exact matches is correct.

  3. Longest-prefix wildcard selection (matchSassTsconfigPathAlias, sass.ts:72-93) mirrors TypeScript's findBestPatternMatch (longest prefix wins, first-wins on ties via <=). Suffix length isn't a tie-breaker, which also matches TS.

  4. node_modules isolationisSassAliasEligibleUrl blocks app aliases when containingUrl is under node_modules (NODE_MODULES_PATH_RE correctly matches vendor/node_modules/...). The dependency-tokens fixture verifies a dependency resolves its own package rather than the app alias. When Sass omits containingUrl (top-level entry) the guard correctly allows resolution.

  5. extends merge ordering — the sass merge (index.ts:942,959) uses the same later-overrides-earlier / own-overrides-parent ordering as the existing vite alias merge, keeping the two representations consistent. Note this key-by-key merge across extends is not strictly TypeScript semantics (TS replaces paths wholesale), but it matches vinext's pre-existing vite behavior, so it's a consistent, non-regressing choice rather than a new divergence.

Minor / optional

  • The PR body correctly flags this as intentionally broader than Next.js webpack (Next.js does not resolve tsconfig paths from Sass). Since this is a deliberate vinext-only capability, that divergence is documented in both the code comments (sass.ts:116-133) and the test file header — good adherence to the "document intentional divergences" guidance in AGENTS.md.

LGTM.

@ask-bonk

ask-bonk Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review posted to PR #2824.

Summary

Reviewed PR #2824 (fix(css): resolve Sass tsconfig path aliases) at head c59f0c1. Ran the four relevant test files (all pass) and vp check on both touched source files (clean).

Verdict: LGTM — no blocking issues.

Key findings:

  • CodeQL alert is stale. It complains that the code "replaces only the first occurrence of *", but the current head uses replaceAll("*", () => matchedStar) (sass.ts:152), not .replace(...). The function-replacer form is also correct — it avoids $&/$1 interpretation, which the @palette-$&-tokens test explicitly guards. Recommend re-scan/dismiss.

  • Correctness verified across the trickier paths: hasZeroOrOneAsterisk guarantees at most one * per target so replaceAll is safe; exact-match returns are *-free by construction; longest-prefix wildcard selection matches TypeScript's findBestPatternMatch; node_modules isolation works (including vendor/node_modules); and the extends sass merge mirrors the existing vite merge ordering.

  • Intentional Next.js divergence is properly documented in code comments and test headers, per AGENTS.md guidance.

The regression coverage (dev + buildApp() production build, wildcard-in-middle, longest-prefix, spaces, external URLs, dependency isolation, importer ordering, single-object importer) is thorough.

github run

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.

2 participants