@wix/zero-config-implementation 1.25.0 → 1.26.0

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.
@@ -1,6 +1,8 @@
1
1
  export interface CSSProperty {
2
2
  name: string;
3
3
  value: string;
4
+ /** CSS variable names referenced via var() in this property's value */
5
+ varRefs?: string[];
4
6
  }
5
7
  export interface CssSelectorMatch {
6
8
  selector: string;
@@ -14,6 +14,7 @@ export interface ExtractedCssInfo {
14
14
  api: CSSParserAPI;
15
15
  properties: Map<string, string>;
16
16
  customProperties: Map<string, string>;
17
+ isCssModule: boolean;
17
18
  }
18
19
  export interface ComponentInfoWithCss extends CoupledComponentInfo {
19
20
  css: ExtractedCssInfo[];
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "registry": "https://registry.npmjs.org/",
5
5
  "access": "public"
6
6
  },
7
- "version": "1.25.0",
7
+ "version": "1.26.0",
8
8
  "description": "Core library for extracting component manifests from JS and CSS files",
9
9
  "type": "module",
10
10
  "main": "dist/index.js",
@@ -83,5 +83,5 @@
83
83
  ]
84
84
  }
85
85
  },
86
- "falconPackageHash": "e76e4e02feadede0d8c9d011d32118218b349b5ab948a12605907e98"
86
+ "falconPackageHash": "3dd0aab1018f95841806376f73376b94c1dcf15d586caa70806a01f8"
87
87
  }
@@ -345,7 +345,8 @@ function extractPropertyNameAndValue(decl: LightningDecl): CSSProperty | null {
345
345
  if (name) {
346
346
  const value = propertyValueToString(decl)
347
347
  if (value) {
348
- return { name, value }
348
+ const varRefs = extractVarNamesFromTokens(unparsedValue.value ?? [])
349
+ return { name, value, ...(varRefs.length > 0 && { varRefs }) }
349
350
  }
350
351
  }
351
352
  return null
@@ -412,7 +413,7 @@ function serializeCustomPropertyValue(valueArray: unknown[]): string {
412
413
  } else if (tokenValue.type === 'dimension') {
413
414
  parts.push(`${tokenValue.value}${tokenValue.unit}`)
414
415
  } else if (tokenValue.type === 'white-space') {
415
- parts.push(tokenValue.value as string)
416
+ // skip — parts.join(' ') already handles separation
416
417
  }
417
418
  }
418
419
  }
@@ -4,6 +4,45 @@ import type { ExtractedCssInfo } from '../../index'
4
4
  import type { ExtractedElement } from '../react/extractors/core/tree-builder'
5
5
  import type { CSSProperty, CssSelectorMatch, MatchedCssData } from './types'
6
6
 
