@wix/zero-config-implementation 1.79.0 → 1.81.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.
- package/dist/{index-DCJue8M7.js → index-B5j6kWzS.js} +1 -1
- package/dist/{index-CoU1WRYf.js → index-CqoYtob0.js} +12426 -12360
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/__fixtures__/esm-browser-globals-entry.mjs +2 -0
- package/src/__fixtures__/esm-browser-globals-existing-navigator-entry.mjs +2 -0
- package/src/__fixtures__/esm-browser-globals-preservation-entry.mjs +2 -0
- package/src/__fixtures__/esm-browser-globals-runtime-error-entry.mjs +4 -0
- package/src/information-extractors/css/parse.test.ts +58 -0
- package/src/information-extractors/css/parse.ts +54 -7
- package/src/information-extractors/css/sass-adapter.test.ts +137 -0
- package/src/information-extractors/css/sass-adapter.ts +59 -1
- package/src/module-loader.test.ts +163 -0
- package/src/module-loader.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { B as t, D as e, E as o, I as n, N as c, P as l, R as i, a as p, V as E, b as m, c as x, d as f, e as d, f as u, h as C, i as I, j as k, k as y, l as A, m as D, n as P, o as R, p as h, q as B, r as M, s as T, t as b, w } from "./index-
|
|
1
|
+
import { B as t, D as e, E as o, I as n, N as c, P as l, R as i, a as p, V as E, b as m, c as x, d as f, e as d, f as u, h as C, i as I, j as k, k as y, l as A, m as D, n as P, o as R, p as h, q as B, r as M, s as T, t as b, w } from "./index-CqoYtob0.js";
|
|
2
2
|
import "react";
|
|
3
3
|
export {
|
|
4
4
|
t as BaseError,
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"registry": "https://registry.npmjs.org/",
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"version": "1.
|
|
7
|
+
"version": "1.81.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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@wix/builder-services-wrapper": "^1.56.0",
|
|
43
|
-
"@wix/react-component-schema": "1.
|
|
43
|
+
"@wix/react-component-schema": "1.8.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@faker-js/faker": "^10.2.0",
|
|
@@ -82,5 +82,5 @@
|
|
|
82
82
|
]
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
|
-
"falconPackageHash": "
|
|
85
|
+
"falconPackageHash": "a62684e6a36b7c409927242d6d286817a3aa7b2ef5601dac8e42ab5f"
|
|
86
86
|
}
|
|
@@ -267,3 +267,61 @@ describe('resolveCssPropertyValue', () => {
|
|
|
267
267
|
).toBe('inline-flex')
|
|
268
268
|
})
|
|
269
269
|
})
|
|
270
|
+
|
|
271
|
+
describe('resolveVarIdentifiers — CSS Modules @value compatibility', () => {
|
|
272
|
+
it('prepends -- to a bare var() identifier produced by CSS Modules @value substitution', () => {
|
|
273
|
+
const [colorProperty] = parseCss(`
|
|
274
|
+
.heading {
|
|
275
|
+
color: var(wst-heading-3-color);
|
|
276
|
+
}
|
|
277
|
+
`).getPropertiesForSelector('.heading')
|
|
278
|
+
|
|
279
|
+
expect(colorProperty.value).toBe('var(--wst-heading-3-color)')
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('does not modify a var() identifier that already starts with --', () => {
|
|
283
|
+
const [colorProperty] = parseCss(`
|
|
284
|
+
.heading {
|
|
285
|
+
color: var(--wst-heading-3-color);
|
|
286
|
+
}
|
|
287
|
+
`).getPropertiesForSelector('.heading')
|
|
288
|
+
|
|
289
|
+
expect(colorProperty.value).toBe('var(--wst-heading-3-color)')
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
it('normalizes a bare var() that has a fallback value', () => {
|
|
293
|
+
const [colorProperty] = parseCss(`
|
|
294
|
+
.heading {
|
|
295
|
+
color: var(wst-heading-3-color, #1e293b);
|
|
296
|
+
}
|
|
297
|
+
`).getPropertiesForSelector('.heading')
|
|
298
|
+
|
|
299
|
+
expect(colorProperty.value).toBe('var(--wst-heading-3-color,#1e293b)')
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
it('resolves a @value local alias to the real CSS custom property name', () => {
|
|
303
|
+
const [borderProperty] = parseCss(`
|
|
304
|
+
@value any-other-name: --wst-system-line-2-width;
|
|
305
|
+
.foo { border-top-width: var(any-other-name); }
|
|
306
|
+
`).getPropertiesForSelector('.foo')
|
|
307
|
+
|
|
308
|
+
expect(borderProperty.value).toBe('var(--wst-system-line-2-width)')
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('falls back to -- prefix for import-form @value names (convention: alias matches property)', () => {
|
|
312
|
+
const [colorProperty] = parseCss(`
|
|
313
|
+
@value wst-heading-3-color from "@wix/react-component-schema/theme-variables.module.css";
|
|
314
|
+
.heading { color: var(wst-heading-3-color); }
|
|
315
|
+
`).getPropertiesForSelector('.heading')
|
|
316
|
+
|
|
317
|
+
expect(colorProperty.value).toBe('var(--wst-heading-3-color)')
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('does not modify var() that appears inside a CSS string literal', () => {
|
|
321
|
+
const [contentProperty] = parseCss(`
|
|
322
|
+
.foo { content: "var(foo)"; }
|
|
323
|
+
`).getPropertiesForSelector('.foo')
|
|
324
|
+
|
|
325
|
+
expect(contentProperty.value).toBe('"var(foo)"')
|
|
326
|
+
})
|
|
327
|
+
})
|
|
@@ -128,6 +128,8 @@ function parseAllProperties(
|
|
|
128
128
|
try {
|
|
129
129
|
const ast = parse(cssString, { parseCustomProperty: true })
|
|
130
130
|
|
|
131
|
+
const valueAliases = parseValueAliases(ast)
|
|
132
|
+
|
|
131
133
|
walk(ast, {
|
|
132
134
|
visit: 'Rule',
|
|
133
135
|
enter(this: WalkContext, rule: Rule) {
|
|
@@ -135,7 +137,7 @@ function parseAllProperties(
|
|
|
135
137
|
const properties: CSSProperty[] = []
|
|
136
138
|
for (const child of rule.block.children) {
|
|
137
139
|
if (child.type !== 'Declaration') continue
|
|
138
|
-
const extracted = extractProperty(child, varUsagesByProperty)
|
|
140
|
+
const extracted = extractProperty(child, varUsagesByProperty, valueAliases)
|
|
139
141
|
if (extracted) {
|
|
140
142
|
properties.push(extracted)
|
|
141
143
|
}
|
|
@@ -181,13 +183,18 @@ function parseAllProperties(
|
|
|
181
183
|
* Extracts property name, value, and var() references from a css-tree Declaration node.
|
|
182
184
|
* Also records var() usages in the provided tracking map.
|
|
183
185
|
*/
|
|
184
|
-
function extractProperty(
|
|
186
|
+
function extractProperty(
|
|
187
|
+
declaration: Declaration,
|
|
188
|
+
varUsagesByProperty: Map<string, Set<string>>,
|
|
189
|
+
valueAliases: Map<string, string>,
|
|
190
|
+
): CSSProperty | null {
|
|
185
191
|
try {
|
|
186
192
|
const name = declaration.property
|
|
187
|
-
|
|
188
|
-
if (!name || !value) return null
|
|
193
|
+
if (!name) return null
|
|
189
194
|
|
|
190
|
-
const varRefs = extractVarNames(declaration.value)
|
|
195
|
+
const varRefs = extractVarNames(declaration.value, valueAliases)
|
|
196
|
+
const value = resolveVarIdentifiers(declaration.value, valueAliases)
|
|
197
|
+
if (!value) return null
|
|
191
198
|
|
|
192
199
|
for (const varName of varRefs) {
|
|
193
200
|
const usageSet = varUsagesByProperty.get(varName) ?? new Set<string>()
|
|
@@ -323,11 +330,51 @@ export function resolveCssPropertyValue(
|
|
|
323
330
|
return resolvedPropertyValue.kind === 'resolved' ? resolvedPropertyValue.value : undefined
|
|
324
331
|
}
|
|
325
332
|
|
|
333
|
+
/**
|
|
334
|
+
* Parses @value local declarations (e.g. `@value alias: --real-prop;`) from the
|
|
335
|
+
* CSS AST and returns a map of alias → real CSS custom property name.
|
|
336
|
+
* Import-form @value rules (`@value foo from "module"`) are ignored — they rely
|
|
337
|
+
* on the convention that the alias name matches the custom property name.
|
|
338
|
+
*/
|
|
339
|
+
function parseValueAliases(cssAst: CssNode): Map<string, string> {
|
|
340
|
+
const aliases = new Map<string, string>()
|
|
341
|
+
walk(cssAst, {
|
|
342
|
+
visit: 'Atrule',
|
|
343
|
+
enter(atruleNode: Atrule) {
|
|
344
|
+
if (atruleNode.name !== 'value' || atruleNode.block !== null || !atruleNode.prelude) return
|
|
345
|
+
const preludeText = generate(atruleNode.prelude).trim()
|
|
346
|
+
const match = preludeText.match(/^([\w-]+)\s*:\s*(--[\w-]+)\s*$/)
|
|
347
|
+
if (match) aliases.set(match[1], match[2])
|
|
348
|
+
},
|
|
349
|
+
})
|
|
350
|
+
return aliases
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Resolves bare var() identifiers to CSS custom property names by walking the AST.
|
|
355
|
+
* Local @value aliases (e.g. `@value alias: --real-prop`) are substituted with
|
|
356
|
+
* their real property name; unaliased identifiers get `--` prepended.
|
|
357
|
+
* Operating on the AST avoids false matches inside CSS string literals (e.g. content: "var(foo)").
|
|
358
|
+
*/
|
|
359
|
+
function resolveVarIdentifiers(valueNode: CssNode, valueAliases: Map<string, string>): string {
|
|
360
|
+
walk(valueNode, {
|
|
361
|
+
visit: 'Function',
|
|
362
|
+
enter(functionNode: FunctionNode) {
|
|
363
|
+
if (functionNode.name !== 'var') return
|
|
364
|
+
const firstChild = functionNode.children.first
|
|
365
|
+
if (firstChild?.type === 'Identifier' && !firstChild.name.startsWith('--')) {
|
|
366
|
+
firstChild.name = valueAliases.get(firstChild.name) ?? `--${firstChild.name}`
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
})
|
|
370
|
+
return generate(valueNode)
|
|
371
|
+
}
|
|
372
|
+
|
|
326
373
|
/**
|
|
327
374
|
* Extracts all CSS custom property names referenced via var() by walking
|
|
328
375
|
* the declaration value AST for Function nodes named "var".
|
|
329
376
|
*/
|
|
330
|
-
function extractVarNames(valueNode: CssNode): string[] {
|
|
377
|
+
function extractVarNames(valueNode: CssNode, valueAliases: Map<string, string>): string[] {
|
|
331
378
|
const varNames: string[] = []
|
|
332
379
|
|
|
333
380
|
walk(valueNode, {
|
|
@@ -338,7 +385,7 @@ function extractVarNames(valueNode: CssNode): string[] {
|
|
|
338
385
|
const firstChild = functionNode.children.first
|
|
339
386
|
if (firstChild && firstChild.type === 'Identifier') {
|
|
340
387
|
const identName = firstChild.name
|
|
341
|
-
varNames.push(identName.startsWith('--') ? identName : `--${identName}`)
|
|
388
|
+
varNames.push(identName.startsWith('--') ? identName : (valueAliases.get(identName) ?? `--${identName}`))
|
|
342
389
|
}
|
|
343
390
|
},
|
|
344
391
|
})
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { tmpdir } from 'node:os'
|
|
3
|
+
import { join } from 'node:path'
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import { compileSass } from './sass-adapter'
|
|
6
|
+
|
|
7
|
+
function writePackageJson(packageDir: string, content: object) {
|
|
8
|
+
writeFileSync(join(packageDir, 'package.json'), JSON.stringify(content))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function createPackageDir(tempDir: string, packageName: string): string {
|
|
12
|
+
const packageDir = join(tempDir, 'node_modules', packageName)
|
|
13
|
+
mkdirSync(packageDir, { recursive: true })
|
|
14
|
+
return packageDir
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('compileSass — npm package importer', () => {
|
|
18
|
+
let tempDir: string
|
|
19
|
+
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
tempDir = mkdtempSync(join(tmpdir(), 'sass-adapter-test-'))
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
rmSync(tempDir, { recursive: true, force: true })
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('resolves a simple string export entry', () => {
|
|
29
|
+
const packageDir = createPackageDir(tempDir, 'my-pkg')
|
|
30
|
+
mkdirSync(join(packageDir, 'dist'), { recursive: true })
|
|
31
|
+
writeFileSync(join(packageDir, 'dist', '_vars.scss'), '$color: red;')
|
|
32
|
+
writePackageJson(packageDir, { exports: { './vars': './dist/_vars.scss' } })
|
|
33
|
+
|
|
34
|
+
const scssFile = join(tempDir, 'test.scss')
|
|
35
|
+
writeFileSync(scssFile, '@use "my-pkg/vars" as theme; .a { color: theme.$color; }')
|
|
36
|
+
|
|
37
|
+
const result = compileSass(scssFile)
|
|
38
|
+
expect(result.isOk()).toBe(true)
|
|
39
|
+
expect(result._unsafeUnwrap()).toContain('color: red')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('resolves a conditional object export entry via "sass" key', () => {
|
|
43
|
+
const packageDir = createPackageDir(tempDir, 'my-pkg')
|
|
44
|
+
mkdirSync(join(packageDir, 'dist'), { recursive: true })
|
|
45
|
+
writeFileSync(join(packageDir, 'dist', '_vars.scss'), '$color: blue;')
|
|
46
|
+
writePackageJson(packageDir, {
|
|
47
|
+
exports: { './vars': { sass: './dist/_vars.scss', default: './dist/vars.css' } },
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const scssFile = join(tempDir, 'test.scss')
|
|
51
|
+
writeFileSync(scssFile, '@use "my-pkg/vars" as theme; .a { color: theme.$color; }')
|
|
52
|
+
|
|
53
|
+
const result = compileSass(scssFile)
|
|
54
|
+
expect(result.isOk()).toBe(true)
|
|
55
|
+
expect(result._unsafeUnwrap()).toContain('color: blue')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('resolves a conditional object export entry via "default" key when "sass" is absent', () => {
|
|
59
|
+
const packageDir = createPackageDir(tempDir, 'my-pkg')
|
|
60
|
+
mkdirSync(join(packageDir, 'dist'), { recursive: true })
|
|
61
|
+
writeFileSync(join(packageDir, 'dist', '_vars.scss'), '$color: green;')
|
|
62
|
+
writePackageJson(packageDir, {
|
|
63
|
+
exports: { './vars': { default: './dist/_vars.scss' } },
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
const scssFile = join(tempDir, 'test.scss')
|
|
67
|
+
writeFileSync(scssFile, '@use "my-pkg/vars" as theme; .a { color: theme.$color; }')
|
|
68
|
+
|
|
69
|
+
const result = compileSass(scssFile)
|
|
70
|
+
expect(result.isOk()).toBe(true)
|
|
71
|
+
expect(result._unsafeUnwrap()).toContain('color: green')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('falls back to .scss file extension when exports field has no matching entry', () => {
|
|
75
|
+
const packageDir = createPackageDir(tempDir, 'my-pkg')
|
|
76
|
+
writeFileSync(join(packageDir, 'vars.scss'), '$color: orange;')
|
|
77
|
+
writePackageJson(packageDir, { exports: { '.': './index.js' } })
|
|
78
|
+
|
|
79
|
+
const scssFile = join(tempDir, 'test.scss')
|
|
80
|
+
writeFileSync(scssFile, '@use "my-pkg/vars" as theme; .a { color: theme.$color; }')
|
|
81
|
+
|
|
82
|
+
const result = compileSass(scssFile)
|
|
83
|
+
expect(result.isOk()).toBe(true)
|
|
84
|
+
expect(result._unsafeUnwrap()).toContain('color: orange')
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('resolves a scoped package', () => {
|
|
88
|
+
const packageDir = createPackageDir(tempDir, '@scope/pkg')
|
|
89
|
+
mkdirSync(join(packageDir, 'dist'), { recursive: true })
|
|
90
|
+
writeFileSync(join(packageDir, 'dist', '_theme.scss'), '$color: purple;')
|
|
91
|
+
writePackageJson(packageDir, { exports: { './theme': './dist/_theme.scss' } })
|
|
92
|
+
|
|
93
|
+
const scssFile = join(tempDir, 'test.scss')
|
|
94
|
+
writeFileSync(scssFile, '@use "@scope/pkg/theme" as theme; .a { color: theme.$color; }')
|
|
95
|
+
|
|
96
|
+
const result = compileSass(scssFile)
|
|
97
|
+
expect(result.isOk()).toBe(true)
|
|
98
|
+
expect(result._unsafeUnwrap()).toContain('color: purple')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('returns err for an unresolvable package import', () => {
|
|
102
|
+
const scssFile = join(tempDir, 'test.scss')
|
|
103
|
+
writeFileSync(scssFile, '@use "nonexistent-pkg/vars";')
|
|
104
|
+
|
|
105
|
+
const result = compileSass(scssFile)
|
|
106
|
+
expect(result.isErr()).toBe(true)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('returns err for a package with invalid JSON in package.json', () => {
|
|
110
|
+
const packageDir = createPackageDir(tempDir, 'bad-pkg')
|
|
111
|
+
mkdirSync(join(packageDir, 'dist'), { recursive: true })
|
|
112
|
+
writeFileSync(join(packageDir, 'package.json'), 'not valid json {{{')
|
|
113
|
+
|
|
114
|
+
const scssFile = join(tempDir, 'test.scss')
|
|
115
|
+
writeFileSync(scssFile, '@use "bad-pkg/vars";')
|
|
116
|
+
|
|
117
|
+
const result = compileSass(scssFile)
|
|
118
|
+
expect(result.isErr()).toBe(true)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('traverses parent directories to find node_modules', () => {
|
|
122
|
+
const packageDir = createPackageDir(tempDir, 'my-pkg')
|
|
123
|
+
mkdirSync(join(packageDir, 'dist'), { recursive: true })
|
|
124
|
+
writeFileSync(join(packageDir, 'dist', '_vars.scss'), '$color: teal;')
|
|
125
|
+
writePackageJson(packageDir, { exports: { './vars': './dist/_vars.scss' } })
|
|
126
|
+
|
|
127
|
+
// SCSS file is in a subdirectory — node_modules is in the parent (tempDir)
|
|
128
|
+
const subDir = join(tempDir, 'src', 'components')
|
|
129
|
+
mkdirSync(subDir, { recursive: true })
|
|
130
|
+
const scssFile = join(subDir, 'test.scss')
|
|
131
|
+
writeFileSync(scssFile, '@use "my-pkg/vars" as theme; .a { color: theme.$color; }')
|
|
132
|
+
|
|
133
|
+
const result = compileSass(scssFile)
|
|
134
|
+
expect(result.isOk()).toBe(true)
|
|
135
|
+
expect(result._unsafeUnwrap()).toContain('color: teal')
|
|
136
|
+
})
|
|
137
|
+
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
2
2
|
import { createRequire } from 'node:module'
|
|
3
3
|
import { dirname, join } from 'node:path'
|
|
4
|
+
import { pathToFileURL } from 'node:url'
|
|
4
5
|
import { Result, err } from 'neverthrow'
|
|
5
6
|
|
|
6
7
|
function collectNodeModulesPaths(filePath: string): string[] {
|
|
@@ -16,6 +17,62 @@ function collectNodeModulesPaths(filePath: string): string[] {
|
|
|
16
17
|
return paths
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
function resolvePackageExport(packageDir: string, subPath: string): string | null {
|
|
21
|
+
const packageJsonPath = join(packageDir, 'package.json')
|
|
22
|
+
if (!existsSync(packageJsonPath)) return null
|
|
23
|
+
let packageJson: { exports?: Record<string, string | Record<string, string>> }
|
|
24
|
+
try {
|
|
25
|
+
packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'))
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`Failed to parse ${packageJsonPath}:`, error)
|
|
28
|
+
return null
|
|
29
|
+
}
|
|
30
|
+
const exportEntry = packageJson.exports?.[subPath]
|
|
31
|
+
let exportPath: string | undefined
|
|
32
|
+
if (typeof exportEntry === 'string') {
|
|
33
|
+
exportPath = exportEntry
|
|
34
|
+
} else if (typeof exportEntry === 'object' && exportEntry !== null) {
|
|
35
|
+
exportPath = exportEntry.sass ?? exportEntry.default
|
|
36
|
+
}
|
|
37
|
+
if (typeof exportPath === 'string') {
|
|
38
|
+
const resolvedPath = join(packageDir, exportPath)
|
|
39
|
+
return existsSync(resolvedPath) ? resolvedPath : null
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function createNodePackageImporter(startDir: string) {
|
|
45
|
+
return {
|
|
46
|
+
findFileUrl(url: string): URL | null {
|
|
47
|
+
if (url.startsWith('.') || url.startsWith('/')) return null
|
|
48
|
+
|
|
49
|
+
const isScoped = url.startsWith('@')
|
|
50
|
+
const parts = url.split('/')
|
|
51
|
+
const packageName = isScoped ? `${parts[0]}/${parts[1]}` : parts[0]
|
|
52
|
+
const subpathParts = isScoped ? parts.slice(2) : parts.slice(1)
|
|
53
|
+
const subPath = subpathParts.length > 0 ? `./${subpathParts.join('/')}` : '.'
|
|
54
|
+
|
|
55
|
+
let dir = startDir
|
|
56
|
+
while (true) {
|
|
57
|
+
const packageDir = join(dir, 'node_modules', packageName)
|
|
58
|
+
if (existsSync(packageDir)) {
|
|
59
|
+
const resolvedPath = resolvePackageExport(packageDir, subPath)
|
|
60
|
+
if (resolvedPath) return pathToFileURL(resolvedPath)
|
|
61
|
+
|
|
62
|
+
if (subpathParts.length > 0) {
|
|
63
|
+
const fallback = join(packageDir, `${subpathParts.join('/')}.scss`)
|
|
64
|
+
if (existsSync(fallback)) return pathToFileURL(fallback)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const parent = dirname(dir)
|
|
68
|
+
if (parent === dir) break
|
|
69
|
+
dir = parent
|
|
70
|
+
}
|
|
71
|
+
return null
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
19
76
|
const require = createRequire(import.meta.url)
|
|
20
77
|
|
|
21
78
|
export function compileSass(filePath: string): Result<string, Error> {
|
|
@@ -34,6 +91,7 @@ export function compileSass(filePath: string): Result<string, Error> {
|
|
|
34
91
|
() =>
|
|
35
92
|
sass.compile(filePath, {
|
|
36
93
|
loadPaths,
|
|
94
|
+
importers: [createNodePackageImporter(dirname(filePath))],
|
|
37
95
|
}).css,
|
|
38
96
|
(thrown) => (thrown instanceof Error ? thrown : new Error(String(thrown))),
|
|
39
97
|
)()
|
|
@@ -8,7 +8,170 @@ import { readLoaderPortRefStateForTests, readRegisteredRefElementModuleUrlsForTe
|
|
|
8
8
|
|
|
9
9
|
const require = createRequire(import.meta.url)
|
|
10
10
|
|
|
11
|
+
const extractionGlobalPropertyNames = [
|
|
12
|
+
'window',
|
|
13
|
+
'document',
|
|
14
|
+
'HTMLElement',
|
|
15
|
+
'Element',
|
|
16
|
+
'navigator',
|
|
17
|
+
'customElements',
|
|
18
|
+
'IntersectionObserver',
|
|
19
|
+
'React',
|
|
20
|
+
'ReactDOM',
|
|
21
|
+
] as const
|
|
22
|
+
|
|
23
|
+
type ExtractionGlobalPropertyName = (typeof extractionGlobalPropertyNames)[number]
|
|
24
|
+
|
|
25
|
+
function snapshotExtractionGlobalDescriptors(): Map<ExtractionGlobalPropertyName, PropertyDescriptor | undefined> {
|
|
26
|
+
return new Map(
|
|
27
|
+
extractionGlobalPropertyNames.map((propertyName) => [
|
|
28
|
+
propertyName,
|
|
29
|
+
Object.getOwnPropertyDescriptor(globalThis, propertyName),
|
|
30
|
+
]),
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function clearExtractionGlobals(): void {
|
|
35
|
+
for (const propertyName of extractionGlobalPropertyNames) {
|
|
36
|
+
Reflect.deleteProperty(globalThis, propertyName)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function restoreExtractionGlobalDescriptors(
|
|
41
|
+
originalDescriptors: Map<ExtractionGlobalPropertyName, PropertyDescriptor | undefined>,
|
|
42
|
+
): void {
|
|
43
|
+
clearExtractionGlobals()
|
|
44
|
+
for (const [propertyName, propertyDescriptor] of originalDescriptors) {
|
|
45
|
+
if (propertyDescriptor !== undefined) {
|
|
46
|
+
Object.defineProperty(globalThis, propertyName, propertyDescriptor)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
11
51
|
describe('module loading', () => {
|
|
52
|
+
it('loads an ESM entry that reads navigator and Element during module evaluation', async () => {
|
|
53
|
+
const originalDescriptors = snapshotExtractionGlobalDescriptors()
|
|
54
|
+
clearExtractionGlobals()
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const entryPath = path.resolve(__dirname, '__fixtures__/esm-browser-globals-entry.mjs')
|
|
58
|
+
const loadedModuleResult = await loadModuleForExtraction(entryPath)
|
|
59
|
+
const loadedModule = loadedModuleResult._unsafeUnwrap()
|
|
60
|
+
const globals = globalThis as Record<string, unknown>
|
|
61
|
+
const configuredWindow = globals.window as Record<string, unknown>
|
|
62
|
+
|
|
63
|
+
expect(loadedModule.moduleExports.navigatorUserAgent).toBe(
|
|
64
|
+
(configuredWindow.navigator as { userAgent: string }).userAgent,
|
|
65
|
+
)
|
|
66
|
+
expect(loadedModule.moduleExports.elementPrototype).toBe(
|
|
67
|
+
(configuredWindow.Element as { prototype: unknown }).prototype,
|
|
68
|
+
)
|
|
69
|
+
expect(globals.navigator).toBe(configuredWindow.navigator)
|
|
70
|
+
expect(globals.Element).toBe(configuredWindow.Element)
|
|
71
|
+
|
|
72
|
+
await loadedModule.cleanup()
|
|
73
|
+
} finally {
|
|
74
|
+
restoreExtractionGlobalDescriptors(originalDescriptors)
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('preserves caller-provided navigator and Element globals', async () => {
|
|
79
|
+
const originalDescriptors = snapshotExtractionGlobalDescriptors()
|
|
80
|
+
clearExtractionGlobals()
|
|
81
|
+
|
|
82
|
+
const callerNavigator = { userAgent: 'caller-provided navigator' }
|
|
83
|
+
class CallerProvidedElement {}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
Object.defineProperty(globalThis, 'navigator', {
|
|
87
|
+
configurable: true,
|
|
88
|
+
value: callerNavigator,
|
|
89
|
+
writable: true,
|
|
90
|
+
})
|
|
91
|
+
Object.defineProperty(globalThis, 'Element', {
|
|
92
|
+
configurable: true,
|
|
93
|
+
value: CallerProvidedElement,
|
|
94
|
+
writable: true,
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
const entryPath = path.resolve(__dirname, '__fixtures__/esm-browser-globals-preservation-entry.mjs')
|
|
98
|
+
const loadedModuleResult = await loadModuleForExtraction(entryPath)
|
|
99
|
+
const loadedModule = loadedModuleResult._unsafeUnwrap()
|
|
100
|
+
const globals = globalThis as Record<string, unknown>
|
|
101
|
+
|
|
102
|
+
expect(globals.navigator).toBe(callerNavigator)
|
|
103
|
+
expect(globals.Element).toBe(CallerProvidedElement)
|
|
104
|
+
expect(loadedModule.moduleExports.elementPrototype).toBe(CallerProvidedElement.prototype)
|
|
105
|
+
|
|
106
|
+
await loadedModule.cleanup()
|
|
107
|
+
} finally {
|
|
108
|
+
restoreExtractionGlobalDescriptors(originalDescriptors)
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it('preserves an existing navigator while adding a missing Element global', async () => {
|
|
113
|
+
const originalDescriptors = snapshotExtractionGlobalDescriptors()
|
|
114
|
+
clearExtractionGlobals()
|
|
115
|
+
|
|
116
|
+
const existingNavigator = { userAgent: 'existing Node navigator' }
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
Object.defineProperty(globalThis, 'navigator', {
|
|
120
|
+
configurable: true,
|
|
121
|
+
value: existingNavigator,
|
|
122
|
+
writable: true,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const entryPath = path.resolve(__dirname, '__fixtures__/esm-browser-globals-existing-navigator-entry.mjs')
|
|
126
|
+
const loadedModuleResult = await loadModuleForExtraction(entryPath)
|
|
127
|
+
const loadedModule = loadedModuleResult._unsafeUnwrap()
|
|
128
|
+
const globals = globalThis as Record<string, unknown>
|
|
129
|
+
const configuredWindow = globals.window as Record<string, unknown>
|
|
130
|
+
|
|
131
|
+
expect(loadedModule.moduleExports.navigatorUserAgent).toBe(existingNavigator.userAgent)
|
|
132
|
+
expect(loadedModule.moduleExports.elementPrototype).toBe(
|
|
133
|
+
(configuredWindow.Element as { prototype: unknown }).prototype,
|
|
134
|
+
)
|
|
135
|
+
expect(globals.navigator).toBe(existingNavigator)
|
|
136
|
+
expect(globals.Element).toBe(configuredWindow.Element)
|
|
137
|
+
|
|
138
|
+
await loadedModule.cleanup()
|
|
139
|
+
} finally {
|
|
140
|
+
restoreExtractionGlobalDescriptors(originalDescriptors)
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('keeps unrelated module-evaluation failures visible after browser-global setup', async () => {
|
|
145
|
+
const originalDescriptors = snapshotExtractionGlobalDescriptors()
|
|
146
|
+
clearExtractionGlobals()
|
|
147
|
+
|
|
148
|
+
const callerNavigator = { userAgent: 'caller-provided navigator' }
|
|
149
|
+
class CallerProvidedElement {}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
Object.defineProperty(globalThis, 'navigator', {
|
|
153
|
+
configurable: true,
|
|
154
|
+
value: callerNavigator,
|
|
155
|
+
writable: true,
|
|
156
|
+
})
|
|
157
|
+
Object.defineProperty(globalThis, 'Element', {
|
|
158
|
+
configurable: true,
|
|
159
|
+
value: CallerProvidedElement,
|
|
160
|
+
writable: true,
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
const entryPath = path.resolve(__dirname, '__fixtures__/esm-browser-globals-runtime-error-entry.mjs')
|
|
164
|
+
const loadedModuleResult = await loadModuleForExtraction(entryPath)
|
|
165
|
+
|
|
166
|
+
expect(loadedModuleResult.isErr()).toBe(true)
|
|
167
|
+
if (loadedModuleResult.isErr()) {
|
|
168
|
+
expect(loadedModuleResult.error.esmError?.message).toContain('browser-global fixture runtime failure')
|
|
169
|
+
}
|
|
170
|
+
} finally {
|
|
171
|
+
restoreExtractionGlobalDescriptors(originalDescriptors)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
12
175
|
it('temporarily tags ref-element modules in the CJS fallback and cleans them up afterwards', async () => {
|
|
13
176
|
const entryPath = path.resolve(__dirname, '__fixtures__/cjs-ref-element-entry.cjs')
|
|
14
177
|
const targetPath = path.resolve(__dirname, '__fixtures__/cjs-ref-element-target.cjs')
|
package/src/module-loader.ts
CHANGED
|
@@ -44,6 +44,8 @@ function setupWindowGlobals(): void {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
const windowObj = globals.window as Record<string, unknown>
|
|
47
|
+
if (globals.navigator === undefined) globals.navigator = windowObj.navigator
|
|
48
|
+
if (globals.Element === undefined) globals.Element = windowObj.Element
|
|
47
49
|
const intersectionObserver = windowObj.IntersectionObserver ?? NoopIntersectionObserver
|
|
48
50
|
if (globals.IntersectionObserver === undefined) globals.IntersectionObserver = intersectionObserver
|
|
49
51
|
if (windowObj.IntersectionObserver === undefined) windowObj.IntersectionObserver = intersectionObserver
|