@knighted/css 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![codecov](https://codecov.io/gh/knightedcodemonkey/css/graph/badge.svg?token=q93Qqwvq6l)](https://codecov.io/gh/knightedcodemonkey/css)
5
5
  [![NPM version](https://img.shields.io/npm/v/@knighted/css.svg)](https://www.npmjs.com/package/@knighted/css)
6
6
 
7
- `@knighted/css` walks your JavaScript/TypeScript module graph, compiles every CSS-like dependency (plain CSS, Sass/SCSS, Less, vanilla-extract), and ships both the concatenated stylesheet string and optional `.knighted-css.*` imports that keep selectors typed. Use it when you need fully materialized styles ahead of runtime—Shadow DOM surfaces, server-rendered routes, static site builds, or any entry point that should inline CSS without spinning up a full bundler.
7
+ `@knighted/css` walks your module graph, compiles every CSS-like dependency (plain CSS, Sass/SCSS, Less, vanilla-extract), and ships both the concatenated stylesheet string and optional `.knighted-css.*` imports that keep selectors typed. Use it with or without a bundler: run the `css()` API in scripts/SSR pipelines, or lean on the `?knighted-css` loader query so bundlers import compiled CSS alongside modules. Either path yields fully materialized styles for Shadow DOM surfaces, server-rendered routes, static site builds, or any entry point that should inline CSS.
8
8
 
9
9
  ## Why
10
10
 
@@ -23,7 +23,7 @@ I needed a single source of truth for UI components that could drop into both li
23
23
 
24
24
  ## Features
25
25
 
26
- - Traverses module graphs with a built-in walker to find transitive style imports (no bundler required).
26
+ - Traverses module graphs with a built-in walker to find transitive style imports (bundler optional—works standalone or through bundler loaders), including static import attributes (`with { type: "css" }`) for extensionless or aliased specifiers.
27
27
  - Resolution parity via [`oxc-resolver`](https://github.com/oxc-project/oxc-resolver): tsconfig `paths`, package `exports` + `imports`, and extension aliasing (e.g., `.css.js` → `.css.ts`) are honored without wiring up a bundler.
28
28
  - Compiles `*.css`, `*.scss`, `*.sass`, `*.less`, and `*.css.ts` (vanilla-extract) files out of the box.
29
29
  - Optional post-processing via [`lightningcss`](https://github.com/parcel-bundler/lightningcss) for minification, prefixing, media query optimizations, or specificity boosts.
@@ -47,7 +47,7 @@ async function collectStyleImports(entryPath, options) {
47
47
  return;
48
48
  }
49
49
  const specifiers = extractModuleSpecifiers(source, absolutePath);
50
- for (const specifier of specifiers) {
50
+ for (const { specifier, assertedType } of specifiers) {
51
51
  if (!specifier || isBuiltinSpecifier(specifier)) {
52
52
  continue;
53
53
  }
@@ -59,6 +59,13 @@ async function collectStyleImports(entryPath, options) {
59
59
  if (!filter(normalized)) {
60
60
  continue;
61
61
  }
62
+ if (assertedType === 'css') {
63
+ if (!seenStyles.has(normalized)) {
64
+ seenStyles.add(normalized);
65
+ styleOrder.push(normalized);
66
+ }
67
+ continue;
68
+ }
62
69
  if (isStyleExtension(normalized, normalizedStyles)) {
63
70
  if (!seenStyles.has(normalized)) {
64
71
  seenStyles.add(normalized);
@@ -144,26 +151,26 @@ function extractModuleSpecifiers(sourceText, filePath) {
144
151
  return [];
145
152
  }
146
153
  const specifiers = [];
147
- const addSpecifier = (raw) => {
154
+ const addSpecifier = (raw, assertedType) => {
148
155
  if (!raw) {
149
156
  return;
150
157
  }
151
158
  const normalized = normalizeSpecifier(raw);
152
159
  if (normalized) {
153
- specifiers.push(normalized);
160
+ specifiers.push({ specifier: normalized, assertedType });
154
161
  }
155
162
  };
156
163
  const visitor = new oxc_parser_1.Visitor({
157
164
  ImportDeclaration(node) {
158
- addSpecifier(node.source?.value);
165
+ addSpecifier(node.source?.value, getImportAssertedType(node));
159
166
  },
160
167
  ExportNamedDeclaration(node) {
161
168
  if (node.source) {
162
- addSpecifier(node.source.value);
169
+ addSpecifier(node.source.value, getImportAssertedType(node));
163
170
  }
164
171
  },
165
172
  ExportAllDeclaration(node) {
166
- addSpecifier(node.source?.value);
173
+ addSpecifier(node.source?.value, getImportAssertedType(node));
167
174
  },
168
175
  TSImportEqualsDeclaration(node) {
169
176
  const specifier = extractImportEqualsSpecifier(node);
@@ -174,7 +181,7 @@ function extractModuleSpecifiers(sourceText, filePath) {
174
181
  ImportExpression(node) {
175
182
  const specifier = getStringFromExpression(node.source);
176
183
  if (specifier) {
177
- addSpecifier(specifier);
184
+ addSpecifier(specifier, getImportExpressionAssertedType(node));
178
185
  }
179
186
  },
180
187
  CallExpression(node) {
@@ -210,6 +217,126 @@ function normalizeSpecifier(raw) {
210
217
  }
211
218
  return withoutQuery;
212
219
  }
220
+ function getImportAssertedType(node) {
221
+ const attributes = getImportAttributes(node);
222
+ for (const attribute of attributes) {
223
+ const key = getAttributeKey(attribute);
224
+ const value = getAttributeValue(attribute);
225
+ if (key === 'type' && value === 'css') {
226
+ return 'css';
227
+ }
228
+ }
229
+ return undefined;
230
+ }
231
+ function getImportAttributes(node) {
232
+ const attributes = [];
233
+ const candidate = node;
234
+ const withClause = candidate?.withClause;
235
+ if (withClause && Array.isArray(withClause.attributes)) {
236
+ attributes.push(...withClause.attributes);
237
+ }
238
+ const directAttributes = candidate?.attributes;
239
+ if (Array.isArray(directAttributes)) {
240
+ attributes.push(...directAttributes);
241
+ }
242
+ const assertions = candidate?.assertions;
243
+ if (Array.isArray(assertions)) {
244
+ attributes.push(...assertions);
245
+ }
246
+ return attributes;
247
+ }
248
+ function getAttributeKey(attribute) {
249
+ const attr = attribute;
250
+ const key = attr?.key;
251
+ if (!key) {
252
+ return undefined;
253
+ }
254
+ if (typeof key.name === 'string') {
255
+ return key.name;
256
+ }
257
+ const value = key.value;
258
+ if (typeof value === 'string') {
259
+ return value;
260
+ }
261
+ return undefined;
262
+ }
263
+ function getAttributeValue(attribute) {
264
+ const attr = attribute;
265
+ const value = attr?.value;
266
+ if (typeof value === 'string') {
267
+ return value;
268
+ }
269
+ if (value && typeof value.value === 'string') {
270
+ return value.value;
271
+ }
272
+ return undefined;
273
+ }
274
+ function getImportExpressionAssertedType(node) {
275
+ // Stage-3 import attributes proposal shape: import(spec, { with: { type: "css" } })
276
+ const options = node.options;
277
+ if (!options) {
278
+ return undefined;
279
+ }
280
+ const withObject = getStaticObjectProperty(options, 'with');
281
+ if (withObject && isObjectExpression(withObject)) {
282
+ const typeValue = getStaticObjectString(withObject, 'type');
283
+ if (typeValue === 'css') {
284
+ return 'css';
285
+ }
286
+ }
287
+ const assertObject = getStaticObjectProperty(options, 'assert');
288
+ if (assertObject && isObjectExpression(assertObject)) {
289
+ const typeValue = getStaticObjectString(assertObject, 'type');
290
+ if (typeValue === 'css') {
291
+ return 'css';
292
+ }
293
+ }
294
+ return undefined;
295
+ }
296
+ function isObjectExpression(expression) {
297
+ return expression && expression.type === 'ObjectExpression'
298
+ ? expression
299
+ : undefined;
300
+ }
301
+ function getStaticObjectProperty(expression, name) {
302
+ const objectExpression = isObjectExpression(expression);
303
+ if (!objectExpression) {
304
+ return undefined;
305
+ }
306
+ for (const prop of objectExpression.properties) {
307
+ const maybeProp = prop;
308
+ if (maybeProp.type && maybeProp.type !== 'Property') {
309
+ continue;
310
+ }
311
+ const keyName = getPropertyKeyName(maybeProp.key);
312
+ if (keyName === name) {
313
+ const value = maybeProp.value;
314
+ if (value) {
315
+ return value;
316
+ }
317
+ }
318
+ }
319
+ return undefined;
320
+ }
321
+ function getPropertyKeyName(key) {
322
+ if (!key)
323
+ return undefined;
324
+ const asAny = key;
325
+ if (typeof asAny.name === 'string') {
326
+ return asAny.name;
327
+ }
328
+ if (typeof asAny.value === 'string') {
329
+ return asAny.value;
330
+ }
331
+ return undefined;
332
+ }
333
+ function getStaticObjectString(expression, name) {
334
+ const valueExpression = getStaticObjectProperty(expression, name);
335
+ if (!valueExpression) {
336
+ return undefined;
337
+ }
338
+ return getStringFromExpression(valueExpression);
339
+ }
213
340
  function extractImportEqualsSpecifier(node) {
214
341
  if (node.moduleReference.type === 'TSExternalModuleReference') {
215
342
  return node.moduleReference.expression.value;