7
+ /**
8
+ * Matches selectors composed entirely of one or more class selectors with no combinators,
9
+ * pseudo-classes, tag names, or IDs — e.g. `.wrapper`, `.wrapper.active`.
10
+ * Used to identify selectors eligible for CSS module prefix matching.
11
+ */
12
+ const SIMPLE_CLASS_SELECTOR_PATTERN = /^(\.[a-zA-Z][\w-]*)+$/
13
+
14
+ /**
15
+ * Finds DOM elements matching a CSS selector, with special handling for CSS module files.
16
+ *
17
+ * For CSS module files, class names in the source CSS (e.g. `.wrapper`) are hashed at build
18
+ * time into names like `wrapper_AbCdE`. When the selector is a simple class selector
19
+ * (e.g. `.wrapper` or `.wrapper.active`), this function matches elements whose class list
20
+ * contains a class that exactly equals the local name OR starts with `localName_` /
21
+ * `_localName_` (the two most common Vite CSS module hash patterns).
22
+ *
23
+ * For non-module files, or for selectors that contain combinators or non-class parts
24
+ * (e.g. `div .wrapper`, `#id`), falls back to a direct Cheerio query.
25
+ */
26
+ function findMatchingElements($: cheerio.CheerioAPI, domSelector: string, isCssModule: boolean) {
27
+ if (isCssModule && SIMPLE_CLASS_SELECTOR_PATTERN.test(domSelector)) {
28
+ const classNames = domSelector.split('.').filter(Boolean)
29
+ return $('[class]').filter((_index, element) => {
30
+ const classAttr = $(element).attr('class')
31
+ if (!classAttr) return false
32
+ const elementClasses = classAttr.trim().split(/\s+/)
33
+ return classNames.every((localName) =>
34
+ elementClasses.some(
35
+ (elementClass) =>
36
+ elementClass === localName ||
37
+ elementClass.startsWith(`${localName}_`) ||
38
+ elementClass.startsWith(`_${localName}_`),
39
+ ),
40
+ )
41
+ })
42
+ }
43
+ return $(domSelector)
44
+ }
45
+
7
46
  export function matchCssSelectors(
8
47
  html: string,
9
48
  elements: ExtractedElement[],
@@ -28,8 +67,8 @@ export function matchCssSelectors(
28
67
  if (!domSelector) continue
29
68
 
30
69
  try {
31
- $(domSelector).each((_, elem) => {
32
- const traceId = $(elem).attr(TRACE_ATTR)
70
+ findMatchingElements($, domSelector, cssInfo.isCssModule).each((_index, element) => {
71
+ const traceId = $(element).attr(TRACE_ATTR)
33
72
  if (!traceId) return
34
73
 
35
74
  // Use reduce to separate regular and custom properties in one pass
@@ -37,13 +76,13 @@ export function matchCssSelectors(
37
76
  regular: CSSProperty[]
38
77
  custom: Record<string, string>
39
78
  }>(
40
- (acc, prop) => {
79
+ (accumulator, prop) => {
41
80
  if (prop.name.startsWith('--')) {
42
- acc.custom[prop.name] = prop.value
81
+ accumulator.custom[prop.name] = prop.value
43
82
  } else {
44
- acc.regular.push(prop)
83
+ accumulator.regular.push(prop)
45
84
  }
46
- return acc
85
+ return accumulator
47
86
  },
48
87
  { regular: [], custom: {} },
49
88
  )
@@ -55,7 +94,7 @@ export function matchCssSelectors(
55
94
 
56
95
  // Track which CSS custom properties are used (via var()) by this element
57
96
  for (const regularProp of regular) {
58
- for (const varName of extractVarRefs(regularProp.value)) {
97
+ for (const varName of regularProp.varRefs ?? []) {
59
98
  const traceIdSet = varUsedByTraceId.get(varName) ?? new Set()
60
99
  traceIdSet.add(traceId)
61
100
  varUsedByTraceId.set(varName, traceIdSet)
@@ -81,20 +120,6 @@ export function matchCssSelectors(
81
120
  }
82
121
  }
83
122
 
84
- /**
85
- * Extracts all CSS custom property names referenced via var() in a property value string.
86
- */
87
- function extractVarRefs(value: string): string[] {
88
- const varNames: string[] = []
89
- const varPattern = /var\(\s*(--[\w-]+)/g
90
- let varMatch = varPattern.exec(value)
91
- while (varMatch !== null) {
92
- varNames.push(varMatch[1])
93
- varMatch = varPattern.exec(value)
94
- }
95
- return varNames
96
- }
97
-
98
123
  function enrichElements(
99
124
  elements: ExtractedElement[],
100
125
  matchesByTraceId: Map<string, CssSelectorMatch[]>,
@@ -1,6 +1,8 @@
1
1
  export interface CSSProperty {
2
2
  name: string
3
3
  value: string
4
+ /** CSS variable names referenced via var() in this property's value */
5
+ varRefs?: string[]
4
6
  }
5
7
 
6
8
  export interface CssSelectorMatch {
@@ -36,6 +36,7 @@ export interface ExtractedCssInfo {
36
36
  api: CSSParserAPI
37
37
  properties: Map<string, string>
38
38
  customProperties: Map<string, string>
39
+ isCssModule: boolean
39
40
  }
40
41
 
41
42
  export interface ComponentInfoWithCss extends CoupledComponentInfo {
@@ -378,6 +379,7 @@ function extractCssInfo(
378
379
  api,
379
380
  properties,
380
381
  customProperties,
382
+ isCssModule: /\.module\.(css|scss|sass)$/.test(cssPath),
381
383
  })
382
384
  } catch (error) {
383
385
  warnings.push({
@@ -1,5 +1,4 @@
1
1
  import { createRequire } from 'node:module'
2
- import { pascalCase } from 'case-anything'
3
2
  import { ResultAsync, errAsync, okAsync } from 'neverthrow'
4
3
  import type { ComponentType } from 'react'
5
4
  import { IoError } from './errors'
@@ -37,12 +36,8 @@ export function loadModule(entryPath: string): ResultAsync<Record<string, unknow
37
36
  })
38
37
  }
39
38
 
40
- function isPascalCase(name: string): boolean {
41
- return name.length > 0 && pascalCase(name) === name
42
- }
43
-
44
39
  function isComponent(value: unknown): value is ComponentType<unknown> {
45
- if (typeof value === 'function') return isPascalCase(value.name)
40
+ if (typeof value === 'function') return true
46
41
  // React.memo() and React.forwardRef() return objects, not functions
47
42
  if (typeof value === 'object' && value !== null && '$$typeof' in value) return true
48
43
  return false
@@ -51,7 +46,7 @@ function isComponent(value: unknown): value is ComponentType<unknown> {
51
46
  export function findComponent(moduleExports: Record<string, unknown>, name: string): ComponentType<unknown> | null {
52
47
  // Direct named export
53
48
  const direct = moduleExports[name]
54
- if ((typeof direct === 'function' && isPascalCase(name)) || isComponent(direct)) {
49
+ if (isComponent(direct)) {
55
50
  return direct as ComponentType<unknown>
56
51
  }
57
52