@wix/zero-config-implementation 1.70.0 → 1.72.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.
Files changed (42) hide show
  1. package/dist/{index-D_XmjZkE.js → index-CSi4SZpQ.js} +1 -1
  2. package/dist/{index-DnxDZx90.js → index-CdHu4-o-.js} +20462 -18723
  3. package/dist/index.d.ts +15 -8
  4. package/dist/index.js +1 -1
  5. package/package.json +4 -3
  6. package/src/__fixtures__/cjs-ref-element-entry.cjs +1 -0
  7. package/src/__fixtures__/cjs-ref-element-lazy-entry.cjs +3 -0
  8. package/src/__fixtures__/cjs-ref-element-order-entry.cjs +8 -0
  9. package/src/__fixtures__/cjs-ref-element-order-target.cjs +7 -0
  10. package/src/__fixtures__/cjs-ref-element-target.cjs +5 -0
  11. package/src/__fixtures__/esm-ref-element-entry.mjs +3 -0
  12. package/src/component-renderer.ts +12 -9
  13. package/src/converters/data-item-builder.test.ts +33 -0
  14. package/src/converters/to-editor-component.ts +24 -6
  15. package/src/index.ts +121 -54
  16. package/src/information-extractors/react/extractors/core/tree-builder.ts +3 -3
  17. package/src/information-extractors/react/extractors/core/types.ts +4 -5
  18. package/src/information-extractors/react/extractors/css-properties.ts +2 -0
  19. package/src/information-extractors/react/extractors/prop-tracker.test.ts +251 -0
  20. package/src/information-extractors/react/extractors/prop-tracker.ts +78 -14
  21. package/src/information-extractors/react/types.ts +1 -1
  22. package/src/information-extractors/react/utils/mock-generator.test.ts +131 -0
  23. package/src/information-extractors/react/utils/mock-generator.ts +38 -13
  24. package/src/information-extractors/ts/components.test.ts +52 -0
  25. package/src/information-extractors/ts/components.ts +3 -1
  26. package/src/manifest-pipeline.ts +19 -14
  27. package/src/module-loader.test.ts +150 -0
  28. package/src/module-loader.ts +48 -8
  29. package/src/react-runtime-interceptor.ts +64 -0
  30. package/src/react-runtime-loader.ts +656 -0
  31. package/src/ref-elements/component-tag.ts +105 -0
  32. package/src/ref-elements/context.test.ts +179 -0
  33. package/src/ref-elements/context.ts +280 -0
  34. package/src/ref-elements/eligible-paths.ts +45 -0
  35. package/src/ref-elements/module-resolution.test.ts +50 -0
  36. package/src/ref-elements/module-resolution.ts +21 -0
  37. package/src/ref-elements/module-specifier.ts +17 -0
  38. package/src/ref-elements/path-utils.test.ts +14 -0
  39. package/src/ref-elements/path-utils.ts +55 -0
  40. package/src/ref-elements/types.ts +17 -0
  41. package/src/utils/css-class.ts +15 -0
  42. package/src/jsx-runtime-interceptor.ts +0 -245
