From 68ccc7a5624dc79c9590c66705c54958524423a0 Mon Sep 17 00:00:00 2001 From: KCM Date: Fri, 26 Dec 2025 13:20:10 -0600 Subject: [PATCH 1/2] fix: loader context. --- packages/css/src/loader.ts | 75 +++++++++++++++++++++++++--- packages/css/test/loaderUnit.test.ts | 66 ++++++++++++++++++++++-- 2 files changed, 132 insertions(+), 9 deletions(-) diff --git a/packages/css/src/loader.ts b/packages/css/src/loader.ts index fcc1313..47c4dfe 100644 --- a/packages/css/src/loader.ts +++ b/packages/css/src/loader.ts @@ -1,3 +1,5 @@ +import path from 'node:path' + import type { LoaderContext, LoaderDefinitionFunction, @@ -203,15 +205,25 @@ function buildProxyRequest(ctx: LoaderContext): string const sanitizedQuery = buildSanitizedQuery(ctx.resourceQuery) const rawRequest = getRawRequest(ctx) if (rawRequest) { - const stripped = stripResourceQuery(rawRequest) - return `${stripped}${sanitizedQuery}` + return rebuildProxyRequestFromRaw(ctx, rawRequest, sanitizedQuery) } const request = `${ctx.resourcePath}${sanitizedQuery}` - const context = ctx.context ?? ctx.rootContext ?? process.cwd() - if (ctx.utils && typeof ctx.utils.contextify === 'function') { - return ctx.utils.contextify(context, request) + return contextifyRequest(ctx, request) +} + +function rebuildProxyRequestFromRaw( + ctx: LoaderContext, + rawRequest: string, + sanitizedQuery: string, +): string { + const stripped = stripResourceQuery(rawRequest) + const loaderDelimiter = stripped.lastIndexOf('!') + const loaderPrefix = loaderDelimiter >= 0 ? stripped.slice(0, loaderDelimiter + 1) : '' + let resource = loaderDelimiter >= 0 ? stripped.slice(loaderDelimiter + 1) : stripped + if (isRelativeSpecifier(resource)) { + resource = makeResourceRelativeToContext(ctx, ctx.resourcePath) } - return request + return `${loaderPrefix}${resource}${sanitizedQuery}` } function getRawRequest(ctx: LoaderContext): string | undefined { @@ -232,6 +244,57 @@ function stripResourceQuery(request: string): string { return idx >= 0 ? request.slice(0, idx) : request } +function contextifyRequest( + ctx: LoaderContext, + request: string, +): string { + const context = ctx.context ?? ctx.rootContext ?? process.cwd() + if (ctx.utils && typeof ctx.utils.contextify === 'function') { + return ctx.utils.contextify(context, request) + } + return rebuildRelativeRequest(context, request) +} + +function rebuildRelativeRequest(context: string, request: string): string { + const queryIndex = request.indexOf('?') + const resourcePath = queryIndex >= 0 ? request.slice(0, queryIndex) : request + const query = queryIndex >= 0 ? request.slice(queryIndex) : '' + const relative = ensureDotPrefixedRelative( + path.relative(context, resourcePath), + resourcePath, + ) + return `${relative}${query}` +} + +function makeResourceRelativeToContext( + ctx: LoaderContext, + resourcePath: string, +): string { + const context = ctx.context ?? path.dirname(resourcePath) + if (ctx.utils && typeof ctx.utils.contextify === 'function') { + const result = ctx.utils.contextify(context, resourcePath) + return stripResourceQuery(result) + } + return ensureDotPrefixedRelative(path.relative(context, resourcePath), resourcePath) +} + +function ensureDotPrefixedRelative(relativePath: string, resourcePath: string): string { + const fallback = relativePath.length > 0 ? relativePath : path.basename(resourcePath) + const normalized = normalizeToPosix(fallback) + if (normalized.startsWith('./') || normalized.startsWith('../')) { + return normalized + } + return `./${normalized}` +} + +function normalizeToPosix(filePath: string): string { + return filePath.split(path.sep).join('/') +} + +function isRelativeSpecifier(specifier: string): boolean { + return specifier.startsWith('./') || specifier.startsWith('../') +} + interface CombinedModuleOptions { emitDefault?: boolean stableSelectorsLiteral?: string diff --git a/packages/css/test/loaderUnit.test.ts b/packages/css/test/loaderUnit.test.ts index b1ba4ca..2558fad 100644 --- a/packages/css/test/loaderUnit.test.ts +++ b/packages/css/test/loaderUnit.test.ts @@ -357,7 +357,7 @@ test('pitch preserves undecodable query fragments when sanitizing requests', asy ) }) -test('pitch reuses rawRequest when building proxy module', async () => { +test('pitch rewrites rawRequest relative to the resource when building proxy module', async () => { const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') const ctx = createMockContext({ resourcePath, @@ -380,9 +380,69 @@ test('pitch reuses rawRequest when building proxy module', async () => { const combinedOutput = String(result ?? '') assert.match( combinedOutput, - /import \* as __knightedModule from "\.\/aliased\/entry\.js\?chunk=demo";/, + /import \* as __knightedModule from "\.\/entry\.js\?chunk=demo";/, ) - assert.match(combinedOutput, /export \* from "\.\/aliased\/entry\.js\?chunk=demo";/) + assert.match(combinedOutput, /export \* from "\.\/entry\.js\?chunk=demo";/) +}) + +test('pitch rewrites relative rawRequest specifiers to resource-local paths', async () => { + const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') + const ctx = createMockContext({ + resourcePath, + context: path.dirname(resourcePath), + resourceQuery: '?knighted-css&combined', + _module: { + rawRequest: './components/entry.js?knighted-css&combined', + } as LoaderContext['_module'], + loadModule: (_request: string, callback: LoaderCallback) => { + callback(null, 'export const stub = 1;') + }, + }) + + const result = await pitch.call( + ctx as LoaderContext, + `${resourcePath}?knighted-css&combined`, + '', + {}, + ) + + const combinedOutput = String(result ?? '') + assert.match( + combinedOutput, + /import \* as __knightedModule from "\.\/entry\.js";/, + 'should rebase proxy specifier next to the resource', + ) + assert.match(combinedOutput, /export \* from "\.\/entry\.js";/) +}) + +test('pitch preserves inline loader prefixes while rebasing relative specifiers', async () => { + const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') + const ctx = createMockContext({ + resourcePath, + context: path.dirname(resourcePath), + resourceQuery: '?knighted-css&combined&chunk=demo', + _module: { + rawRequest: 'style-loader!./components/entry.js?knighted-css&combined&chunk=demo', + } as LoaderContext['_module'], + loadModule: (_request: string, callback: LoaderCallback) => { + callback(null, 'export const stub = 1;') + }, + }) + + const result = await pitch.call( + ctx as LoaderContext, + `${resourcePath}?knighted-css&combined&chunk=demo`, + '', + {}, + ) + + const combinedOutput = String(result ?? '') + assert.match( + combinedOutput, + /import \* as __knightedModule from "style-loader!\.\/entry\.js\?chunk=demo";/, + 'should retain loader prefixes but drop the duplicated folder segment', + ) + assert.match(combinedOutput, /export \* from "style-loader!\.\/entry\.js\?chunk=demo";/) }) test('combined modules skip default export for vanilla style entries', async () => { From e1e6ffd369f6b6c242cfe57dd26d2c40c79a7472 Mon Sep 17 00:00:00 2001 From: KCM Date: Fri, 26 Dec 2025 14:49:16 -0600 Subject: [PATCH 2/2] test: add playwright regression. --- package-lock.json | 212 ++++++++++-------- package.json | 2 +- packages/css/package.json | 2 +- packages/playwright/package.json | 4 +- packages/playwright/src/lit-react/app.css | 6 +- .../components/nested-combined-entry.css | 42 ++++ .../components/nested-combined-entry.tsx | 26 +++ .../nested-combined-card.tsx | 26 +++ .../playwright/src/lit-react/showcase.tsx | 10 + packages/playwright/test/lit-react.spec.ts | 27 +++ 10 files changed, 260 insertions(+), 97 deletions(-) create mode 100644 packages/playwright/src/lit-react/cards/nested-combined-card/components/nested-combined-entry.css create mode 100644 packages/playwright/src/lit-react/cards/nested-combined-card/components/nested-combined-entry.tsx create mode 100644 packages/playwright/src/lit-react/cards/nested-combined-card/nested-combined-card.tsx diff --git a/package-lock.json b/package-lock.json index 4b56880..a7d8edf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "npm": ">= 10.9.0" }, "optionalDependencies": { - "@oxc-parser/binding-wasm32-wasi": "^0.99.0" + "@oxc-parser/binding-wasm32-wasi": "^0.105.0" } }, "node_modules/@babel/code-frame": { @@ -1182,13 +1182,14 @@ } }, "node_modules/@knighted/jsx": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@knighted/jsx/-/jsx-1.4.1.tgz", - "integrity": "sha512-JHdbNEeTgOL4jBYHFMqBCuHrQ3XnMZ1RZBvjFeam2fNW+S20kLJjxjp4Lz6CO9DCGcAZtbb80UO8XVyCMGb/cQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@knighted/jsx/-/jsx-1.6.1.tgz", + "integrity": "sha512-sQpx+b/6PrbriH7oK2DRdJYppYq1HIV2W9EVea8w5sWhmDARl+uIqvmG4jOTpQcFOTziRtKlZ0Jh2ybcYuL5dA==", "license": "MIT", "dependencies": { "magic-string": "^0.30.21", - "oxc-parser": "^0.99.0", + "oxc-parser": "^0.105.0", + "property-information": "^7.1.0", "tar": "^7.4.3" }, "bin": { @@ -1198,9 +1199,9 @@ "node": ">=22.17.0" }, "optionalDependencies": { - "@oxc-parser/binding-darwin-arm64": "^0.99.0", - "@oxc-parser/binding-linux-x64-gnu": "^0.99.0", - "@oxc-parser/binding-wasm32-wasi": "^0.99.0" + "@oxc-parser/binding-darwin-arm64": "^0.105.0", + "@oxc-parser/binding-linux-x64-gnu": "^0.105.0", + "@oxc-parser/binding-wasm32-wasi": "^0.105.0" }, "peerDependencies": { "jsdom": "*", @@ -1220,9 +1221,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.99.0.tgz", - "integrity": "sha512-V4jhmKXgQQdRnm73F+r3ZY4pUEsijQeSraFeaCGng7abSNJGs76X6l82wHnmjLGFAeY00LWtjcELs7ZmbJ9+lA==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.105.0.tgz", + "integrity": "sha512-YfphT1yP5Z2rsIAId4DeaumIqnBVwIyzjnGtb3scSBjIPclfhc6asu5ci0gPcXkfswzrPxNseZ0eWUaPBo1Cdg==", "cpu": [ "arm64" ], @@ -1236,9 +1237,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.99.0.tgz", - "integrity": "sha512-Rp41nf9zD5FyLZciS9l1GfK8PhYqrD5kEGxyTOA2esTLeAy37rZxetG2E3xteEolAkeb2WDkVrlxPtibeAncMg==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.105.0.tgz", + "integrity": "sha512-uh1bxJT4lCU0IHkx6N8fgl9JBYlhWQDBZDpayn1ugtGW5rwB9M8dYMemeQoXJZiVxUun9SdDkBlrFv12EapwyA==", "cpu": [ "arm64" ], @@ -1252,9 +1253,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.99.0.tgz", - "integrity": "sha512-WVonp40fPPxo5Gs0POTI57iEFv485TvNKOHMwZRhigwZRhZY2accEAkYIhei9eswF4HN5B44Wybkz7Gd1Qr/5Q==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.105.0.tgz", + "integrity": "sha512-/sqErZWA4/jLd4QYHRaNgn/kRyHodcLFTeNrkAqYxIDlgnNRI7vLeT1zAj4CH7FOiWCtlBZb1NpubjXOraKEKg==", "cpu": [ "x64" ], @@ -1268,9 +1269,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.99.0.tgz", - "integrity": "sha512-H30bjOOttPmG54gAqu6+HzbLEzuNOYO2jZYrIq4At+NtLJwvNhXz28Hf5iEAFZIH/4hMpLkM4VN7uc+5UlNW3Q==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.105.0.tgz", + "integrity": "sha512-1QhbXXkkz7w+l+iftIbsjvLOLo1g0CQkIHWBLZcsmz8vEbbAOa4VODWTaof4VpvarbHzELs3MMKkPD48bvFmrA==", "cpu": [ "x64" ], @@ -1284,25 +1285,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.99.0.tgz", - "integrity": "sha512-0Z/Th0SYqzSRDPs6tk5lQdW0i73UCupnim3dgq2oW0//UdLonV/5wIZCArfKGC7w9y4h8TxgXpgtIyD1kKzzlQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.99.0.tgz", - "integrity": "sha512-xo0wqNd5bpbzQVNpAIFbHk1xa+SaS/FGBABCd942SRTnrpxl6GeDj/s1BFaGcTl8MlwlKVMwOcyKrw/2Kdfquw==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.105.0.tgz", + "integrity": "sha512-VU6tqS8pJJsiYbf3cQ6yAgRLt+3R1FZgF5LkzM5OUm64d7z62UPgLwr+pUSHqtfRU8QnL7M3t+Qerw1t/odTSQ==", "cpu": [ "arm" ], @@ -1316,9 +1301,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.99.0.tgz", - "integrity": "sha512-u26I6LKoLTPTd4Fcpr0aoAtjnGf5/ulMllo+QUiBhupgbVCAlaj4RyXH/mvcjcsl2bVBv9E/gYJZz2JjxQWXBA==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.105.0.tgz", + "integrity": "sha512-qP7oLZal69E0HSE/jjOHMBpCLIwqXHej1pwIo07pthjhRe2YZb1NBDE+Lsxzd+gTvr7WmmA0X52Ce2fvrFZ8GA==", "cpu": [ "arm64" ], @@ -1332,9 +1317,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.99.0.tgz", - "integrity": "sha512-qhftDo2D37SqCEl3ZTa367NqWSZNb1Ddp34CTmShLKFrnKdNiUn55RdokLnHtf1AL5ssaQlYDwBECX7XiBWOhw==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.105.0.tgz", + "integrity": "sha512-PoAvVQofT7j/zlWdERNYb2xQgsu5zLGDMJZKkghd2eNijnAhegswHSyO497u4Bm0mwGlnqpHDa8GQLTR08OIuA==", "cpu": [ "arm64" ], @@ -1348,9 +1333,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.99.0.tgz", - "integrity": "sha512-zxn/xkf519f12FKkpL5XwJipsylfSSnm36h6c1zBDTz4fbIDMGyIhHfWfwM7uUmHo9Aqw1pLxFpY39Etv398+Q==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.105.0.tgz", + "integrity": "sha512-dDhNsKFIqqyUFl+WSbStVueJDEVY5WYexJYHOSf3aDOhT4mxVVj+z0iKU0ZjSSWStepHXIgBD1F2E+m4pvPQZw==", "cpu": [ "riscv64" ], @@ -1364,9 +1349,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.99.0.tgz", - "integrity": "sha512-Y1eSDKDS5E4IVC7Oxw+NbYAKRmJPMJTIjW+9xOWwteDHkFqpocKe0USxog+Q1uhzalD9M0p9eXWEWdGQCMDBMQ==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.105.0.tgz", + "integrity": "sha512-/6i8VVnMRU011EEH9FbQDvYFocDMlV/N7VDUEbaiCJqABH+bk6Mx5hMn1dMP1gfuF7wqpxPGejMzADXKvt6tnA==", "cpu": [ "s390x" ], @@ -1380,9 +1365,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.99.0.tgz", - "integrity": "sha512-YVJMfk5cFWB8i2/nIrbk6n15bFkMHqWnMIWkVx7r2KwpTxHyFMfu2IpeVKo1ITDSmt5nBrGdLHD36QRlu2nDLg==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.105.0.tgz", + "integrity": "sha512-tv4ym2FmK4lUntG+fWFw0d/UuiGv05RGmyTnCLsP33CjjUyTl85QZSojxkoIPoy4DcxOddUkJkUQWHAw7tcCUQ==", "cpu": [ "x64" ], @@ -1396,9 +1381,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.99.0.tgz", - "integrity": "sha512-2+SDPrie5f90A1b9EirtVggOgsqtsYU5raZwkDYKyS1uvJzjqHCDhG/f4TwQxHmIc5YkczdQfwvN91lwmjsKYQ==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.105.0.tgz", + "integrity": "sha512-f8e3lsBaxV3E/MxgmA7Ghf8kGvA5yxvGrUAH+RO+28UbomFj/z1d8YiHVpk7Za/VhLXFW7HCxhAepY9xJdEZVA==", "cpu": [ "x64" ], @@ -1411,10 +1396,26 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.105.0.tgz", + "integrity": "sha512-b1Y3qfgpf594Ro4N8UImobmKUq4GrkyXuNzs94V2P0u65qCVNeTsrvWpKtTdQltK1j+SvNQpisFbabYrxRI63A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.99.0.tgz", - "integrity": "sha512-EaB3AvsxqdNUhh9FOoAxRZ2L4PCRwDlDb//QXItwyOJrX7XS+uGK9B1KEUV4FZ/7rDhHsWieLt5e07wl2Ti5AQ==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.105.0.tgz", + "integrity": "sha512-WtMhV8nLZi260FyZr21ukVa+IUCSGvQ+r6LAAHAzcGMgmXUp+NGBiySdgFmBExq8SVabGYeQ0OXFjL++DSfttQ==", "cpu": [ "arm64" ], @@ -1428,9 +1429,9 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.99.0.tgz", - "integrity": "sha512-sJN1Q8h7ggFOyDn0zsHaXbP/MklAVUvhrbq0LA46Qum686P3SZQHjbATqJn9yaVEvaSKXCshgl0vQ1gWkGgpcQ==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.105.0.tgz", + "integrity": "sha512-ycJv2HahQRUxcSW/FeC5OFDw1yO2W2LVMCI8uwPhcGCuWIfuS+rDZWCOTarHCSdf3/sIxWjSYtSgEDFdxE61sw==", "cpu": [ "x64" ], @@ -1444,21 +1445,21 @@ } }, "node_modules/@knighted/jsx/node_modules/@oxc-project/types": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.99.0.tgz", - "integrity": "sha512-LLDEhXB7g1m5J+woRSgfKsFPS3LhR9xRhTeIoEBm5WrkwMxn6eZ0Ld0c0K5eHB57ChZX6I3uSmmLjZ8pcjlRcw==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.105.0.tgz", + "integrity": "sha512-KUZKnfZWMGI+7Dl0FoW574LI+xOkl2cvX5JKdnHH0VSKKqcft1UbthN1Mwa7VOwhNaqzKWUHnqC5MJhWgVJnYw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@knighted/jsx/node_modules/oxc-parser": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.99.0.tgz", - "integrity": "sha512-MpS1lbd2vR0NZn1v0drpgu7RUFu3x9Rd0kxExObZc2+F+DIrV0BOMval/RO3BYGwssIOerII6iS8EbbpCCZQpQ==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.105.0.tgz", + "integrity": "sha512-9O+8zEIC/7Ly519wuKVAFHbcqeZxW8bOHXXE+BZYf8BA/J53SEJNvevMH3VI0ptrs4SMPAaxOW6K1jDJTSl0cg==", "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.99.0" + "@oxc-project/types": "^0.105.0" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -1467,21 +1468,21 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-parser/binding-android-arm64": "0.99.0", - "@oxc-parser/binding-darwin-arm64": "0.99.0", - "@oxc-parser/binding-darwin-x64": "0.99.0", - "@oxc-parser/binding-freebsd-x64": "0.99.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.99.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.99.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.99.0", - "@oxc-parser/binding-linux-arm64-musl": "0.99.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.99.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.99.0", - "@oxc-parser/binding-linux-x64-gnu": "0.99.0", - "@oxc-parser/binding-linux-x64-musl": "0.99.0", - "@oxc-parser/binding-wasm32-wasi": "0.99.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.99.0", - "@oxc-parser/binding-win32-x64-msvc": "0.99.0" + "@oxc-parser/binding-android-arm64": "0.105.0", + "@oxc-parser/binding-darwin-arm64": "0.105.0", + "@oxc-parser/binding-darwin-x64": "0.105.0", + "@oxc-parser/binding-freebsd-x64": "0.105.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.105.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.105.0", + "@oxc-parser/binding-linux-arm64-musl": "0.105.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.105.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.105.0", + "@oxc-parser/binding-linux-x64-gnu": "0.105.0", + "@oxc-parser/binding-linux-x64-musl": "0.105.0", + "@oxc-parser/binding-openharmony-arm64": "0.105.0", + "@oxc-parser/binding-wasm32-wasi": "0.105.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.105.0", + "@oxc-parser/binding-win32-x64-msvc": "0.105.0" } }, "node_modules/@knighted/module": { @@ -1722,6 +1723,23 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@knighted/specifier/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", + "integrity": "sha512-DKA4j0QerUWSMADziLM5sAyM7V53Fj95CV9SjP77bPfEfT7MnvFKnneaRMqPK1cpzjAGiQF52OBUIKyk0dwOQA==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.7" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@knighted/specifier/node_modules/@oxc-parser/binding-win32-arm64-msvc": { "version": "0.99.0", "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.99.0.tgz", @@ -2129,16 +2147,16 @@ } }, "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", - "integrity": "sha512-DKA4j0QerUWSMADziLM5sAyM7V53Fj95CV9SjP77bPfEfT7MnvFKnneaRMqPK1cpzjAGiQF52OBUIKyk0dwOQA==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.105.0.tgz", + "integrity": "sha512-n4ybGB3b2Gttm6mVUYMoMI09ueK7CFLl1u4mQ0fzk6SAHUwbOmC+HHGb9tERSYVxw14uIrAn4Prk9yxriAyaRQ==", "cpu": [ "wasm32" ], "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" + "@napi-rs/wasm-runtime": "^1.1.0" }, "engines": { "node": ">=14.0.0" @@ -9341,6 +9359,16 @@ "dev": true, "license": "MIT" }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -11931,7 +11959,7 @@ }, "packages/css": { "name": "@knighted/css", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "dependencies": { "es-module-lexer": "^2.0.0", @@ -12231,8 +12259,8 @@ "name": "@knighted/css-playwright-fixture", "version": "0.0.0", "dependencies": { - "@knighted/css": "1.0.3", - "@knighted/jsx": "^1.4.1", + "@knighted/css": "1.0.4", + "@knighted/jsx": "^1.6.1", "lit": "^3.2.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/package.json b/package.json index e6e6ba3..3ba8583 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,6 @@ "*.{js,jsx,ts,tsx,mjs,cjs,cts,mts,json,md,css,scss,html}": "prettier --check" }, "optionalDependencies": { - "@oxc-parser/binding-wasm32-wasi": "^0.99.0" + "@oxc-parser/binding-wasm32-wasi": "^0.105.0" } } diff --git a/packages/css/package.json b/packages/css/package.json index 0dba00c..da56df7 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -1,6 +1,6 @@ { "name": "@knighted/css", - "version": "1.0.3", + "version": "1.0.4", "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", diff --git a/packages/playwright/package.json b/packages/playwright/package.json index b627161..9b986e6 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -15,8 +15,8 @@ "pretest": "npm run build" }, "dependencies": { - "@knighted/css": "1.0.3", - "@knighted/jsx": "^1.4.1", + "@knighted/css": "1.0.4", + "@knighted/jsx": "^1.6.1", "lit": "^3.2.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/packages/playwright/src/lit-react/app.css b/packages/playwright/src/lit-react/app.css index 7b2c243..8e5c38a 100644 --- a/packages/playwright/src/lit-react/app.css +++ b/packages/playwright/src/lit-react/app.css @@ -1,6 +1,10 @@ -@import '@knighted/css/stable/stable.css'; +@layer knighted.stable; @layer knighted.stable { + :root { + --knighted-stable-namespace: 'knighted'; + } + .knighted-layer-glow { box-shadow: 0 25px 55px rgba(14, 165, 233, 0.35); } diff --git a/packages/playwright/src/lit-react/cards/nested-combined-card/components/nested-combined-entry.css b/packages/playwright/src/lit-react/cards/nested-combined-card/components/nested-combined-entry.css new file mode 100644 index 0000000..d9bb4c3 --- /dev/null +++ b/packages/playwright/src/lit-react/cards/nested-combined-card/components/nested-combined-entry.css @@ -0,0 +1,42 @@ +.nested-combined-card { + background: radial-gradient(circle at top left, #eef2ff 0%, #c7d2fe 45%, #a5b4fc 100%); + border-radius: 22px; + box-shadow: 0 12px 32px rgba(15, 23, 42, 0.18); + color: #0f172a; + display: flex; + flex-direction: column; + gap: 0.85rem; + padding: 1.5rem; +} + +.nested-entry { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.nested-entry__subtitle { + color: rgba(15, 23, 42, 0.6); + font-size: 0.85rem; + letter-spacing: 0.05em; +} + +.nested-entry__badge { + align-self: flex-start; + background: #312e81; + border-radius: 999px; + color: #ede9fe; + font-size: 0.7rem; + font-weight: 700; + letter-spacing: 0.08em; + padding: 0.2rem 0.8rem; + text-transform: uppercase; +} + +.nested-details { + border-top: 1px solid rgba(49, 46, 129, 0.35); + font-size: 0.92rem; + line-height: 1.4; + margin: 0; + padding-top: 0.9rem; +} diff --git a/packages/playwright/src/lit-react/cards/nested-combined-card/components/nested-combined-entry.tsx b/packages/playwright/src/lit-react/cards/nested-combined-card/components/nested-combined-entry.tsx new file mode 100644 index 0000000..b277b16 --- /dev/null +++ b/packages/playwright/src/lit-react/cards/nested-combined-card/components/nested-combined-entry.tsx @@ -0,0 +1,26 @@ +import './nested-combined-entry.css' + +export const NESTED_COMBINED_TEST_ID = 'dialect-nested-combined' + +export function NestedCombinedBadge() { + return Nested combined loader +} + +export function NestedCombinedDetails() { + return ( +

+ The Lit host lives one directory above this entry module, mirroring the css-jsx-app + structure that once broke `?knighted-css&combined` relative imports. +

+ ) +} + +export default function NestedCombinedEntry() { + return ( +
+

Parent + Child dirs

+ Nested combined example + +
+ ) +} diff --git a/packages/playwright/src/lit-react/cards/nested-combined-card/nested-combined-card.tsx b/packages/playwright/src/lit-react/cards/nested-combined-card/nested-combined-card.tsx new file mode 100644 index 0000000..40d5608 --- /dev/null +++ b/packages/playwright/src/lit-react/cards/nested-combined-card/nested-combined-card.tsx @@ -0,0 +1,26 @@ +import { asKnightedCssCombinedModule } from '@knighted/css/loader-helpers' + +import * as nestedModule from './components/nested-combined-entry.js?knighted-css&combined' +import { NESTED_COMBINED_TEST_ID } from './components/nested-combined-entry.js' + +const { + default: NestedCombinedEntry, + NestedCombinedBadge, + NestedCombinedDetails, + knightedCss, +} = asKnightedCssCombinedModule( + nestedModule, +) + +export const nestedCombinedCardCss = knightedCss +export { NESTED_COMBINED_TEST_ID } from './components/nested-combined-entry.js' + +export function NestedCombinedCard() { + return ( +
+ + + +
+ ) +} diff --git a/packages/playwright/src/lit-react/showcase.tsx b/packages/playwright/src/lit-react/showcase.tsx index b1f9a3a..77670e1 100644 --- a/packages/playwright/src/lit-react/showcase.tsx +++ b/packages/playwright/src/lit-react/showcase.tsx @@ -24,6 +24,11 @@ import { COMBINED_CARD_TEST_ID, combinedCardCss, } from './cards/combined-card/combined-card.js' +import { + NestedCombinedCard, + NESTED_COMBINED_TEST_ID, + nestedCombinedCardCss, +} from './cards/nested-combined-card/nested-combined-card.js' import { CombinedTypesCard, COMBINED_TYPES_TEST_ID, @@ -70,6 +75,11 @@ const cards: DialectCard[] = [ css: combinedCardCss, Component: CombinedCard, }, + { + id: NESTED_COMBINED_TEST_ID, + css: nestedCombinedCardCss, + Component: NestedCombinedCard, + }, { id: COMBINED_TYPES_TEST_ID, css: combinedTypesCardCss, diff --git a/packages/playwright/test/lit-react.spec.ts b/packages/playwright/test/lit-react.spec.ts index 4bee798..d612df9 100644 --- a/packages/playwright/test/lit-react.spec.ts +++ b/packages/playwright/test/lit-react.spec.ts @@ -12,6 +12,7 @@ const dialectCases = [ { id: 'dialect-css-modules', property: 'color' }, { id: 'dialect-vanilla', property: 'color' }, { id: 'dialect-combined', property: 'background-image' }, + { id: 'dialect-nested-combined', property: 'background-image' }, { id: 'dialect-combined-types', property: 'border-color' }, { id: 'dialect-named-only', property: 'background-image' }, ] @@ -146,6 +147,32 @@ test.describe('Lit + React wrapper demo', () => { expect(metrics.background).toContain('linear-gradient') }) + test('nested combined import works when the host lives one directory up', async ({ + page, + }) => { + const card = page.getByTestId('dialect-nested-combined') + await expect(card).toBeVisible() + const metrics = await card.evaluate(node => { + const el = node as HTMLElement + const entry = el.querySelector( + '[data-testid="nested-combined-entry"]', + ) as HTMLElement | null + const details = el.querySelector( + '[data-testid="nested-combined-details"]', + ) as HTMLElement | null + const style = getComputedStyle(el) + return { + entryText: entry?.textContent?.replace(/\s+/g, ' ').trim() ?? '', + detailsText: details?.textContent?.replace(/\s+/g, ' ').trim() ?? '', + background: style.getPropertyValue('background-image').trim(), + } + }) + + expect(metrics.entryText).toContain('Nested combined example') + expect(metrics.detailsText).toContain('css-jsx-app') + expect(metrics.background).toContain('radial-gradient') + }) + test('combined & types import keeps runtime selectors synced', async ({ page }) => { const card = page.getByTestId('dialect-combined-types') await expect(card).toBeVisible()