From 1557c8b74a00e23792d30fd76aff7ebf440ef9e6 Mon Sep 17 00:00:00 2001 From: KCM Date: Sun, 21 Dec 2025 14:18:20 -0600 Subject: [PATCH 1/5] feat: use oxc-resolver. --- README.md | 10 +- docs/how-it-works.md | 5 +- package-lock.json | 402 +++++++++++++++++++++- packages/css/package.json | 12 +- packages/css/src/css.ts | 50 +-- packages/css/src/moduleGraph.ts | 548 ++++++++++++++++++++++++++++++ packages/css/src/sassInternals.ts | 18 +- packages/css/src/types.ts | 4 + packages/playwright/package.json | 2 +- 9 files changed, 1003 insertions(+), 48 deletions(-) create mode 100644 packages/css/src/moduleGraph.ts create mode 100644 packages/css/src/types.ts diff --git a/README.md b/README.md index 5d13ddc..0e36eb4 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,8 @@ ## Features -- Traverses module graphs using [`dependency-tree`](https://github.com/dependents/node-dependency-tree) to find transitive style imports. +- Traverses module graphs with a built-in walker to find transitive style imports (no bundler required). +- Resolution parity via [`oxc-resolver`](https://github.com/oxc-project/oxc-resolver): tsconfig `paths`, package `exports` conditions, and extension aliasing (e.g., `.css.js` → `.css.ts`) are honored without wiring up a bundler. - Compiles `*.css`, `*.scss`, `*.sass`, `*.less`, and `*.css.ts` (vanilla-extract) files out of the box. - Optional post-processing via [`lightningcss`](https://github.com/parcel-bundler/lightningcss) for minification, prefixing, media query optimizations, or specificity boosts. - Pluggable resolver/filter hooks for custom module resolution (e.g., Rspack/Vite/webpack aliases) or selective inclusion. @@ -66,10 +67,10 @@ type CssOptions = { strategy?: SpecificityStrategy match?: SpecificitySelector[] } - dependencyTree?: DependencyTreeOptions + moduleGraph?: ModuleGraphOptions resolver?: ( specifier: string, - ctx: { cwd: string }, + ctx: { cwd: string; from?: string }, ) => string | Promise peerResolver?: (name: string) => Promise // for custom module loading } @@ -81,6 +82,7 @@ Typical customizations: - **filter** – Skip certain paths (e.g., storybook-only styles) before compilation. - **resolver** – Resolve virtual specifiers the way your bundler does (the repo ships test fixtures for webpack, Vite, and Rspack). +- **moduleGraph** – Configure tsconfig path aliases, extra script extensions, or custom `package.json` conditions for the built-in dependency walker. - **lightningcss** – Pass `true` for defaults or a config object for minification/autoprefixing. - **specificityBoost** – Provide a Lightning CSS visitor to bump specificity on selected selectors (e.g., duplicate a class for matching selectors). @@ -388,7 +390,7 @@ That hint keeps the upstream CommonJS helpers intact while still letting the res ### Custom resolver (enhanced-resolve example) -If your project uses aliases or nonstandard resolution, plug in a custom resolver. Here’s how to use [`enhanced-resolve`](https://github.com/webpack/enhanced-resolve): +The built-in walker already leans on [`oxc-resolver`](https://github.com/oxc-project/oxc-resolver), so tsconfig `paths`, package `exports` conditions, and common extension aliases work out of the box. If you still need to mirror bespoke behavior (virtual modules, framework-specific loaders, etc.), plug in a custom resolver. Here’s how to use [`enhanced-resolve`](https://github.com/webpack/enhanced-resolve): ```ts import { ResolverFactory } from 'enhanced-resolve' diff --git a/docs/how-it-works.md b/docs/how-it-works.md index 389b5f1..b98c8ff 100644 --- a/docs/how-it-works.md +++ b/docs/how-it-works.md @@ -3,7 +3,8 @@ ## Overview - The loader walks the module graph and gathers style-producing files (`.css`, `.scss`, `.sass`, `.less`, `.css.ts`, etc.). -- It uses `dependency-tree` (via `precinct`) to follow imports in the order they appear in source. +- It walks the module graph with a built-in depth-first resolver so imports are visited in source order. +- Resolution is powered by [`oxc-resolver`](https://github.com/oxc-project/oxc-resolver), so tsconfig `paths`, package `exports` conditions, and extension aliasing (like `.css.js` → `.css.ts`) all map to the same targets you’d get in a bundler. - CSS from those files is concatenated in that discovery order and returned as `knightedCss` for injection (e.g., Lit ` css`` `, SSR, SSG). - We do **not** sort or reorder; first-seen order is kept, so the CSS cascade mirrors the original import sequence. @@ -16,7 +17,7 @@ ### Example ordering -We rely on `dependency-tree` (via `precinct`) to follow imports in the order they appear in source. We concatenate CSS in depth-first, preorder (parent before children), preserving sibling order as written. Files are deduped: the first time a file is seen, it’s included; later encounters are skipped to avoid reshuffling the cascade. We never sort the list, and `lightningcss` preserves the rule order we provide. +The walker performs a depth-first, preorder traversal (parent before children) so imports are resolved exactly as they are written in source. Files are deduped: the first time a file is seen, it’s included; later encounters are skipped to avoid reshuffling the cascade. We never sort the list, and `lightningcss` preserves the rule order we provide. Given: diff --git a/package-lock.json b/package-lock.json index c8e291b..cfd2121 100644 --- a/package-lock.json +++ b/package-lock.json @@ -200,6 +200,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -209,6 +210,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -242,6 +244,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "devOptional": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.5" @@ -317,6 +320,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -340,6 +344,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/@dependents/detective-less/-/detective-less-5.0.1.tgz", "integrity": "sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==", + "dev": true, "license": "MIT", "dependencies": { "gonzales-pe": "^4.3.0", @@ -2167,6 +2172,269 @@ "url": "https://github.com/sponsors/Boshen" } }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.16.0.tgz", + "integrity": "sha512-/kFX4o8KISHCZzHRs8fBp/wZOPdkhYGquhMP2PQjc8ePAVbtaXXDPAFkjUKhz2jXNPS4jGA1wNW+8grhnJgstw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.16.0.tgz", + "integrity": "sha512-kPySx7j7mPxW4mRDrdbADyzJV2XrxVeMPDmNnFvTt0/LT1IA26Uk9hzWKQb4k4aeJY58bnRY1soYSawW5wAlKQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.16.0.tgz", + "integrity": "sha512-eB00fkys5TX6oI3lY+1hgHl6dwfmrbhHTmInmJmfD6BysHpE+DUqSdQIRS2v5NI6+j+J9EWBmbW3hRtolr+MSg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.16.0.tgz", + "integrity": "sha512-B/yMSxqe4MZfh/VoMax0qixl4XxG/sAQVlYtdVGNteBAYKfX/uw2mglkYsApk6D4qD6fVgJ21RwI50lV7oD0Qg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.16.0.tgz", + "integrity": "sha512-aKj+PNsSdn0owueMt/6TtR8QuLBNL/q2HgMdN8nRCDmoCBPvQlwB2s+AcW+UW1vyiok+9qiI5tVjihbKwQ+Khg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.16.0.tgz", + "integrity": "sha512-fxod0D0eMsIlGF98KRAwR3zjLCbpRoknDHjCHx22A9TmyQthGo7t66gwkRCj5g2LBbpaPZ+i6cYd2l9bRrx8+Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.16.0.tgz", + "integrity": "sha512-5BoVnD0hpEID/13hnj0fCIojE26wfa9p4puCnm12/D5BhGlXA103n8iRaPZPLHS/prQGtrwMiFONiysD6vmIBA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.16.0.tgz", + "integrity": "sha512-dMoKX6A8iuIdShbc4PB/+q6Tx8grgQxNAJQfIAmpaDTZp5NxfgzKrssPL0TCdu3RQMblF8yfXLYUFnOdPYZeRg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.16.0.tgz", + "integrity": "sha512-oLJsyqVHw53ZZPl3+wPiRNXTvavBFSInRYBB5MaNf+y42+b4XJfH7hVYyc67er0c26cQUCfx2KzqltSx7Jg9jg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.16.0.tgz", + "integrity": "sha512-qL7GsXwyytVTIh/o8cLftRYvzrpniD8pFf0jDW3VXlVsl1joCrb4GM26udGls7Zxe76nsZpPvQVB5eZ9xmHxIA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.16.0.tgz", + "integrity": "sha512-CFJEvagoakxPtIoKtRgPoGUqeXSgd63c3/T9hOXrgelOaMv6aEWFfjvc/4Lk5ppk2wv4KeK4IqOKBe8Faqv1Mw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.16.0.tgz", + "integrity": "sha512-LVuE2tbZ7gjEjY1G8mjf7+pacj0/Rge9EoHxr8DY2gAxxy0qXe5Yh2Qxe3dwwFGObVNioqRH0IPkePmQ/KJK6w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.16.0.tgz", + "integrity": "sha512-D4Zk48WN7sKsbyq4xD2F09U4S0sIkHXTW9A33BaqjfNXOD/jFXM5nTPahHx2RxBLo5ZEgS3kUW1U8V0oCBcPcg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.16.0.tgz", + "integrity": "sha512-WyqsQwz+x1lDe/rwf5pl/FiTiS4eEM7hEHn1OwjP+EThzXXBup9BeZE5QVB421QGm9n4SyJT1gJgI1LCRvqbaA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.16.0.tgz", + "integrity": "sha512-5XCuIoviaMsiAAuaQL4HqnYj1BkADcbtdf2s6Ru4YHF3P/bt2p05hd4xVo85cFT1VXlGYL66XVfepsAGymJs0g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.16.0.tgz", + "integrity": "sha512-gn54HKxOhWTxZG8pNeBMmbRwHT4k/eIf0KxBII2oHUrSTinNTcqu6xn1etqt1Yezi9KzJzkTMS0cl5kTFmCHUQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.16.0.tgz", + "integrity": "sha512-dUsUjffSI7nlt+TH9C4gGqmD/kNyx3Kghh8u+i8eZZAEFWDO+s51Yw3UADDa0BYrZDeaLjz8rgHWCE8lxpL2XQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.16.0.tgz", + "integrity": "sha512-6EhsnwzA6iT752sU5tv/r+XI5cz6sWUPHJZu3brTW3m96j6yCZ8vnfeKAkFCzuDwZAXOkRLPW8WKrL0GXWfCUQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.16.0.tgz", + "integrity": "sha512-YpUXuKrslGs4+In1gZhY25menhzyBbMct4RvWT9je6mYA5VCQ6aGAZf/ky5b+5sNPpR2UBNbCcYk5pP/6MowMw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.16.0.tgz", + "integrity": "sha512-x3hU0m0c/+frUSFaw3r5Xmde5q/PdsAfznh+8lZloGK2/qfIze0jyQG0H5M6AgrUIQE1oNn8vdGXanza5+naMw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@oxlint/darwin-arm64": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-0.4.4.tgz", @@ -3261,6 +3529,7 @@ "version": "8.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.50.0", @@ -3282,6 +3551,7 @@ "version": "8.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3298,6 +3568,7 @@ "version": "8.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3311,6 +3582,7 @@ "version": "8.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/project-service": "8.50.0", @@ -3338,6 +3610,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -3353,6 +3626,7 @@ "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -3365,6 +3639,7 @@ "version": "8.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.50.0", @@ -3555,6 +3830,7 @@ "version": "3.5.26", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", @@ -3568,12 +3844,14 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, "license": "MIT" }, "node_modules/@vue/compiler-dom": { "version": "3.5.26", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", + "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-core": "3.5.26", @@ -3584,6 +3862,7 @@ "version": "3.5.26", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", @@ -3601,12 +3880,14 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, "license": "MIT" }, "node_modules/@vue/compiler-ssr": { "version": "3.5.26", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", + "dev": true, "license": "MIT", "dependencies": { "@vue/compiler-dom": "3.5.26", @@ -3617,6 +3898,7 @@ "version": "3.5.26", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/ast": { @@ -4037,6 +4319,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz", "integrity": "sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/array-flatten": { @@ -4050,6 +4333,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ast-module-types/-/ast-module-types-6.0.1.tgz", "integrity": "sha512-WHw67kLXYbZuHTmcdbIrVArCq5wxo6NEuj3hiYAWr8mwJeC+C2mMCIBIWCiDoCye/OF/xelc+teJ1ERoWmnEIA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -4066,6 +4350,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -4224,6 +4509,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -4729,6 +5015,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/colorette": { @@ -4999,6 +5286,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5124,6 +5412,7 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/dependency-tree/-/dependency-tree-11.2.0.tgz", "integrity": "sha512-+C1H3mXhcvMCeu5i2Jpg9dc0N29TWTuT6vJD7mHLAfVmAbo9zW8NlkvQ1tYd3PDMab0IRQM0ccoyX68EZtx9xw==", + "dev": true, "license": "MIT", "dependencies": { "commander": "^12.1.0", @@ -5142,6 +5431,7 @@ "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -5183,6 +5473,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/detective-amd/-/detective-amd-6.0.1.tgz", "integrity": "sha512-TtyZ3OhwUoEEIhTFoc1C9IyJIud3y+xYkSRjmvCt65+ycQuc3VcBrPRTMWoO/AnuCyOB8T5gky+xf7Igxtjd3g==", + "dev": true, "license": "MIT", "dependencies": { "ast-module-types": "^6.0.1", @@ -5201,6 +5492,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/detective-cjs/-/detective-cjs-6.0.1.tgz", "integrity": "sha512-tLTQsWvd2WMcmn/60T2inEJNhJoi7a//PQ7DwRKEj1yEeiQs4mrONgsUtEJKnZmrGWBBmE0kJ1vqOG/NAxwaJw==", + "dev": true, "license": "MIT", "dependencies": { "ast-module-types": "^6.0.1", @@ -5214,6 +5506,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/detective-es6/-/detective-es6-5.0.1.tgz", "integrity": "sha512-XusTPuewnSUdoxRSx8OOI6xIA/uld/wMQwYsouvFN2LAg7HgP06NF1lHRV3x6BZxyL2Kkoih4ewcq8hcbGtwew==", + "dev": true, "license": "MIT", "dependencies": { "node-source-walk": "^7.0.1" @@ -5226,6 +5519,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/detective-postcss/-/detective-postcss-7.0.1.tgz", "integrity": "sha512-bEOVpHU9picRZux5XnwGsmCN4+8oZo7vSW0O0/Enq/TO5R2pIAP2279NsszpJR7ocnQt4WXU0+nnh/0JuK4KHQ==", + "dev": true, "license": "MIT", "dependencies": { "is-url": "^1.2.4", @@ -5242,6 +5536,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/detective-sass/-/detective-sass-6.0.1.tgz", "integrity": "sha512-jSGPO8QDy7K7pztUmGC6aiHkexBQT4GIH+mBAL9ZyBmnUIOFbkfZnO8wPRRJFP/QP83irObgsZHCoDHZ173tRw==", + "dev": true, "license": "MIT", "dependencies": { "gonzales-pe": "^4.3.0", @@ -5255,6 +5550,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/detective-scss/-/detective-scss-5.0.1.tgz", "integrity": "sha512-MAyPYRgS6DCiS6n6AoSBJXLGVOydsr9huwXORUlJ37K3YLyiN0vYHpzs3AdJOgHobBfispokoqrEon9rbmKacg==", + "dev": true, "license": "MIT", "dependencies": { "gonzales-pe": "^4.3.0", @@ -5268,6 +5564,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/detective-stylus/-/detective-stylus-5.0.1.tgz", "integrity": "sha512-Dgn0bUqdGbE3oZJ+WCKf8Dmu7VWLcmRJGc6RCzBgG31DLIyai9WAoEhYRgIHpt/BCRMrnXLbGWGPQuBUrnF0TA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -5277,6 +5574,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/detective-typescript/-/detective-typescript-14.0.0.tgz", "integrity": "sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/typescript-estree": "^8.23.0", @@ -5294,6 +5592,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/detective-vue2/-/detective-vue2-2.2.0.tgz", "integrity": "sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==", + "dev": true, "license": "MIT", "dependencies": { "@dependents/detective-less": "^5.0.1", @@ -5398,6 +5697,7 @@ "version": "5.18.4", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -5411,6 +5711,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -5574,6 +5875,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esprima": "^4.0.1", @@ -5619,6 +5921,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5631,6 +5934,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -5657,6 +5961,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -5676,6 +5981,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -5870,6 +6176,7 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/filing-cabinet/-/filing-cabinet-5.0.3.tgz", "integrity": "sha512-PlPcMwVWg60NQkhvfoxZs4wEHjhlOO/y7OAm4sKM60o1Z9nttRY4mcdQxp/iZ+kg/Vv6Hw1OAaTbYVM9DA9pYg==", + "dev": true, "license": "MIT", "dependencies": { "app-module-path": "^2.2.0", @@ -5895,6 +6202,7 @@ "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6051,6 +6359,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -6072,6 +6381,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6091,6 +6401,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-amd-module-type/-/get-amd-module-type-6.0.1.tgz", "integrity": "sha512-MtjsmYiCXcYDDrGqtNbeIYdAl85n+5mSv2r3FbzER/YV3ZILw4HNNIw34HuV5pyl0jzs6GFYU1VHVEefhgcNHQ==", + "dev": true, "license": "MIT", "dependencies": { "ast-module-types": "^6.0.1", @@ -6152,6 +6463,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, "license": "ISC" }, "node_modules/get-proto": { @@ -6240,6 +6552,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.3.0.tgz", "integrity": "sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ==", + "dev": true, "license": "MIT", "dependencies": { "minimist": "^1.2.5" @@ -6268,6 +6581,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/gzip-size": { @@ -6320,6 +6634,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6688,6 +7003,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -6810,6 +7126,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6845,6 +7162,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -6867,12 +7185,14 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "dev": true, "license": "MIT" }, "node_modules/is-url-superb": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-url-superb/-/is-url-superb-4.0.0.tgz", "integrity": "sha512-GI+WjezhPPcbM+tqE9LnmsY5qqjwHzTvjJ36wxYX5ujNXefSUJ/T17r5bqDV8yLhcgB59KTPNOc9O9cmHTPWsA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -7059,6 +7379,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "devOptional": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -7568,6 +7889,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "devOptional": true, "license": "ISC" }, "node_modules/madge": { @@ -7833,6 +8155,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7883,6 +8206,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.1.tgz", "integrity": "sha512-FeVc50FTfVVQnolk/WQT8MX+2WVcDnTGiq6Wo+/+lJ2ET1bRVi3HG3YlJUfqagNMc/kUlFSoR96AJkxGpKz13g==", + "dev": true, "license": "MIT", "dependencies": { "ast-module-types": "^6.0.1", @@ -7899,6 +8223,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/module-lookup-amd/-/module-lookup-amd-9.0.5.tgz", "integrity": "sha512-Rs5FVpVcBYRHPLuhHOjgbRhosaQYLtEo3JIeDIbmNo7mSssi1CTzwMh8v36gAzpbzLGXI9wB/yHh+5+3fY1QVw==", + "dev": true, "license": "MIT", "dependencies": { "commander": "^12.1.0", @@ -7917,6 +8242,7 @@ "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -7926,6 +8252,7 @@ "version": "9.3.5", "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -7944,6 +8271,7 @@ "version": "8.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -7959,6 +8287,7 @@ "version": "4.2.8", "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", + "dev": true, "license": "ISC", "engines": { "node": ">=8" @@ -7968,6 +8297,7 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -7984,6 +8314,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -8003,6 +8334,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, "license": "MIT" }, "node_modules/multicast-dns": { @@ -8036,6 +8368,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, "funding": [ { "type": "github", @@ -8128,6 +8461,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/node-source-walk/-/node-source-walk-7.0.1.tgz", "integrity": "sha512-3VW/8JpPqPvnJvseXowjZcirPisssnBuDikk6JIZ8jQzF7KJQX52iPFX4RYYxLycYH7IbMRSPUOga/esVjy5Yg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.26.7" @@ -8409,6 +8743,37 @@ "node": ">=14.0.0" } }, + "node_modules/oxc-resolver": { + "version": "11.16.0", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.16.0.tgz", + "integrity": "sha512-I4sHGa1fZUpTQ9ftS0E0cBYbBjNnIKXRSX/trFMIJDIJ4n21dCrLAZhnJS0TSfRIRqZNFyceNZr2kablfgNyTA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.16.0", + "@oxc-resolver/binding-android-arm64": "11.16.0", + "@oxc-resolver/binding-darwin-arm64": "11.16.0", + "@oxc-resolver/binding-darwin-x64": "11.16.0", + "@oxc-resolver/binding-freebsd-x64": "11.16.0", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.16.0", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.16.0", + "@oxc-resolver/binding-linux-arm64-gnu": "11.16.0", + "@oxc-resolver/binding-linux-arm64-musl": "11.16.0", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.16.0", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.16.0", + "@oxc-resolver/binding-linux-riscv64-musl": "11.16.0", + "@oxc-resolver/binding-linux-s390x-gnu": "11.16.0", + "@oxc-resolver/binding-linux-x64-gnu": "11.16.0", + "@oxc-resolver/binding-linux-x64-musl": "11.16.0", + "@oxc-resolver/binding-openharmony-arm64": "11.16.0", + "@oxc-resolver/binding-wasm32-wasi": "11.16.0", + "@oxc-resolver/binding-win32-arm64-msvc": "11.16.0", + "@oxc-resolver/binding-win32-ia32-msvc": "11.16.0", + "@oxc-resolver/binding-win32-x64-msvc": "11.16.0" + } + }, "node_modules/oxlint": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-0.4.4.tgz", @@ -8587,6 +8952,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -8634,6 +9000,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "devOptional": true, "license": "ISC" }, "node_modules/picomatch": { @@ -8829,6 +9196,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -8858,6 +9226,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-6.0.2.tgz", "integrity": "sha512-YLJpK0N1brcNJrs9WatuJFtHaV9q5aAOj+S4DI5S7jgHlRfm0PIbDCAFRYMQD5SHq7Fy6xsDhyutgS0QOAs0qw==", + "dev": true, "license": "MPL-2.0", "dependencies": { "color-name": "^1.1.4", @@ -8875,6 +9244,7 @@ "version": "12.2.0", "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.2.0.tgz", "integrity": "sha512-NFBMuwIfaJ4SocE9YXPU/n4AcNSoFMVFjP72nvl3cx69j/ke61/hPOWFREVxLkFhhEGnA8ZuVfTqJBa+PK3b5w==", + "dev": true, "license": "MIT", "dependencies": { "@dependents/detective-less": "^5.0.1", @@ -8904,6 +9274,7 @@ "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -9000,6 +9371,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/quote-unquote/-/quote-unquote-1.0.0.tgz", "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", + "dev": true, "license": "MIT" }, "node_modules/randombytes": { @@ -9201,6 +9573,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.8.tgz", "integrity": "sha512-7/cTSLOdYkNBNJcDMWf+luFvMriVm7eYxp4BcFCsAX0wF421Vyce5SXP17c+Jd5otXKGNehIonFlyQXSowL6Mw==", + "dev": true, "license": "MIT", "bin": { "r_js": "bin/r.js", @@ -9214,6 +9587,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz", "integrity": "sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==", + "dev": true, "license": "MIT", "dependencies": { "esprima": "^4.0.0", @@ -9234,6 +9608,7 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -9267,6 +9642,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/resolve-dependency-path/-/resolve-dependency-path-4.0.1.tgz", "integrity": "sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -9379,6 +9755,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/sass-lookup/-/sass-lookup-6.1.0.tgz", "integrity": "sha512-Zx+lVyoWqXZxHuYWlTA17Z5sczJ6braNT2C7rmClw+c4E7r/n911Zwss3h1uHI9reR5AgHZyNHF7c2+VIp5AUA==", + "dev": true, "license": "MIT", "dependencies": { "commander": "^12.1.0", @@ -9395,6 +9772,7 @@ "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -9862,7 +10240,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -9872,6 +10250,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -10087,6 +10466,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", @@ -10141,6 +10521,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10160,6 +10541,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/stylus-lookup/-/stylus-lookup-6.1.0.tgz", "integrity": "sha512-5QSwgxAzXPMN+yugy61C60PhoANdItfdjSEZR8siFwz7yL9jTmV0UBKDCfn3K8GkGB4g0Y9py7vTCX8rFu4/pQ==", + "dev": true, "license": "MIT", "dependencies": { "commander": "^12.1.0" @@ -10175,6 +10557,7 @@ "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -10197,6 +10580,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -10222,6 +10606,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10414,6 +10799,7 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -10430,6 +10816,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -10447,6 +10834,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, "license": "MIT", "peer": true, "engines": { @@ -10510,6 +10898,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -10592,6 +10981,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -10983,6 +11373,7 @@ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -11520,13 +11911,14 @@ }, "packages/css": { "name": "@knighted/css", - "version": "1.0.0-rc.8", + "version": "1.0.0-rc.9", "license": "MIT", "dependencies": { - "dependency-tree": "^11.2.0", "es-module-lexer": "^2.0.0", "lightningcss": "^1.30.2", - "node-module-type": "^1.0.1" + "node-module-type": "^1.0.1", + "oxc-resolver": "^11.16.0", + "typescript": "^5.9.3" }, "bin": { "knighted-css-generate-types": "bin/generate-types.js" @@ -11552,7 +11944,7 @@ "name": "@knighted/css-playwright-fixture", "version": "0.0.0", "dependencies": { - "@knighted/css": "1.0.0-rc.8", + "@knighted/css": "1.0.0-rc.9", "@knighted/jsx": "^1.4.1", "lit": "^3.2.1", "react": "^19.0.0", diff --git a/packages/css/package.json b/packages/css/package.json index d15f117..6b315bd 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -1,6 +1,6 @@ { "name": "@knighted/css", - "version": "1.0.0-rc.8", + "version": "1.0.0-rc.9", "description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.", "type": "module", "main": "./dist/css.js", @@ -76,15 +76,11 @@ "prepack": "npm run build" }, "dependencies": { - "dependency-tree": "^11.2.0", "es-module-lexer": "^2.0.0", "lightningcss": "^1.30.2", - "node-module-type": "^1.0.1" - }, - "overrides": { - "module-lookup-amd": { - "glob": "^9.0.0" - } + "node-module-type": "^1.0.1", + "oxc-resolver": "^11.16.0", + "typescript": "^5.9.3" }, "peerDependencies": { "@vanilla-extract/integration": "^8.0.0", diff --git a/packages/css/src/css.ts b/packages/css/src/css.ts index 4a2b84c..159399a 100644 --- a/packages/css/src/css.ts +++ b/packages/css/src/css.ts @@ -1,8 +1,6 @@ import path from 'node:path' import { existsSync, promises as fs } from 'node:fs' -import dependencyTree from 'dependency-tree' -import type { Options as DependencyTreeOpts } from 'dependency-tree' import { composeVisitors, transform as lightningTransform, @@ -14,10 +12,13 @@ import { type SpecificitySelector, type SpecificityStrategy, } from './helpers.js' +import { collectStyleImports } from './moduleGraph.js' +import type { ModuleGraphOptions } from './moduleGraph.js' import { createSassImporter } from './sassInternals.js' -import type { CssResolver } from './sassInternals.js' +import type { CssResolver } from './types.js' -export type { CssResolver } from './sassInternals.js' +export type { CssResolver } from './types.js' +export type { ModuleGraphOptions } from './moduleGraph.js' export const DEFAULT_EXTENSIONS = ['.css', '.scss', '.sass', '.less', '.css.ts'] @@ -37,7 +38,7 @@ export interface CssOptions { strategy?: SpecificityStrategy match?: SpecificitySelector[] } - dependencyTree?: Partial> + moduleGraph?: ModuleGraphOptions resolver?: CssResolver peerResolver?: PeerLoader } @@ -75,11 +76,12 @@ export async function cssWithMeta( ext.toLowerCase(), ) - const files = collectStyleDependencies(entryPath, { + const files = await collectStyleDependencies(entryPath, { cwd, extensions, filter: options.filter, - dependencyTreeOptions: options.dependencyTree, + graphOptions: options.moduleGraph, + resolver: options.resolver, }) if (files.length === 0) { @@ -144,20 +146,22 @@ async function resolveEntry( return path.resolve(cwd, entry) } -function collectStyleDependencies( +async function collectStyleDependencies( entryPath: string, { cwd, extensions, filter, - dependencyTreeOptions, + graphOptions, + resolver, }: { cwd: string extensions: string[] filter?: (filePath: string) => boolean - dependencyTreeOptions?: Partial> + graphOptions?: ModuleGraphOptions + resolver?: CssResolver }, -): StyleModule[] { +): Promise { const seen = new Set() const order: StyleModule[] = [] @@ -167,25 +171,27 @@ function collectStyleDependencies( : (filePath: string) => !filePath.includes('node_modules') const entryIsStyle = Boolean(matchExtension(entryPath, extensions)) - let treeList: string[] = [] + let discoveredStyles: string[] = [] if (!entryIsStyle) { - const dependencyConfig: DependencyTreeOpts = { - ...dependencyTreeOptions, - filename: entryPath, - directory: cwd, + discoveredStyles = await collectStyleImports(entryPath, { + cwd, + styleExtensions: extensions, filter: shouldInclude, - } - treeList = dependencyTree.toList(dependencyConfig) + resolver, + graphOptions, + }) } - const candidates = entryIsStyle ? [entryPath] : [entryPath, ...treeList] + const candidates = entryIsStyle ? [entryPath] : [entryPath, ...discoveredStyles] for (const candidate of candidates) { const match = matchExtension(candidate, extensions) - if (!match || seen.has(candidate)) continue - seen.add(candidate) - order.push({ path: path.resolve(candidate), ext: match }) + if (!match) continue + const resolvedCandidate = path.resolve(candidate) + if (seen.has(resolvedCandidate)) continue + seen.add(resolvedCandidate) + order.push({ path: resolvedCandidate, ext: match }) } return order diff --git a/packages/css/src/moduleGraph.ts b/packages/css/src/moduleGraph.ts new file mode 100644 index 0000000..4b264ae --- /dev/null +++ b/packages/css/src/moduleGraph.ts @@ -0,0 +1,548 @@ +import path from 'node:path' +import { builtinModules } from 'node:module' +import { existsSync, promises as fs, statSync } from 'node:fs' +import { fileURLToPath } from 'node:url' + +import { + ResolverFactory, + type NapiResolveOptions, + type TsconfigOptions as ResolverTsconfigOptions, +} from 'oxc-resolver' +import ts from 'typescript' + +import type { CssResolver } from './types.js' + +const SCRIPT_EXTENSIONS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs'] + +const BUILTIN_SPECIFIERS = new Set([ + ...builtinModules, + ...builtinModules.map(mod => `node:${mod}`), +]) + +export interface ModuleGraphOptions { + tsConfig?: string | Record + extensions?: string[] + conditions?: string[] +} + +interface CollectOptions { + cwd: string + styleExtensions: string[] + filter: (filePath: string) => boolean + resolver?: CssResolver + graphOptions?: ModuleGraphOptions +} + +type TsconfigLike = string | Record + +interface TsconfigPathsResult { + baseUrl: string + paths: Record +} + +export async function collectStyleImports( + entryPath: string, + options: CollectOptions, +): Promise { + const { cwd, styleExtensions, filter, resolver, graphOptions } = options + const normalizedStyles = normalizeExtensions(styleExtensions) + const scriptExtensions = normalizeExtensions([ + ...SCRIPT_EXTENSIONS, + ...(graphOptions?.extensions ?? []), + ]) + const resolutionExtensions = dedupeExtensions([ + ...scriptExtensions, + ...normalizedStyles, + ]) + const tsconfigMatcher = createTsconfigMatcher( + graphOptions?.tsConfig, + cwd, + resolutionExtensions, + ) + + const seenScripts = new Set() + const seenStyles = new Set() + const styleOrder: string[] = [] + const resolutionCache = new Map() + const resolverFactory = createResolverFactory( + cwd, + resolutionExtensions, + scriptExtensions, + graphOptions, + ) + + async function walk(filePath: string): Promise { + const absolutePath = path.resolve(filePath) + if (seenScripts.has(absolutePath)) { + return + } + seenScripts.add(absolutePath) + const source = await readSourceFile(absolutePath) + if (!source) { + return + } + const specifiers = extractModuleSpecifiers(source, absolutePath) + for (const specifier of specifiers) { + if (!specifier || isBuiltinSpecifier(specifier)) { + continue + } + const resolved = await resolveImport(specifier, absolutePath) + if (!resolved) { + continue + } + const normalized = path.resolve(resolved) + if (!filter(normalized)) { + continue + } + if (isStyleExtension(normalized, normalizedStyles)) { + if (!seenStyles.has(normalized)) { + seenStyles.add(normalized) + styleOrder.push(normalized) + } + continue + } + if (isScriptExtension(normalized, scriptExtensions)) { + await walk(normalized) + } + } + } + + async function resolveImport( + specifier: string, + importer: string, + ): Promise { + const cacheKey = `${importer}::${specifier}` + if (resolutionCache.has(cacheKey)) { + return resolutionCache.get(cacheKey) + } + let resolved: string | undefined + + if (resolver) { + resolved = normalizeResolverResult( + await resolver(specifier, { cwd, from: importer }), + cwd, + ) + } + + if (!resolved && tsconfigMatcher) { + resolved = tsconfigMatcher(specifier) + } + + if (!resolved) { + resolved = resolveWithFactory( + resolverFactory, + specifier, + importer, + resolutionExtensions, + ) + } + + resolutionCache.set(cacheKey, resolved) + return resolved + } + + await walk(entryPath) + return styleOrder +} + +function normalizeExtensions(extensions: string[]): string[] { + const result = new Set() + for (const ext of extensions) { + if (!ext) continue + const normalized = ext.startsWith('.') ? ext.toLowerCase() : `.${ext.toLowerCase()}` + result.add(normalized) + } + return Array.from(result) +} + +function dedupeExtensions(extensions: string[]): string[] { + const result = new Set() + for (const ext of extensions) { + result.add(ext) + } + return Array.from(result) +} + +function isBuiltinSpecifier(specifier: string): boolean { + if (BUILTIN_SPECIFIERS.has(specifier)) { + return true + } + if (specifier.startsWith('node:')) { + return true + } + return false +} + +function isStyleExtension(filePath: string, extensions: string[]): boolean { + const lower = filePath.toLowerCase() + return extensions.some(ext => lower.endsWith(ext)) +} + +function isScriptExtension(filePath: string, extensions: string[]): boolean { + const ext = path.extname(filePath).toLowerCase() + return extensions.includes(ext) +} + +async function readSourceFile(filePath: string): Promise { + try { + return await fs.readFile(filePath, 'utf8') + } catch { + return undefined + } +} + +function extractModuleSpecifiers(sourceText: string, filePath: string): string[] { + const specifiers: string[] = [] + const source = ts.createSourceFile( + filePath, + sourceText, + ts.ScriptTarget.Latest, + true, + getScriptKind(filePath), + ) + + const addSpecifier = (raw: string) => { + const normalized = normalizeSpecifier(raw) + if (normalized) { + specifiers.push(normalized) + } + } + + const visit = (node: ts.Node) => { + if ( + (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && + node.moduleSpecifier && + ts.isStringLiteralLike(node.moduleSpecifier) + ) { + addSpecifier(node.moduleSpecifier.text) + } else if (ts.isImportEqualsDeclaration(node)) { + if ( + ts.isExternalModuleReference(node.moduleReference) && + node.moduleReference.expression && + ts.isStringLiteralLike(node.moduleReference.expression) + ) { + addSpecifier(node.moduleReference.expression.text) + } + } else if (ts.isCallExpression(node)) { + if (isRequireCall(node) || isDynamicImport(node)) { + const [argument] = node.arguments + if (argument && ts.isStringLiteralLike(argument)) { + addSpecifier(argument.text) + } + } + } + ts.forEachChild(node, visit) + } + + visit(source) + return specifiers +} + +function getScriptKind(filePath: string): ts.ScriptKind { + const ext = path.extname(filePath).toLowerCase() + switch (ext) { + case '.ts': + return ts.ScriptKind.TS + case '.tsx': + return ts.ScriptKind.TSX + case '.mts': + return ts.ScriptKind.TS + case '.cts': + return ts.ScriptKind.TS + case '.jsx': + return ts.ScriptKind.JSX + default: + return ts.ScriptKind.JS + } +} + +function normalizeSpecifier(raw: string): string { + if (!raw) return '' + const trimmed = raw.trim() + if (!trimmed || trimmed.startsWith('\0')) { + return '' + } + const queryIndex = trimmed.search(/[?#]/) + const withoutQuery = queryIndex === -1 ? trimmed : trimmed.slice(0, queryIndex) + if (!withoutQuery) { + return '' + } + if (/^[a-z][\w+.-]*:/i.test(withoutQuery) && !withoutQuery.startsWith('file:')) { + return '' + } + return withoutQuery +} + +function isRequireCall(node: ts.CallExpression): boolean { + if (!ts.isIdentifier(node.expression)) { + if ( + ts.isPropertyAccessExpression(node.expression) && + ts.isIdentifier(node.expression.expression) + ) { + return node.expression.expression.text === 'require' + } + return false + } + return node.expression.text === 'require' +} + +function isDynamicImport(node: ts.CallExpression): boolean { + return node.expression.kind === ts.SyntaxKind.ImportKeyword +} + +function normalizeResolverResult( + result: string | undefined, + cwd: string, +): string | undefined { + if (!result) { + return undefined + } + if (result.startsWith('file://')) { + try { + return fileURLToPath(new URL(result)) + } catch { + return undefined + } + } + return path.isAbsolute(result) ? result : path.resolve(cwd, result) +} + +function resolveWithFactory( + factory: ResolverFactory, + specifier: string, + importer: string, + extensions: string[], +): string | undefined { + if (specifier.startsWith('file://')) { + try { + return findExistingFile(fileURLToPath(new URL(specifier)), extensions) + } catch { + return undefined + } + } + if (/^[a-z][\w+.-]*:/i.test(specifier)) { + return undefined + } + try { + const result = factory.resolveFileSync(importer, specifier) + return result?.path + } catch { + return undefined + } +} + +function createResolverFactory( + cwd: string, + extensions: string[], + scriptExtensions: string[], + graphOptions?: ModuleGraphOptions, +): ResolverFactory { + const options: NapiResolveOptions = { + extensions, + conditionNames: graphOptions?.conditions, + } + const extensionAlias = buildExtensionAlias(scriptExtensions) + if (extensionAlias) { + options.extensionAlias = extensionAlias + } + const tsconfigOption = resolveResolverTsconfig(graphOptions?.tsConfig, cwd) + if (tsconfigOption) { + options.tsconfig = tsconfigOption + } + return new ResolverFactory(options) +} + +function buildExtensionAlias( + scriptExtensions: string[], +): Record | undefined { + const alias: Record = {} + const jsTargets = dedupeExtensions( + scriptExtensions.filter(ext => + ['.js', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts'].includes(ext), + ), + ) + if (jsTargets.length > 0) { + for (const key of ['.js', '.mjs', '.cjs']) { + alias[key] = jsTargets + } + } + const jsxTargets = dedupeExtensions( + scriptExtensions.filter(ext => ext === '.jsx' || ext === '.tsx'), + ) + if (jsxTargets.length > 0) { + alias['.jsx'] = jsxTargets + } + return Object.keys(alias).length > 0 ? alias : undefined +} + +function resolveResolverTsconfig( + input: TsconfigLike | undefined, + cwd: string, +): ResolverTsconfigOptions | undefined { + if (!input || typeof input !== 'string') { + return undefined + } + const resolved = resolveTsconfigPath(input, cwd) + if (!resolved) { + return undefined + } + return { configFile: resolved } +} + +function createTsconfigMatcher( + input: TsconfigLike | undefined, + cwd: string, + extensions: string[], +): ((specifier: string) => string | undefined) | undefined { + const config = loadTsconfigPaths(input, cwd) + if (!config) { + return undefined + } + const patterns = Object.entries(config.paths).map(([pattern, replacements]) => ({ + pattern, + replacements: Array.isArray(replacements) ? replacements : [replacements], + })) + + return (specifier: string) => { + for (const { pattern, replacements } of patterns) { + if (!pattern.includes('*')) { + if (pattern === specifier) { + const resolved = resolveReplacements( + replacements, + '', + config.baseUrl, + extensions, + ) + if (resolved) { + return resolved + } + } + continue + } + const [prefix, suffix] = pattern.split('*') + if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) { + continue + } + const wildcard = specifier.slice(prefix.length, specifier.length - suffix.length) + const resolved = resolveReplacements( + replacements, + wildcard, + config.baseUrl, + extensions, + ) + if (resolved) { + return resolved + } + } + return undefined + } +} + +function resolveReplacements( + replacements: readonly string[], + wildcard: string, + baseUrl: string, + extensions: string[], +): string | undefined { + for (const replacement of replacements) { + const substituted = replacement.includes('*') + ? replacement.split('*').join(wildcard) + : replacement + const candidate = path.resolve(baseUrl, substituted) + const resolved = findExistingFile(candidate, extensions) + if (resolved) { + return resolved + } + } + return undefined +} + +function loadTsconfigPaths( + input: TsconfigLike | undefined, + cwd: string, +): TsconfigPathsResult | undefined { + if (!input) { + return undefined + } + if (typeof input === 'string') { + const configPath = resolveTsconfigPath(input, cwd) + if (!configPath) { + return undefined + } + const readResult = ts.readConfigFile(configPath, ts.sys.readFile) + if (readResult.error) { + return undefined + } + const parsed = ts.parseJsonConfigFileContent( + readResult.config, + ts.sys, + path.dirname(configPath), + undefined, + configPath, + ) + const baseUrl = parsed.options.baseUrl + const paths = parsed.options.paths + if (!baseUrl || !paths) { + return undefined + } + return { + baseUrl: path.isAbsolute(baseUrl) + ? baseUrl + : path.resolve(path.dirname(configPath), baseUrl), + paths, + } + } + const compilerOptions = ( + input as { compilerOptions?: { baseUrl?: string; paths?: Record } } + ).compilerOptions + if (!compilerOptions?.baseUrl || !compilerOptions.paths) { + return undefined + } + return { + baseUrl: path.resolve(cwd, compilerOptions.baseUrl), + paths: compilerOptions.paths, + } +} + +function resolveTsconfigPath(tsconfigPath: string, cwd: string): string | undefined { + const absolute = path.isAbsolute(tsconfigPath) + ? tsconfigPath + : path.resolve(cwd, tsconfigPath) + if (!existsSync(absolute)) { + return undefined + } + const stats = statSync(absolute) + if (stats.isDirectory()) { + const candidate = path.join(absolute, 'tsconfig.json') + return existsSync(candidate) ? candidate : undefined + } + return absolute +} + +function findExistingFile(candidate: string, extensions: string[]): string | undefined { + const candidateHasExt = hasExtension(candidate) + if (candidateHasExt && existsSync(candidate)) { + return candidate + } + if (!candidateHasExt) { + for (const ext of extensions) { + const withExt = `${candidate}${ext}` + if (existsSync(withExt)) { + return withExt + } + } + } + if (existsSync(candidate) && statSync(candidate).isDirectory()) { + for (const ext of extensions) { + const indexPath = path.join(candidate, `index${ext}`) + if (existsSync(indexPath)) { + return indexPath + } + } + } + return undefined +} + +function hasExtension(filePath: string): boolean { + return Boolean(path.extname(filePath)) +} diff --git a/packages/css/src/sassInternals.ts b/packages/css/src/sassInternals.ts index 41ddafb..701954e 100644 --- a/packages/css/src/sassInternals.ts +++ b/packages/css/src/sassInternals.ts @@ -2,10 +2,7 @@ import path from 'node:path' import { existsSync, promises as fs } from 'node:fs' import { fileURLToPath, pathToFileURL } from 'node:url' -export type CssResolver = ( - specifier: string, - ctx: { cwd: string }, -) => string | Promise +import type { CssResolver } from './types.js' export function createSassImporter({ cwd, @@ -25,8 +22,16 @@ export function createSassImporter({ console.error('[knighted-css:sass] containing url:', context.containingUrl.href) } } + const containingPath = context?.containingUrl + ? fileURLToPath(context.containingUrl) + : undefined if (shouldNormalizeSpecifier(url)) { - const resolvedPath = await resolveAliasSpecifier(url, resolver, cwd) + const resolvedPath = await resolveAliasSpecifier( + url, + resolver, + cwd, + containingPath, + ) if (!resolvedPath) { if (debug) { console.error('[knighted-css:sass] resolver returned no result for', url) @@ -67,8 +72,9 @@ export async function resolveAliasSpecifier( specifier: string, resolver: CssResolver, cwd: string, + from?: string, ): Promise { - const resolved = await resolver(specifier, { cwd }) + const resolved = await resolver(specifier, { cwd, from }) if (!resolved) { return undefined } diff --git a/packages/css/src/types.ts b/packages/css/src/types.ts new file mode 100644 index 0000000..e81da5b --- /dev/null +++ b/packages/css/src/types.ts @@ -0,0 +1,4 @@ +export type CssResolver = ( + specifier: string, + ctx: { cwd: string; from?: string }, +) => string | Promise diff --git a/packages/playwright/package.json b/packages/playwright/package.json index 99b9191..f8765ad 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -14,7 +14,7 @@ "pretest": "npm run build" }, "dependencies": { - "@knighted/css": "1.0.0-rc.8", + "@knighted/css": "1.0.0-rc.9", "@knighted/jsx": "^1.4.1", "lit": "^3.2.1", "react": "^19.0.0", From 42d2f0992c8c9ba29327382de1b0d67c8cdbed08 Mon Sep 17 00:00:00 2001 From: KCM Date: Sun, 21 Dec 2025 14:20:13 -0600 Subject: [PATCH 2/5] feat: use oxc-resolver. --- packages/css/src/sassInternals.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/css/src/sassInternals.ts b/packages/css/src/sassInternals.ts index 701954e..706b681 100644 --- a/packages/css/src/sassInternals.ts +++ b/packages/css/src/sassInternals.ts @@ -4,6 +4,8 @@ import { fileURLToPath, pathToFileURL } from 'node:url' import type { CssResolver } from './types.js' +export type { CssResolver } from './types.js' + export function createSassImporter({ cwd, resolver, From 2def3cadd39b8d1f117d3730723b8f9c5fc2a12d Mon Sep 17 00:00:00 2001 From: KCM Date: Sun, 21 Dec 2025 14:38:25 -0600 Subject: [PATCH 3/5] test: bump patch coverage. --- packages/css/test/moduleGraph.test.ts | 207 ++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 packages/css/test/moduleGraph.test.ts diff --git a/packages/css/test/moduleGraph.test.ts b/packages/css/test/moduleGraph.test.ts new file mode 100644 index 0000000..3a807a9 --- /dev/null +++ b/packages/css/test/moduleGraph.test.ts @@ -0,0 +1,207 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import test from 'node:test' +import { pathToFileURL } from 'node:url' + +import { collectStyleImports } from '../src/moduleGraph.ts' +import type { CssResolver } from '../src/types.js' + +interface Project { + root: string + file: (rel: string) => string + writeFile: (rel: string, contents: string) => Promise + cleanup: () => Promise +} + +async function createProject(prefix: string): Promise { + const root = await fs.mkdtemp(path.join(os.tmpdir(), prefix)) + const writeFile = async (rel: string, contents: string): Promise => { + const target = path.join(root, rel) + await fs.mkdir(path.dirname(target), { recursive: true }) + await fs.writeFile(target, contents, 'utf8') + return target + } + return { + root, + file: rel => path.join(root, rel), + writeFile, + cleanup: () => fs.rm(root, { recursive: true, force: true }), + } +} + +async function realpathAll(paths: string[]): Promise { + return Promise.all(paths.map(filePath => fs.realpath(filePath))) +} + +test('collectStyleImports walks modules, resolves variants, and dedupes styles', async () => { + const project = await createProject('knighted-module-graph-basic-') + try { + await project.writeFile('styles/shared.css', '.shared { color: red; }') + await project.writeFile('styles/component.scss', '.component { color: blue; }') + await project.writeFile('styles/legacy.css', '.legacy { color: green; }') + await project.writeFile('styles/exported.css', '.exported { color: purple; }') + await project.writeFile('styles/async.css', '.async { color: orange; }') + await project.writeFile('styles/nested/deep.css', '.deep { color: teal; }') + await project.writeFile('styles/virtual.css', '.virtual { color: pink; }') + await project.writeFile('styles/url.css', '.url { color: black; }') + + const fileUrl = pathToFileURL(project.file('styles/url.css')).href + const entrySource = `import '${fileUrl}' + import './styles/shared.css?inline#hash' + import './component.js' + import './missing/script.js' + import 'node:fs' + const builtin = require('fs') + await import('./async-chunk.js') + await import('https://cdn.knighted.dev/widget.js') + import '@virtual/style' + void builtin + ` + await project.writeFile('entry.ts', entrySource) + + const componentSource = `import './styles/component.scss' +const duplicate = require('./styles/shared.css') +import legacyStyles = require('./styles/legacy.css') +export * from './styles/exported.css?raw' +const viaProperty = require.resolve('./styles/component.scss') +void duplicate +void legacyStyles +void viaProperty +` + await project.writeFile('component.ts', componentSource) + + const asyncChunkSource = `import './styles/async.css?url#data' +export async function load() { + await import('./nested/deep.js') + return import('node:path') +} +` + await project.writeFile('async-chunk.tsx', asyncChunkSource) + + const nestedSource = `import '../styles/nested/deep.css' +` + await project.writeFile('nested/deep.ts', nestedSource) + + const resolver: CssResolver = async specifier => { + if (specifier === '@virtual/style') { + return pathToFileURL(project.file('styles/virtual.css')).href + } + return undefined + } + + const styles = await collectStyleImports(project.file('entry.ts'), { + cwd: project.root, + styleExtensions: ['.css', '.scss'], + filter: () => true, + resolver, + }) + + const expected = [ + project.file('styles/url.css'), + project.file('styles/shared.css'), + project.file('styles/component.scss'), + project.file('styles/legacy.css'), + project.file('styles/exported.css'), + project.file('styles/async.css'), + project.file('styles/nested/deep.css'), + project.file('styles/virtual.css'), + ] + + assert.deepEqual(await realpathAll(styles), await realpathAll(expected)) + } finally { + await project.cleanup() + } +}) + +test('collectStyleImports honors tsconfig paths loaded from a file', async () => { + const project = await createProject('knighted-module-graph-tsconfig-file-') + try { + await project.writeFile( + 'configs/tsconfig.json', + JSON.stringify( + { + compilerOptions: { + baseUrl: '../src', + paths: { + '@theme/*': ['styles/*'], + '@pkg/*': ['pkg/*/index'], + '@core': ['styles/core/index.css'], + }, + }, + }, + null, + 2, + ), + ) + + await project.writeFile('src/styles/colors.css', '.colors { color: coral; }') + await project.writeFile('src/styles/button.css', '.button { color: salmon; }') + await project.writeFile('src/styles/core/index.css', '.core { color: navy; }') + + const pkgSource = `import '@theme/button.css' +` + await project.writeFile('src/pkg/button/index.ts', pkgSource) + + const entrySource = `import '@theme/colors.css' +import '@core' +import '@pkg/button' +` + await project.writeFile('src/entry.ts', entrySource) + + const styles = await collectStyleImports(project.file('src/entry.ts'), { + cwd: project.root, + styleExtensions: ['.css'], + filter: filePath => filePath.startsWith(project.root), + graphOptions: { + tsConfig: project.file('configs'), + }, + }) + + assert.deepEqual(styles, [ + project.file('src/styles/colors.css'), + project.file('src/styles/core/index.css'), + project.file('src/styles/button.css'), + ]) + } finally { + await project.cleanup() + } +}) + +test('collectStyleImports resolves inline tsconfig objects with directory indexes', async () => { + const project = await createProject('knighted-module-graph-tsconfig-inline-') + try { + await project.writeFile('styles/direct.css', '.direct { color: cyan; }') + await project.writeFile('blocks/panel/index.css', '.panel { color: magenta; }') + + const entrySource = `import '@direct' +import '@blocks/panel' +` + await project.writeFile('entry.ts', entrySource) + + const styles = await collectStyleImports(project.file('entry.ts'), { + cwd: project.root, + styleExtensions: ['.css'], + filter: filePath => filePath.startsWith(project.root), + graphOptions: { + tsConfig: { + compilerOptions: { + baseUrl: '.', + paths: { + '@direct': ['styles/direct.css'], + '@blocks/*': ['blocks/*'], + }, + }, + }, + }, + }) + + assert.deepEqual(styles, [ + project.file('styles/direct.css'), + project.file('blocks/panel/index.css'), + ]) + } finally { + await project.cleanup() + } +}) From 4d9e50eba61d9fd58c327974fab1fec234747024 Mon Sep 17 00:00:00 2001 From: KCM Date: Sun, 21 Dec 2025 15:27:01 -0600 Subject: [PATCH 4/5] refactor: remove typescript dependency. --- package-lock.json | 292 +++++++++++++++++++++++++++++- packages/css/package.json | 4 +- packages/css/src/moduleGraph.ts | 305 +++++++++++++++++--------------- 3 files changed, 448 insertions(+), 153 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfd2121..6990bb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2112,6 +2112,22 @@ "node": ">=20.0.0" } }, + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.104.0.tgz", + "integrity": "sha512-/FNOTg5tzlpB0hunjdI1AiJp2lC8NvBl0HpqMk0qp42KAK+yOe7nyw7XcNpMH7SvhmPe+R3oyJV8UQv953sCvA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@oxc-parser/binding-wasm32-wasi": { "version": "0.99.0", "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.99.0.tgz", @@ -6484,7 +6500,6 @@ "version": "4.13.0", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", - "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -7379,7 +7394,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "devOptional": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -8155,7 +8169,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9662,7 +9675,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -10521,7 +10533,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -10981,7 +10992,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, "license": "MIT", "dependencies": { "json5": "^2.2.2", @@ -11054,6 +11064,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -11915,10 +11926,12 @@ "license": "MIT", "dependencies": { "es-module-lexer": "^2.0.0", + "get-tsconfig": "^4.13.0", "lightningcss": "^1.30.2", "node-module-type": "^1.0.1", + "oxc-parser": "^0.104.0", "oxc-resolver": "^11.16.0", - "typescript": "^5.9.3" + "tsconfig-paths": "^4.2.0" }, "bin": { "knighted-css-generate-types": "bin/generate-types.js" @@ -11940,6 +11953,271 @@ } } }, + "packages/css/node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.104.0.tgz", + "integrity": "sha512-2a4tk6mtkPbGwT4Zi8pjOqMkmkrvc/Pussmas+uXoiCVv7sn7P7Jn93ATu/HK49dxIwvrvQCZ3BcS0KhAvKxlQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.104.0.tgz", + "integrity": "sha512-qprm6/Hr35TSCkJoo+huRHq3siyqRyMBtNVNJQkJorZAGcffQTzzdhmzJnA7TQc+WnN36/Jq56D7/INu0/2knQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.104.0.tgz", + "integrity": "sha512-Xpu0l/l2zCUeheIrpu7dz6FxQ96W0f8Dq9rhYE1yLtTqQpMGhefnEV5Lr8qd15clwniZiKWZO6FGQawQ7npt3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.104.0.tgz", + "integrity": "sha512-I++fE3ZrhjtLzA0mqFcC/GuA6+dWNa/QkJJfm7sS4NGGQhipQQ2vikCK6jZK1YGIsbDFDrSoQCfA3tQfeK6pqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.104.0.tgz", + "integrity": "sha512-JkvwCNZm3ZwVG1r+c3LrcrxpJ1YXAtYEbVwOcwe+wh6LnltGoxEEZk7Zo9259jch8GiWW6AfRGDQY2FxzWJT7A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.104.0.tgz", + "integrity": "sha512-CD9Tzp3dfMtujHikS0JwTIOaDUCBWItvziUTDYsMXrfVHjfSaGGsaKqIQmNsG1Qg7QdmtMZtMW0pQcHGZQ2OoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.104.0.tgz", + "integrity": "sha512-lARUu7lrZ7CoAm2arLJQkq9Sem2NiC3DaphmPvlf2X+ngkWcYUCRxZanhe+++x/bVzKrwvxormC3IVaulP51mQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.104.0.tgz", + "integrity": "sha512-hnPWrncj89/7AiMt19Juqpa04z8OpJrIf3/ToGkJafZ3A1ZN50atyNKQD5EmGs2/gdaFxF4hG3zUnLKhX9nTtQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.104.0.tgz", + "integrity": "sha512-VegcKzBgUoHQ/p5NyV+GrdEgJEXhX4AD+kCZng+viP/eNsZJgmp2K1jDBCeLSUZIdSmdldK5wlSma3Bf8MKcUQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.104.0.tgz", + "integrity": "sha512-nDtCJIwaNgVNLR1EOgb1Xsz7VL3TOASLRbQEC+wLZycs3CCETlGNQht5OqwXPNjbem61kl4l8dvuEavu8BRT/Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.104.0.tgz", + "integrity": "sha512-E98MNPE3d88D2xUABQYWH2cYqLA/mmOM4XL3CZHaG5dM68sN3fzCyV0ryK5skq4hVK1L2e6jk79z5oLArsCYyA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.104.0.tgz", + "integrity": "sha512-I+xnHDxkInzHBZV+YERIvvrt+GFwEBdgq/RUax5jLl8yHg5iPVq11qtGh3m+2v8zAjQXYPmBbcANNGWwgXqtWQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.104.0.tgz", + "integrity": "sha512-EWHVWfllZGTJFWfjfBKzvS/0uuw6/Z3+o598VTvySamy5fz2f4P1mNElvTi4tGGhbm1sGTYnYRNRW8XBAnGUGg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.104.0.tgz", + "integrity": "sha512-Nj7zeywIvxBRRd0I6NrdGufOOdiwQcAKnkRwtWo5dejPTmR1XM+9iRHJztoKacejW5+VOdEwQrfCjpa8MdLJBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "packages/css/node_modules/@oxc-project/types": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.104.0.tgz", + "integrity": "sha512-We30k+29WpOerl3gCb1v8IFL4vmKTkghjist4TQ89yw7adGDCKFGf8+4mA4H3Ek5ajRzzBZ7BtGY8aIaOv9oFQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "packages/css/node_modules/oxc-parser": { + "version": "0.104.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.104.0.tgz", + "integrity": "sha512-sIEpobLwe7KhW1JdTvBJkNDgjJarHNsx+Q37iRNyASvmlDOD+f8Qirvwp6nFNz7Q5q3JcsJ1dpKM+oMPTDF/dw==", + "license": "MIT", + "dependencies": { + "@oxc-project/types": "^0.104.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm64": "0.104.0", + "@oxc-parser/binding-darwin-arm64": "0.104.0", + "@oxc-parser/binding-darwin-x64": "0.104.0", + "@oxc-parser/binding-freebsd-x64": "0.104.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.104.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.104.0", + "@oxc-parser/binding-linux-arm64-musl": "0.104.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.104.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.104.0", + "@oxc-parser/binding-linux-x64-gnu": "0.104.0", + "@oxc-parser/binding-linux-x64-musl": "0.104.0", + "@oxc-parser/binding-openharmony-arm64": "0.104.0", + "@oxc-parser/binding-wasm32-wasi": "0.104.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.104.0", + "@oxc-parser/binding-win32-x64-msvc": "0.104.0" + } + }, "packages/playwright": { "name": "@knighted/css-playwright-fixture", "version": "0.0.0", diff --git a/packages/css/package.json b/packages/css/package.json index 6b315bd..45982e2 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -79,8 +79,10 @@ "es-module-lexer": "^2.0.0", "lightningcss": "^1.30.2", "node-module-type": "^1.0.1", + "get-tsconfig": "^4.13.0", + "oxc-parser": "^0.104.0", "oxc-resolver": "^11.16.0", - "typescript": "^5.9.3" + "tsconfig-paths": "^4.2.0" }, "peerDependencies": { "@vanilla-extract/integration": "^8.0.0", diff --git a/packages/css/src/moduleGraph.ts b/packages/css/src/moduleGraph.ts index 4b264ae..25160f7 100644 --- a/packages/css/src/moduleGraph.ts +++ b/packages/css/src/moduleGraph.ts @@ -3,12 +3,20 @@ import { builtinModules } from 'node:module' import { existsSync, promises as fs, statSync } from 'node:fs' import { fileURLToPath } from 'node:url' +import { parseSync, Visitor } from 'oxc-parser' +import type { + Argument, + Expression, + ImportExpression, + TSImportEqualsDeclaration, +} from 'oxc-parser' import { ResolverFactory, type NapiResolveOptions, type TsconfigOptions as ResolverTsconfigOptions, } from 'oxc-resolver' -import ts from 'typescript' +import { createMatchPath } from 'tsconfig-paths' +import { getTsconfig } from 'get-tsconfig' import type { CssResolver } from './types.js' @@ -19,6 +27,9 @@ const BUILTIN_SPECIFIERS = new Set([ ...builtinModules.map(mod => `node:${mod}`), ]) +const tsconfigResultCache = new Map() +const tsconfigFsCache = new Map() + export interface ModuleGraphOptions { tsConfig?: string | Record extensions?: string[] @@ -36,8 +47,8 @@ interface CollectOptions { type TsconfigLike = string | Record interface TsconfigPathsResult { - baseUrl: string - paths: Record + absoluteBaseUrl: string + paths: Record } export async function collectStyleImports( @@ -192,70 +203,63 @@ async function readSourceFile(filePath: string): Promise { } function extractModuleSpecifiers(sourceText: string, filePath: string): string[] { - const specifiers: string[] = [] - const source = ts.createSourceFile( - filePath, - sourceText, - ts.ScriptTarget.Latest, - true, - getScriptKind(filePath), - ) + let program + try { + ;({ program } = parseSync(filePath, sourceText, { sourceType: 'unambiguous' })) + } catch { + return [] + } - const addSpecifier = (raw: string) => { + const specifiers: string[] = [] + const addSpecifier = (raw?: string | null) => { + if (!raw) { + return + } const normalized = normalizeSpecifier(raw) if (normalized) { specifiers.push(normalized) } } - const visit = (node: ts.Node) => { - if ( - (ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && - node.moduleSpecifier && - ts.isStringLiteralLike(node.moduleSpecifier) - ) { - addSpecifier(node.moduleSpecifier.text) - } else if (ts.isImportEqualsDeclaration(node)) { - if ( - ts.isExternalModuleReference(node.moduleReference) && - node.moduleReference.expression && - ts.isStringLiteralLike(node.moduleReference.expression) - ) { - addSpecifier(node.moduleReference.expression.text) + const visitor = new Visitor({ + ImportDeclaration(node) { + addSpecifier(node.source?.value) + }, + ExportNamedDeclaration(node) { + if (node.source) { + addSpecifier(node.source.value) } - } else if (ts.isCallExpression(node)) { - if (isRequireCall(node) || isDynamicImport(node)) { - const [argument] = node.arguments - if (argument && ts.isStringLiteralLike(argument)) { - addSpecifier(argument.text) - } + }, + ExportAllDeclaration(node) { + addSpecifier(node.source?.value) + }, + TSImportEqualsDeclaration(node: TSImportEqualsDeclaration) { + const specifier = extractImportEqualsSpecifier(node) + if (specifier) { + addSpecifier(specifier) } - } - ts.forEachChild(node, visit) - } + }, + ImportExpression(node: ImportExpression) { + const specifier = getStringFromExpression(node.source) + if (specifier) { + addSpecifier(specifier) + } + }, + CallExpression(node) { + if (!isRequireLikeCallee(node.callee)) { + return + } + const specifier = getStringFromArgument(node.arguments[0]) + if (specifier) { + addSpecifier(specifier) + } + }, + }) - visit(source) + visitor.visit(program) return specifiers } -function getScriptKind(filePath: string): ts.ScriptKind { - const ext = path.extname(filePath).toLowerCase() - switch (ext) { - case '.ts': - return ts.ScriptKind.TS - case '.tsx': - return ts.ScriptKind.TSX - case '.mts': - return ts.ScriptKind.TS - case '.cts': - return ts.ScriptKind.TS - case '.jsx': - return ts.ScriptKind.JSX - default: - return ts.ScriptKind.JS - } -} - function normalizeSpecifier(raw: string): string { if (!raw) return '' const trimmed = raw.trim() @@ -273,21 +277,65 @@ function normalizeSpecifier(raw: string): string { return withoutQuery } -function isRequireCall(node: ts.CallExpression): boolean { - if (!ts.isIdentifier(node.expression)) { - if ( - ts.isPropertyAccessExpression(node.expression) && - ts.isIdentifier(node.expression.expression) - ) { - return node.expression.expression.text === 'require' +function extractImportEqualsSpecifier( + node: TSImportEqualsDeclaration, +): string | undefined { + if (node.moduleReference.type === 'TSExternalModuleReference') { + return node.moduleReference.expression.value + } + return undefined +} + +function getStringFromArgument(argument: Argument | undefined): string | undefined { + if (!argument || argument.type === 'SpreadElement') { + return undefined + } + return getStringFromExpression(argument) +} + +function getStringFromExpression( + expression: Expression | null | undefined, +): string | undefined { + if (!expression) { + return undefined + } + if (expression.type === 'Literal') { + const literalValue = (expression as { value: unknown }).value + return typeof literalValue === 'string' ? literalValue : undefined + } + if (expression.type === 'TemplateLiteral' && expression.expressions.length === 0) { + const [first] = expression.quasis + return first?.value.cooked ?? first?.value.raw ?? undefined + } + return undefined +} + +function isRequireLikeCallee(expression: Expression): boolean { + const target = unwrapExpression(expression) + if (target.type === 'Identifier') { + return target.name === 'require' + } + if (target.type === 'MemberExpression') { + const object = target.object + if (object.type === 'Identifier') { + return object.name === 'require' } - return false } - return node.expression.text === 'require' + return false } -function isDynamicImport(node: ts.CallExpression): boolean { - return node.expression.kind === ts.SyntaxKind.ImportKeyword +function unwrapExpression(expression: Expression): Expression { + if (expression.type === 'ChainExpression') { + const inner = expression.expression as Expression + if (inner.type === 'CallExpression') { + return unwrapExpression(inner.callee) + } + return unwrapExpression(inner) + } + if (expression.type === 'TSNonNullExpression') { + return unwrapExpression(expression.expression) + } + return expression } function normalizeResolverResult( @@ -398,63 +446,14 @@ function createTsconfigMatcher( if (!config) { return undefined } - const patterns = Object.entries(config.paths).map(([pattern, replacements]) => ({ - pattern, - replacements: Array.isArray(replacements) ? replacements : [replacements], - })) - + const matchPath = createMatchPath(config.absoluteBaseUrl, config.paths) return (specifier: string) => { - for (const { pattern, replacements } of patterns) { - if (!pattern.includes('*')) { - if (pattern === specifier) { - const resolved = resolveReplacements( - replacements, - '', - config.baseUrl, - extensions, - ) - if (resolved) { - return resolved - } - } - continue - } - const [prefix, suffix] = pattern.split('*') - if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) { - continue - } - const wildcard = specifier.slice(prefix.length, specifier.length - suffix.length) - const resolved = resolveReplacements( - replacements, - wildcard, - config.baseUrl, - extensions, - ) - if (resolved) { - return resolved - } - } - return undefined - } -} - -function resolveReplacements( - replacements: readonly string[], - wildcard: string, - baseUrl: string, - extensions: string[], -): string | undefined { - for (const replacement of replacements) { - const substituted = replacement.includes('*') - ? replacement.split('*').join(wildcard) - : replacement - const candidate = path.resolve(baseUrl, substituted) - const resolved = findExistingFile(candidate, extensions) - if (resolved) { - return resolved + const matched = matchPath(specifier, undefined, undefined, extensions) + if (!matched) { + return undefined } + return findExistingFile(matched, extensions) ?? matched } - return undefined } function loadTsconfigPaths( @@ -465,43 +464,59 @@ function loadTsconfigPaths( return undefined } if (typeof input === 'string') { - const configPath = resolveTsconfigPath(input, cwd) - if (!configPath) { - return undefined + const target = path.isAbsolute(input) ? input : path.resolve(cwd, input) + const cached = tsconfigResultCache.get(target) + if (cached !== undefined) { + return cached ?? undefined } - const readResult = ts.readConfigFile(configPath, ts.sys.readFile) - if (readResult.error) { + const result = getTsconfig(target, undefined, tsconfigFsCache as Map) + if (!result) { + tsconfigResultCache.set(target, null) return undefined } - const parsed = ts.parseJsonConfigFileContent( - readResult.config, - ts.sys, - path.dirname(configPath), - undefined, - configPath, + const normalized = normalizeTsconfigCompilerOptions( + result.config.compilerOptions, + path.dirname(result.path), ) - const baseUrl = parsed.options.baseUrl - const paths = parsed.options.paths - if (!baseUrl || !paths) { - return undefined - } - return { - baseUrl: path.isAbsolute(baseUrl) - ? baseUrl - : path.resolve(path.dirname(configPath), baseUrl), - paths, - } + tsconfigResultCache.set(target, normalized ?? null) + return normalized } const compilerOptions = ( - input as { compilerOptions?: { baseUrl?: string; paths?: Record } } + input as { + compilerOptions?: { baseUrl?: string; paths?: Record } + } ).compilerOptions + return normalizeTsconfigCompilerOptions(compilerOptions, cwd) +} + +function normalizeTsconfigCompilerOptions( + compilerOptions: + | { + baseUrl?: string + paths?: Record + } + | undefined, + configDir: string, +): TsconfigPathsResult | undefined { if (!compilerOptions?.baseUrl || !compilerOptions.paths) { return undefined } - return { - baseUrl: path.resolve(cwd, compilerOptions.baseUrl), - paths: compilerOptions.paths, + const normalizedPaths: Record = {} + for (const [pattern, replacements] of Object.entries(compilerOptions.paths)) { + if (!replacements || replacements.length === 0) { + continue + } + normalizedPaths[pattern] = Array.isArray(replacements) + ? [...replacements] + : [replacements] + } + if (Object.keys(normalizedPaths).length === 0) { + return undefined } + const absoluteBaseUrl = path.isAbsolute(compilerOptions.baseUrl) + ? compilerOptions.baseUrl + : path.resolve(configDir, compilerOptions.baseUrl) + return { absoluteBaseUrl, paths: normalizedPaths } } function resolveTsconfigPath(tsconfigPath: string, cwd: string): string | undefined { From cd7c21f7dc0bc90cef2f0b79ef3432144c9df3f0 Mon Sep 17 00:00:00 2001 From: KCM Date: Sun, 21 Dec 2025 15:33:13 -0600 Subject: [PATCH 5/5] docs: update node version. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e36eb4..8f79ccd 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ ## Requirements -- Node.js `>= 22.15.0` +- Node.js `>= 22.17.0` - npm `>= 10.9.0` - Install peer toolchains you intend to use (`sass`, `less`, `@vanilla-extract/integration`, etc.).