@@ -0,0 +1,14 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { buildRefElementManifestKey } from './path-utils'
3
+
4
+ describe('buildRefElementManifestKey', () => {
5
+ it('falls back to the leaf key when the ref-element path matches the inherited parent path exactly', () => {
6
+ expect(buildRefElementManifestKey('cta', 'cta')).toBe('cta')
7
+ expect(buildRefElementManifestKey('card.elementProps.cta', 'card.elementProps.cta')).toBe('cta')
8
+ })
9
+
10
+ it('keeps nested relative paths when the ref-element path extends the parent path', () => {
11
+ expect(buildRefElementManifestKey('card.elementProps.cta', 'card')).toBe('cta')
12
+ expect(buildRefElementManifestKey('card.elementProps.primaryAction', 'card')).toBe('primaryAction')
13
+ })
14
+ })
@@ -0,0 +1,55 @@
1
+ import { camelCase } from 'case-anything'
2
+
3
+ const REF_ELEMENT_MARKER_PREFIX = 'mock_ref_element__'
4
+ const REF_ELEMENT_MARKER_PATTERN = /^mock_ref_element__([A-Za-z0-9\-_.!~*'()%]+)$/
5
+ const ELEMENT_PROPS_ROOT_PATH_PREFIX = 'props.elementProps.'
6
+
7
+ export function createRefElementMarker(elementPropsPath: string): string {
8
+ return `${REF_ELEMENT_MARKER_PREFIX}${encodeURIComponent(elementPropsPath)}`
9
+ }
10
+
11
+ export function parseRefElementMarker(value: unknown): string | undefined {
12
+ if (typeof value !== 'string') return undefined
13
+
14
+ const trimmedValue = value.trim()
15
+ const markerMatch = REF_ELEMENT_MARKER_PATTERN.exec(trimmedValue)
16
+ if (!markerMatch?.[1]) return undefined
17
+
18
+ return decodeURIComponent(markerMatch[1])
19
+ }
20
+
21
+ export function extractElementPropsPathFromPropPath(propPath: string): string | undefined {
22
+ if (!propPath.startsWith(ELEMENT_PROPS_ROOT_PATH_PREFIX)) {
23
+ return undefined
24
+ }
25
+
26
+ return propPath.slice(ELEMENT_PROPS_ROOT_PATH_PREFIX.length)
27
+ }
28
+
29
+ export function extractParentElementPropsPath(relativeElementPropsPropPath: string): string | undefined {
30
+ const lastDotIndex = relativeElementPropsPropPath.lastIndexOf('.')
31
+ if (lastDotIndex === -1) return undefined
32
+
33
+ return relativeElementPropsPropPath.slice(0, lastDotIndex)
34
+ }
35
+
36
+ export function splitElementPropsPathSegments(elementPropsPath: string): string[] {
37
+ return elementPropsPath.split('.').filter((segment) => segment !== 'elementProps')
38
+ }
39
+
40
+ export function buildRefElementManifestKey(elementPropsPath: string, parentElementPropsPath?: string): string {
41
+ const elementPropsSegments = splitElementPropsPathSegments(elementPropsPath)
42
+ const parentSegments = parentElementPropsPath ? splitElementPropsPathSegments(parentElementPropsPath) : []
43
+ const sharesParentPath = parentSegments.every((segment, index) => elementPropsSegments[index] === segment)
44
+ const relativeSegments =
45
+ sharesParentPath && parentSegments.length > 0
46
+ ? elementPropsSegments.slice(parentSegments.length)
47
+ : elementPropsSegments
48
+ const manifestKeySegments = relativeSegments.length > 0 ? relativeSegments : elementPropsSegments.slice(-1)
49
+
50
+ if (manifestKeySegments.length <= 1) {
51
+ return manifestKeySegments[0] ?? ''
52
+ }
53
+
54
+ return camelCase(manifestKeySegments.join(' '))
55
+ }
@@ -0,0 +1,17 @@
1
+ export interface RefElementModule {
2
+ exportNames: string[]
3
+ moduleSpecifier: string
4
+ resolvedModuleUrl: string
5
+ refComponentType: string
6
+ }
7
+
8
+ export interface RefElementContext {
9
+ eligiblePaths: string[]
10
+ runtimeModules: RefElementModule[]
11
+ }
12
+
13
+ export interface RefElementMatch {
14
+ elementPropsPath: string
15
+ refComponentType: string
16
+ selector: string
17
+ }
@@ -35,6 +35,21 @@ export function isGlobalSemanticClass(className: string): boolean {
35
35
  return SEMANTIC_CLASS_PATTERN.test(className)
36
36
  }
37
37
 
38
+ export function normalizeClassNames(classNameValue: unknown): string[] {
39
+ if (typeof classNameValue === 'string') {
40
+ return classNameValue
41
+ .split(/\s+/)
42
+ .map((className) => className.trim())
43
+ .filter(Boolean)
44
+ }
45
+
46
+ if (Array.isArray(classNameValue)) {
47
+ return classNameValue.flatMap((value) => normalizeClassNames(value))
48
+ }
49
+
50
+ return []
51
+ }
52
+
38
53
  /**
39
54
  * Picks the best semantic class from a list of class names.
40
55
  * Prefers a class without a BEM modifier (e.g. `card__header`) over one with
@@ -1,245 +0,0 @@
1
- /**
2
- * JSX Interception
3
- *
4
- * Provides mutable interception of React's jsx-runtime functions and registers
5
- * the Node.js ESM loader hook that redirects `react/jsx-runtime` and
6
- * `react/jsx-dev-runtime` imports to an in-memory data: URL interceptor.
7
- *
8
- * Mutable state lives on `globalThis` via `Symbol.for()` so that the bundled
9
- * copy of this module and the data: URL interceptor module share the same state.
10
- */
11
-
12
- import { createRequire, register } from 'node:module'
13
-
14
- // Use require() to load the real React modules, bypassing Vite alias / ESM loader hook
15
- const require = createRequire(import.meta.url)
16
- const originalRuntime = require('react/jsx-runtime') as typeof import('react/jsx-runtime')
17
- const originalDevRuntime = require('react/jsx-dev-runtime') as typeof import('react/jsx-dev-runtime')
18
-
19
- // Type for jsxDEV function
20
- type JsxDevFn = typeof originalDevRuntime.jsxDEV
21
-
22
- // biome-ignore lint/suspicious/noExplicitAny: broad function type matching React.createElement's signature
23
- type CreateElementFn = (...args: any[]) => unknown
24
-
25
- // Captured at module-init time for getOriginalCreateElement(); also reused in
26
- // registerJsxLoaderHook() to stash the full exports for the react data: URL shim.
27
- const cjsReact = require('react') as { createElement: CreateElementFn }
28
- const originalCreateElement: CreateElementFn = cjsReact.createElement
29
-
30
- // ─────────────────────────────────────────────────────────────────────────────
31
- // Shared state via globalThis
32
- //
33
- // The bundled copy (inside index.js) and the data: URL interceptor module share
34
- // mutable state via globalThis. Symbol.for() guarantees a process-wide key.
35
- // ─────────────────────────────────────────────────────────────────────────────
36
-
37
- const STATE_KEY = Symbol.for('zero-config:jsx-interceptor')
38
-
39
- interface JsxInterceptorState {
40
- currentJsx: typeof originalRuntime.jsx
41
- currentJsxs: typeof originalRuntime.jsxs
42
- currentJsxDEV: JsxDevFn
43
- isInsideOriginal: boolean
44
- /** Interceptor for React.createElement set during render; null when not intercepting. */
45
- currentCreateElement: CreateElementFn | null
46
- }
47
-
48
- function getState(): JsxInterceptorState {
49
- const g = globalThis as unknown as Record<symbol, JsxInterceptorState | undefined>
50
- if (!g[STATE_KEY]) {
51
- g[STATE_KEY] = {
52
- currentJsx: originalRuntime.jsx,
53
- currentJsxs: originalRuntime.jsxs,
54
- currentJsxDEV: originalDevRuntime.jsxDEV,
55
- isInsideOriginal: false,
56
- currentCreateElement: null,
57
- }
58
- }
59
- return g[STATE_KEY]!
60
- }
61
-
62
- /**
63
- * Sets custom jsx/jsxs/jsxDEV implementations for interception.
64
- * Call with no arguments to restore originals.
65
- */
66
- export function setJsxInterceptors(
67
- jsx?: typeof originalRuntime.jsx,
68
- jsxs?: typeof originalRuntime.jsxs,
69
- jsxDEV?: JsxDevFn,
70
- ): void {
71
- const state = getState()
72
- state.currentJsx = jsx ?? originalRuntime.jsx
73
- state.currentJsxs = jsxs ?? originalRuntime.jsxs
74
- state.currentJsxDEV = jsxDEV ?? originalDevRuntime.jsxDEV
75
- }
76
-
77
- /**
78
- * Gets the original jsx/jsxs/jsxDEV functions.
79
- */
80
- export function getOriginals() {
81
- return {
82
- jsx: originalRuntime.jsx,
83
- jsxs: originalRuntime.jsxs,
84
- jsxDEV: originalDevRuntime.jsxDEV,
85
- }
86
- }
87
-
88
- /**
89
- * Sets (or clears) the React.createElement interceptor used by the wrapper
90
- * installed on the CJS react module by registerJsxLoaderHook.
91
- *
92
- * This handles ESM namespace imports (`import * as React from 'react'`) which
93
- * snapshot the createElement reference at module-load time. Those snapshots
94
- * point to the wrapper, which reads from this state on every call.
95
- *
96
- * Pass null to restore pass-through behaviour (wrapper delegates to original).
97
- */
98
- export function setCreateElementInterceptor(fn: CreateElementFn | null): void {
99
- getState().currentCreateElement = fn
100
- }
101
-
102
- /**
103
- * Returns the pre-wrapper React.createElement captured at module-init time.
104
- */
105
- export function getOriginalCreateElement(): CreateElementFn {
106
- return originalCreateElement
107
- }
108
-
109
- // ─────────────────────────────────────────────────────────────────────────────
110
- // Hook Registration
111
- // ─────────────────────────────────────────────────────────────────────────────
112
-
113
- const LOADER_REGISTERED_KEY = Symbol.for('zero-config:jsx-loader-registered')
114
- const ORIGINALS_KEY = Symbol.for('zero-config:jsx-originals')
115
-
116
- interface JsxOriginals {
117
- Fragment: typeof originalRuntime.Fragment
118
- jsx: typeof originalRuntime.jsx
119
- jsxs: typeof originalRuntime.jsxs
120
- jsxDEV: JsxDevFn
121
- }
122
-
123
- const REACT_ORIGINALS_KEY = Symbol.for('zero-config:react-originals')
124
-
125
- /**
126
- * Registers a Node.js ESM loader hook that redirects `react/jsx-runtime`,
127
- * `react/jsx-dev-runtime`, and `react` imports to in-memory `data:` URL
128
- * interceptors — no separate files required, safe for bundled consumers.
129
- *
130
- * Intercepting `react` itself is necessary for bundled components (e.g. from
131
- * @wix/site-ui) that use `import * as React from 'react'` (Rspack/webpack style)
132
- * and call `React.createElement` through the namespace object. Node.js snapshots
133
- * named CJS exports into the namespace at first-load time; since Vitest loads
134
- * react before our module runs, the snapshot always contains the unwrapped
135
- * original. The loader hook fires for NEWLY-loaded modules (i.e., the user bundle
136
- * and its dependencies), redirecting `react` to a data: shim that wraps
137
- * `createElement` with our state-based interceptor while delegating everything
138
- * else to the real react instance.
139
- *
140
- * Must be called before any dynamic `import()` of user components.
141
- * Safe to call multiple times; subsequent calls are no-ops.
142
- */
143
- export function registerJsxLoaderHook(): void {
144
- const globalRecord = globalThis as unknown as Record<symbol, unknown>
145
- if (globalRecord[LOADER_REGISTERED_KEY]) return
146
- globalRecord[LOADER_REGISTERED_KEY] = true
147
-
148
- const cjsRequire = createRequire(import.meta.url)
149
- const realRuntime = cjsRequire('react/jsx-runtime') as typeof originalRuntime
150
- const realDevRuntime = cjsRequire('react/jsx-dev-runtime') as typeof originalDevRuntime
151
-
152
- // Stash jsx-runtime originals so the data: interceptor module can read them
153
- // without needing createRequire (data: modules have no filesystem context)
154
- globalRecord[ORIGINALS_KEY] = {
155
- Fragment: realRuntime.Fragment,
156
- jsx: realRuntime.jsx,
157
- jsxs: realRuntime.jsxs,
158
- jsxDEV: realDevRuntime.jsxDEV,
159
- } satisfies JsxOriginals
160
-
161
- // Stash the full react module so the react data: shim can re-export everything
162
- const cjsReactExports = cjsRequire('react') as Record<string, unknown>
163
- globalRecord[REACT_ORIGINALS_KEY] = cjsReactExports
164
-
165
- // Generate named re-export lines for every enumerable property of react.
166
- // `createElement` gets a state-aware wrapper; everything else is a direct
167
- // delegation to the real react so hooks and other internals work correctly.
168
- const reactExportLines = Object.keys(cjsReactExports)
169
- .map((key) => {
170
- if (key === 'createElement') {
171
- return `export function createElement() {
172
- var state = globalThis[STATE_KEY];
173
- if (state && state.currentCreateElement) return state.currentCreateElement.apply(null, arguments);
174
- return r.createElement.apply(null, arguments);
175
- }`
176
- }
177
- return `export var ${key} = r.${key};`
178
- })
179
- .join('\n')
180
-
181
- const reactShimSource = `
182
- var REACT_KEY = Symbol.for('zero-config:react-originals');
183
- var STATE_KEY = Symbol.for('zero-config:jsx-interceptor');
184
- var r = globalThis[REACT_KEY];
185
- export default r;
186
- ${reactExportLines}
187
- `
188
-
189
- const reactShimUrl = `data:text/javascript,${encodeURIComponent(reactShimSource)}`
190
-
191
- const interceptorSource = `
192
- const ORIGINALS_KEY = Symbol.for('zero-config:jsx-originals');
193
- const STATE_KEY = Symbol.for('zero-config:jsx-interceptor');
194
- const originals = globalThis[ORIGINALS_KEY];
195
- function getState() {
196
- if (!globalThis[STATE_KEY]) {
197
- globalThis[STATE_KEY] = {
198
- currentJsx: originals.jsx, currentJsxs: originals.jsxs,
199
- currentJsxDEV: originals.jsxDEV, isInsideOriginal: false,
200
- };
201
- }
202
- return globalThis[STATE_KEY];
203
- }
204
- export const Fragment = originals.Fragment;
205
- export function jsx(type, props, key) {
206
- const state = getState();
207
- if (state.isInsideOriginal) return originals.jsx(type, props, key);
208
- state.isInsideOriginal = true;
209
- try { return state.currentJsx(type, props, key); }
210
- finally { state.isInsideOriginal = false; }
211
- }
212
- export function jsxs(type, props, key) {
213
- const state = getState();
214
- if (state.isInsideOriginal) return originals.jsxs(type, props, key);
215
- state.isInsideOriginal = true;
216
- try { return state.currentJsxs(type, props, key); }
217
- finally { state.isInsideOriginal = false; }
218
- }
219
- export function jsxDEV(type, props, key, isStaticChildren, source, self) {
220
- const state = getState();
221
- if (state.isInsideOriginal) return originals.jsxDEV(type, props, key, isStaticChildren, source, self);
222
- state.isInsideOriginal = true;
223
- try { return state.currentJsxDEV(type, props, key, isStaticChildren, source, self); }
224
- finally { state.isInsideOriginal = false; }
225
- }
226
- `
227
-
228
- const interceptorDataUrl = `data:text/javascript,${encodeURIComponent(interceptorSource)}`
229
-
230
- const loaderSource = `
231
- const INTERCEPTOR_URL = ${JSON.stringify(interceptorDataUrl)};
232
- const REACT_SHIM_URL = ${JSON.stringify(reactShimUrl)};
233
- export async function resolve(specifier, context, nextResolve) {
234
- if (specifier === 'react/jsx-runtime' || specifier === 'react/jsx-dev-runtime') {
235
- return { shortCircuit: true, url: INTERCEPTOR_URL };
236
- }
237
- if (specifier === 'react') {
238
- return { shortCircuit: true, url: REACT_SHIM_URL };
239
- }
240
- return nextResolve(specifier, context);
241
- }
242
- `
243
-
244
- register(`data:text/javascript,${encodeURIComponent(loaderSource)}`)
245
- }