zyzz 0.0.6 → 0.0.7
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 +26 -1
- package/dist/Style.d.ts +3 -3
- package/dist/Style.d.ts.map +1 -1
- package/dist/Style.js +12 -1
- package/dist/Style.js.map +1 -1
- package/dist/internal/Authoring.d.ts.map +1 -1
- package/dist/internal/Authoring.js +14 -0
- package/dist/internal/Authoring.js.map +1 -1
- package/dist/internal/Binding.d.ts +7 -0
- package/dist/internal/Binding.d.ts.map +1 -1
- package/dist/internal/Binding.js.map +1 -1
- package/dist/internal/Configuration.d.ts +1 -1
- package/dist/internal/Configuration.d.ts.map +1 -1
- package/dist/internal/Configuration.js +7 -6
- package/dist/internal/Configuration.js.map +1 -1
- package/dist/internal/Theme.d.ts +1 -1
- package/dist/internal/Theme.d.ts.map +1 -1
- package/dist/internal/Theme.js.map +1 -1
- package/dist/internal/Token.d.ts +3 -1
- package/dist/internal/Token.d.ts.map +1 -1
- package/dist/internal/Token.js +28 -3
- package/dist/internal/Token.js.map +1 -1
- package/dist/internal/Value.d.ts +22 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +76 -9
- package/dist/internal/Value.js.map +1 -1
- package/dist/next/internal/Project.d.ts +28 -0
- package/dist/next/internal/Project.d.ts.map +1 -0
- package/dist/next/internal/Project.js +249 -0
- package/dist/next/internal/Project.js.map +1 -0
- package/dist/next/loader.d.ts +2 -5
- package/dist/next/loader.d.ts.map +1 -1
- package/dist/next/loader.js +11 -150
- package/dist/next/loader.js.map +1 -1
- package/dist/variants.d.ts +1 -1
- package/dist/variants.d.ts.map +1 -1
- package/dist/variants.js.map +1 -1
- package/package.json +1 -1
- package/src/Style.ts +42 -8
- package/src/internal/Authoring.ts +21 -0
- package/src/internal/Binding.ts +30 -0
- package/src/internal/Configuration.ts +19 -13
- package/src/internal/Theme.ts +5 -1
- package/src/internal/Token.ts +34 -3
- package/src/internal/Value.ts +180 -10
- package/src/next/internal/Project.ts +337 -0
- package/src/next/loader.ts +19 -196
- package/src/variants.ts +1 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/** Retains filesystem snapshots and an incremental graph within a Next.js loader worker. @module */
|
|
2
|
+
import * as Contract from '../../compiler/internal/Contract.js'
|
|
3
|
+
import * as Fs from 'node:fs/promises'
|
|
4
|
+
import * as Graph from '../../compiler/Graph.js'
|
|
5
|
+
import type * as NativeFs from 'node:fs'
|
|
6
|
+
import * as Path from 'node:path'
|
|
7
|
+
import * as Reset from '../../node/Reset.js'
|
|
8
|
+
import * as Syntax from '../../compiler/internal/Syntax.js'
|
|
9
|
+
|
|
10
|
+
/** Resolution and dependency notifications supplied by the active loader invocation. */
|
|
11
|
+
export type Context = {
|
|
12
|
+
/** Track graph membership changes. */
|
|
13
|
+
addContextDependency(directory: string): void
|
|
14
|
+
/** Track source and contract edits. */
|
|
15
|
+
addDependency(file: string): void
|
|
16
|
+
/** Resolve with the active bundler's aliases and conditions. */
|
|
17
|
+
getResolve(
|
|
18
|
+
options: object,
|
|
19
|
+
): (
|
|
20
|
+
directory: string,
|
|
21
|
+
specifier: string,
|
|
22
|
+
callback: (error: Error | null, file?: string | false) => void,
|
|
23
|
+
) => void
|
|
24
|
+
/** Physical loader resource. */
|
|
25
|
+
resourcePath: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Creates project state owned by a loader configuration in one worker process. */
|
|
29
|
+
export function create(options: create.Options) {
|
|
30
|
+
let compiler = Graph.create()
|
|
31
|
+
const directories = new Map<
|
|
32
|
+
string,
|
|
33
|
+
{ items: NativeFs.Dirent[]; track: boolean; version: string }
|
|
34
|
+
>()
|
|
35
|
+
const files = new Map<string, { source: string; version: string }>()
|
|
36
|
+
const libraries = new Map<
|
|
37
|
+
string,
|
|
38
|
+
{ dependencies: readonly (readonly string[])[]; source: string }
|
|
39
|
+
>()
|
|
40
|
+
const reset = options.reset ? Reset.read() : undefined
|
|
41
|
+
const syntax = new Map<
|
|
42
|
+
string,
|
|
43
|
+
{ parsed: ReturnType<typeof Syntax.parse>; source: string }
|
|
44
|
+
>()
|
|
45
|
+
let pending = Promise.resolve()
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
/** Compiles a fresh snapshot while retaining unchanged syntax and graph results. */
|
|
49
|
+
compile(context: Context, source?: string) {
|
|
50
|
+
// Loader calls may overlap, but only one immutable snapshot updates the compiler at a time.
|
|
51
|
+
const result = pending.then(async () => {
|
|
52
|
+
try {
|
|
53
|
+
return await compile(context, source)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Failed discovery or extraction may leave a partial filesystem snapshot.
|
|
56
|
+
compiler = Graph.create()
|
|
57
|
+
directories.clear()
|
|
58
|
+
files.clear()
|
|
59
|
+
libraries.clear()
|
|
60
|
+
syntax.clear()
|
|
61
|
+
throw error
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// A failed generation must not prevent recovery after the source is corrected.
|
|
66
|
+
pending = result.then(
|
|
67
|
+
() => {},
|
|
68
|
+
() => {},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return result
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function compile(context: Context, source?: string) {
|
|
76
|
+
const root = options.root
|
|
77
|
+
const programs = new Map<string, ReturnType<typeof Syntax.parse>>()
|
|
78
|
+
const seenDirectories = new Set<string>()
|
|
79
|
+
const seenFiles = new Set<string>()
|
|
80
|
+
async function read(file: string) {
|
|
81
|
+
seenFiles.add(file)
|
|
82
|
+
const version = await stamp(file)
|
|
83
|
+
let entry = files.get(file)
|
|
84
|
+
if (entry?.version !== version) {
|
|
85
|
+
entry = { source: await Fs.readFile(file, 'utf8'), version }
|
|
86
|
+
files.set(file, entry)
|
|
87
|
+
}
|
|
88
|
+
return entry.source
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const modules: Record<string, string> = Object.create(null)
|
|
92
|
+
const imports: Record<
|
|
93
|
+
string,
|
|
94
|
+
Record<string, string | null>
|
|
95
|
+
> = Object.create(null)
|
|
96
|
+
const contracts: Record<string, string> = Object.create(null)
|
|
97
|
+
const resolve = context.getResolve({})
|
|
98
|
+
const id = (file: string) =>
|
|
99
|
+
`app/${Path.relative(root, file).split(Path.sep).join('/')}`
|
|
100
|
+
const eligible = (file: string) =>
|
|
101
|
+
/\.[cm]?[jt]sx?$/.test(file) &&
|
|
102
|
+
!/\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/.test(file) &&
|
|
103
|
+
!Path.relative(root, file)
|
|
104
|
+
.split(Path.sep)
|
|
105
|
+
.some(
|
|
106
|
+
(part) =>
|
|
107
|
+
part === '..' || part === 'node_modules' || part.startsWith('.'),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
async function visit(file: string, text?: string): Promise<void> {
|
|
111
|
+
const name = id(file)
|
|
112
|
+
if (Object.hasOwn(modules, name)) return
|
|
113
|
+
|
|
114
|
+
context.addDependency(file)
|
|
115
|
+
seenFiles.add(file)
|
|
116
|
+
modules[name] = text ?? (await read(file))
|
|
117
|
+
const links: Record<string, string | null> = Object.create(null)
|
|
118
|
+
imports[name] = links
|
|
119
|
+
let entry = syntax.get(name)
|
|
120
|
+
if (entry?.source !== modules[name]) {
|
|
121
|
+
entry = {
|
|
122
|
+
source: modules[name]!,
|
|
123
|
+
parsed: Syntax.parse({ moduleId: name, source: modules[name]! }),
|
|
124
|
+
}
|
|
125
|
+
syntax.set(name, entry)
|
|
126
|
+
}
|
|
127
|
+
programs.set(name, entry.parsed)
|
|
128
|
+
const parsed = entry.parsed
|
|
129
|
+
|
|
130
|
+
for (const node of parsed.program.body) {
|
|
131
|
+
if (
|
|
132
|
+
(node.type !== 'ImportDeclaration' &&
|
|
133
|
+
node.type !== 'ExportNamedDeclaration' &&
|
|
134
|
+
node.type !== 'ExportAllDeclaration') ||
|
|
135
|
+
!node.source
|
|
136
|
+
)
|
|
137
|
+
continue
|
|
138
|
+
if (
|
|
139
|
+
node.type === 'ImportDeclaration'
|
|
140
|
+
? node.importKind === 'type'
|
|
141
|
+
: node.exportKind === 'type'
|
|
142
|
+
)
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
const specifier = node.source.value
|
|
146
|
+
links[specifier] = null
|
|
147
|
+
// Next owns this virtual module, which has no physical stylesheet contract.
|
|
148
|
+
if (specifier === 'next/root-params') continue
|
|
149
|
+
if (
|
|
150
|
+
specifier === 'zyzz' ||
|
|
151
|
+
(specifier.startsWith('zyzz/') && specifier !== 'zyzz/default') ||
|
|
152
|
+
specifier.startsWith('node:')
|
|
153
|
+
)
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
const resolved = await new Promise<string | false | undefined>(
|
|
157
|
+
(accept, reject) => {
|
|
158
|
+
resolve(Path.dirname(file), specifier, (error, value) =>
|
|
159
|
+
error ? reject(error) : accept(value),
|
|
160
|
+
)
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
if (!resolved || !/\.[cm]?[jt]sx?$/.test(resolved)) continue
|
|
164
|
+
if (eligible(resolved)) {
|
|
165
|
+
links[specifier] = id(resolved)
|
|
166
|
+
await visit(resolved)
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const sidecar = `${resolved}.zyzz.json`
|
|
171
|
+
try {
|
|
172
|
+
contracts[resolved] = await read(sidecar)
|
|
173
|
+
context.addDependency(sidecar)
|
|
174
|
+
links[specifier] = resolved
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function discover(directory: string): Promise<void> {
|
|
182
|
+
const version = await stamp(directory)
|
|
183
|
+
let cached = directories.get(directory)
|
|
184
|
+
if (cached?.version !== version) {
|
|
185
|
+
const items = await Fs.readdir(directory, { withFileTypes: true })
|
|
186
|
+
cached = {
|
|
187
|
+
items,
|
|
188
|
+
track:
|
|
189
|
+
!items.some((item) => item.name === 'node_modules') ||
|
|
190
|
+
!(await loops(directory)),
|
|
191
|
+
version,
|
|
192
|
+
}
|
|
193
|
+
directories.set(directory, cached)
|
|
194
|
+
}
|
|
195
|
+
seenDirectories.add(directory)
|
|
196
|
+
const items = cached.items
|
|
197
|
+
|
|
198
|
+
// Linked package roots are tracked through children to avoid recursive dependency loops.
|
|
199
|
+
if (cached.track) context.addContextDependency(directory)
|
|
200
|
+
|
|
201
|
+
for (const item of items) {
|
|
202
|
+
if (
|
|
203
|
+
item.name.startsWith('.') ||
|
|
204
|
+
[
|
|
205
|
+
'node_modules',
|
|
206
|
+
'dist',
|
|
207
|
+
'build',
|
|
208
|
+
'coverage',
|
|
209
|
+
'test',
|
|
210
|
+
'tests',
|
|
211
|
+
'__tests__',
|
|
212
|
+
].includes(item.name)
|
|
213
|
+
)
|
|
214
|
+
continue
|
|
215
|
+
const file = Path.join(directory, item.name)
|
|
216
|
+
if (item.isDirectory()) await discover(file)
|
|
217
|
+
else if (item.isFile() && eligible(file)) await visit(file)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (source !== undefined) await visit(context.resourcePath, source)
|
|
222
|
+
await discover(root)
|
|
223
|
+
const loaded = new Set<string>()
|
|
224
|
+
async function dependencies(file: string): Promise<void> {
|
|
225
|
+
if (loaded.has(file)) return
|
|
226
|
+
loaded.add(file)
|
|
227
|
+
|
|
228
|
+
let library = libraries.get(file)
|
|
229
|
+
if (!library || library.source !== contracts[file]) {
|
|
230
|
+
library = {
|
|
231
|
+
source: contracts[file]!,
|
|
232
|
+
dependencies: Contract.read(
|
|
233
|
+
contracts[file]!,
|
|
234
|
+
new Map(),
|
|
235
|
+
file,
|
|
236
|
+
).stylesheets.map((section) => section.dependency ?? []),
|
|
237
|
+
}
|
|
238
|
+
libraries.set(file, library)
|
|
239
|
+
}
|
|
240
|
+
for (const dependency of library.dependencies) {
|
|
241
|
+
let owner = file
|
|
242
|
+
for (const specifier of dependency) {
|
|
243
|
+
const target = await new Promise<string | false | undefined>(
|
|
244
|
+
(accept, reject) => {
|
|
245
|
+
resolve(Path.dirname(owner), specifier, (error, value) =>
|
|
246
|
+
error ? reject(error) : accept(value),
|
|
247
|
+
)
|
|
248
|
+
},
|
|
249
|
+
)
|
|
250
|
+
if (!target || !Path.isAbsolute(target))
|
|
251
|
+
throw new Error('Unable to resolve packed stylesheet dependency.')
|
|
252
|
+
|
|
253
|
+
const links = (imports[owner] ??= Object.create(null))
|
|
254
|
+
links[specifier] = target
|
|
255
|
+
if (!Object.hasOwn(contracts, target)) {
|
|
256
|
+
const sidecar = `${target}.zyzz.json`
|
|
257
|
+
contracts[target] = await read(sidecar)
|
|
258
|
+
context.addDependency(sidecar)
|
|
259
|
+
}
|
|
260
|
+
await dependencies(target)
|
|
261
|
+
owner = target
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (const file of Object.keys(contracts)) await dependencies(file)
|
|
266
|
+
|
|
267
|
+
const graph = compiler.compile({
|
|
268
|
+
[Syntax.cache]: programs,
|
|
269
|
+
contracts,
|
|
270
|
+
imports,
|
|
271
|
+
modules,
|
|
272
|
+
reset,
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
for (const file of files.keys())
|
|
276
|
+
if (!seenFiles.has(file)) files.delete(file)
|
|
277
|
+
for (const file of directories.keys())
|
|
278
|
+
if (!seenDirectories.has(file)) directories.delete(file)
|
|
279
|
+
for (const file of libraries.keys())
|
|
280
|
+
if (!Object.hasOwn(contracts, file)) libraries.delete(file)
|
|
281
|
+
for (const name of syntax.keys())
|
|
282
|
+
if (!programs.has(name)) syntax.delete(name)
|
|
283
|
+
|
|
284
|
+
return graph
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Project cache settings. */
|
|
289
|
+
export declare namespace create {
|
|
290
|
+
/** Isolates source discovery and shared stylesheet options. */
|
|
291
|
+
type Options = {
|
|
292
|
+
/** Include the packaged reset in shared CSS. */
|
|
293
|
+
readonly reset?: boolean | undefined
|
|
294
|
+
/** Absolute application directory. */
|
|
295
|
+
readonly root: string
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function stamp(file: string) {
|
|
300
|
+
const stat = await Fs.stat(file, { bigint: true })
|
|
301
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Whether an installed dependency links back to the directory or one of its ancestors. */
|
|
305
|
+
async function loops(directory: string): Promise<boolean> {
|
|
306
|
+
const installed = Path.join(directory, 'node_modules')
|
|
307
|
+
const entries = await Fs.readdir(installed, { withFileTypes: true }).catch(
|
|
308
|
+
() => [],
|
|
309
|
+
)
|
|
310
|
+
const candidates = (
|
|
311
|
+
await Promise.all(
|
|
312
|
+
entries.map(async (entry) => {
|
|
313
|
+
if (!entry.name.startsWith('@'))
|
|
314
|
+
return [Path.join(installed, entry.name)]
|
|
315
|
+
|
|
316
|
+
const scoped = Path.join(installed, entry.name)
|
|
317
|
+
|
|
318
|
+
return (await Fs.readdir(scoped).catch(() => [])).map((name) =>
|
|
319
|
+
Path.join(scoped, name),
|
|
320
|
+
)
|
|
321
|
+
}),
|
|
322
|
+
)
|
|
323
|
+
).flat()
|
|
324
|
+
|
|
325
|
+
for (const candidate of candidates) {
|
|
326
|
+
const stat = await Fs.lstat(candidate).catch(() => undefined)
|
|
327
|
+
if (!stat?.isSymbolicLink()) continue
|
|
328
|
+
|
|
329
|
+
const target = await Fs.realpath(candidate).catch(() => undefined)
|
|
330
|
+
if (target === undefined) continue
|
|
331
|
+
|
|
332
|
+
const relative = Path.relative(target, directory)
|
|
333
|
+
if (!relative.startsWith('..') && !Path.isAbsolute(relative)) return true
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return false
|
|
337
|
+
}
|
package/src/next/loader.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
/** Loads a physical source graph through Next.js resolution and emits cache-owned CSS. @module */
|
|
2
|
+
import * as AtRules from '../compiler/internal/AtRules.js'
|
|
2
3
|
import * as Crypto from 'node:crypto'
|
|
3
4
|
import * as Fs from 'node:fs/promises'
|
|
4
5
|
import * as Path from 'node:path'
|
|
5
|
-
import * as
|
|
6
|
-
import * as Graph from '../compiler/Graph.js'
|
|
7
|
-
import * as Reset from '../node/Reset.js'
|
|
8
|
-
import * as AtRules from '../compiler/internal/AtRules.js'
|
|
9
|
-
import * as Contract from '../compiler/internal/Contract.js'
|
|
6
|
+
import * as Project from './internal/Project.js'
|
|
10
7
|
|
|
11
|
-
type Context = {
|
|
12
|
-
addContextDependency(directory: string): void
|
|
13
|
-
addDependency(file: string): void
|
|
8
|
+
type Context = Project.Context & {
|
|
14
9
|
async(): (error: Error | null, code?: string, map?: object) => void
|
|
15
10
|
getOptions(): {
|
|
16
11
|
bundler: string
|
|
@@ -18,16 +13,11 @@ type Context = {
|
|
|
18
13
|
reset?: boolean | undefined
|
|
19
14
|
root: string
|
|
20
15
|
}
|
|
21
|
-
getResolve(
|
|
22
|
-
options: object,
|
|
23
|
-
): (
|
|
24
|
-
directory: string,
|
|
25
|
-
specifier: string,
|
|
26
|
-
callback: (error: Error | null, file?: string | false) => void,
|
|
27
|
-
) => void
|
|
28
|
-
resourcePath: string
|
|
29
16
|
}
|
|
30
17
|
|
|
18
|
+
// Next owns loader worker lifetimes. Each project retains only its latest graph in that worker.
|
|
19
|
+
const projects = new Map<string, ReturnType<typeof Project.create>>()
|
|
20
|
+
|
|
31
21
|
/** Loader-runner boundary used by Webpack and Turbopack. */
|
|
32
22
|
export default function loader(this: Context, source: string): void {
|
|
33
23
|
const callback = this.async()
|
|
@@ -42,14 +32,6 @@ async function compile(context: Context, source: string) {
|
|
|
42
32
|
const options = context.getOptions()
|
|
43
33
|
const root = options.root
|
|
44
34
|
const directory = Path.resolve(root, '.zyzz', 'next')
|
|
45
|
-
const modules: Record<string, string> = Object.create(null)
|
|
46
|
-
const imports: Record<string, Record<string, string | null>> = Object.create(
|
|
47
|
-
null,
|
|
48
|
-
)
|
|
49
|
-
const contracts: Record<string, string> = Object.create(null)
|
|
50
|
-
const resolve = context.getResolve({})
|
|
51
|
-
const id = (file: string) =>
|
|
52
|
-
`app/${Path.relative(root, file).split(Path.sep).join('/')}`
|
|
53
35
|
const eligible = (file: string) =>
|
|
54
36
|
/\.[cm]?[jt]sx?$/.test(file) &&
|
|
55
37
|
!/\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/.test(file) &&
|
|
@@ -71,144 +53,20 @@ async function compile(context: Context, source: string) {
|
|
|
71
53
|
if (options.mode !== 'shared' && !eligible(context.resourcePath))
|
|
72
54
|
return { code: source, map: undefined }
|
|
73
55
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
modules[name] = text ?? (await Fs.readFile(file, 'utf8'))
|
|
80
|
-
const links: Record<string, string | null> = Object.create(null)
|
|
81
|
-
imports[name] = links
|
|
82
|
-
const parsed = Parser.parseSync(file, modules[name]!, {
|
|
83
|
-
sourceType: 'module',
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
for (const node of parsed.program.body) {
|
|
87
|
-
if (
|
|
88
|
-
(node.type !== 'ImportDeclaration' &&
|
|
89
|
-
node.type !== 'ExportNamedDeclaration' &&
|
|
90
|
-
node.type !== 'ExportAllDeclaration') ||
|
|
91
|
-
!node.source
|
|
92
|
-
)
|
|
93
|
-
continue
|
|
94
|
-
if (
|
|
95
|
-
node.type === 'ImportDeclaration'
|
|
96
|
-
? node.importKind === 'type'
|
|
97
|
-
: node.exportKind === 'type'
|
|
98
|
-
)
|
|
99
|
-
continue
|
|
100
|
-
|
|
101
|
-
const specifier = node.source.value
|
|
102
|
-
links[specifier] = null
|
|
103
|
-
// Next owns this virtual module, which has no physical stylesheet contract.
|
|
104
|
-
if (specifier === 'next/root-params') continue
|
|
105
|
-
if (
|
|
106
|
-
specifier === 'zyzz' ||
|
|
107
|
-
(specifier.startsWith('zyzz/') && specifier !== 'zyzz/default') ||
|
|
108
|
-
specifier.startsWith('node:')
|
|
109
|
-
)
|
|
110
|
-
continue
|
|
111
|
-
|
|
112
|
-
const resolved = await new Promise<string | false | undefined>(
|
|
113
|
-
(accept, reject) => {
|
|
114
|
-
resolve(Path.dirname(file), specifier, (error, value) =>
|
|
115
|
-
error ? reject(error) : accept(value),
|
|
116
|
-
)
|
|
117
|
-
},
|
|
118
|
-
)
|
|
119
|
-
if (!resolved || !/\.[cm]?[jt]sx?$/.test(resolved)) continue
|
|
120
|
-
if (eligible(resolved)) {
|
|
121
|
-
links[specifier] = id(resolved)
|
|
122
|
-
await visit(resolved)
|
|
123
|
-
continue
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const sidecar = `${resolved}.zyzz.json`
|
|
127
|
-
try {
|
|
128
|
-
contracts[resolved] = await Fs.readFile(sidecar, 'utf8')
|
|
129
|
-
context.addDependency(sidecar)
|
|
130
|
-
links[specifier] = resolved
|
|
131
|
-
} catch (error) {
|
|
132
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
133
|
-
}
|
|
134
|
-
}
|
|
56
|
+
const key = JSON.stringify([root, options.bundler, options.reset ?? false])
|
|
57
|
+
let project = projects.get(key)
|
|
58
|
+
if (!project) {
|
|
59
|
+
project = Project.create(options)
|
|
60
|
+
projects.set(key, project)
|
|
135
61
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (
|
|
145
|
-
!items.some((item) => item.name === 'node_modules') ||
|
|
146
|
-
!(await loops(directory))
|
|
147
|
-
)
|
|
148
|
-
context.addContextDependency(directory)
|
|
149
|
-
|
|
150
|
-
for (const item of items) {
|
|
151
|
-
if (
|
|
152
|
-
item.name.startsWith('.') ||
|
|
153
|
-
[
|
|
154
|
-
'node_modules',
|
|
155
|
-
'dist',
|
|
156
|
-
'build',
|
|
157
|
-
'coverage',
|
|
158
|
-
'test',
|
|
159
|
-
'tests',
|
|
160
|
-
'__tests__',
|
|
161
|
-
].includes(item.name)
|
|
162
|
-
)
|
|
163
|
-
continue
|
|
164
|
-
const file = Path.join(directory, item.name)
|
|
165
|
-
if (item.isDirectory()) await discover(file)
|
|
166
|
-
else if (item.isFile() && eligible(file)) await visit(file)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (options.mode !== 'shared') await visit(context.resourcePath, source)
|
|
171
|
-
await discover(root)
|
|
172
|
-
const loaded = new Set<string>()
|
|
173
|
-
async function dependencies(file: string): Promise<void> {
|
|
174
|
-
if (loaded.has(file)) return
|
|
175
|
-
loaded.add(file)
|
|
176
|
-
|
|
177
|
-
const metadata = Contract.read(contracts[file]!, new Map(), file)
|
|
178
|
-
for (const section of metadata.stylesheets) {
|
|
179
|
-
let owner = file
|
|
180
|
-
for (const specifier of section.dependency ?? []) {
|
|
181
|
-
const target = await new Promise<string | false | undefined>(
|
|
182
|
-
(accept, reject) => {
|
|
183
|
-
resolve(Path.dirname(owner), specifier, (error, value) =>
|
|
184
|
-
error ? reject(error) : accept(value),
|
|
185
|
-
)
|
|
186
|
-
},
|
|
187
|
-
)
|
|
188
|
-
if (!target || !Path.isAbsolute(target))
|
|
189
|
-
throw new Error('Unable to resolve packed stylesheet dependency.')
|
|
190
|
-
|
|
191
|
-
const links = (imports[owner] ??= Object.create(null))
|
|
192
|
-
links[specifier] = target
|
|
193
|
-
if (!Object.hasOwn(contracts, target)) {
|
|
194
|
-
const sidecar = `${target}.zyzz.json`
|
|
195
|
-
contracts[target] = await Fs.readFile(sidecar, 'utf8')
|
|
196
|
-
context.addDependency(sidecar)
|
|
197
|
-
}
|
|
198
|
-
await dependencies(target)
|
|
199
|
-
owner = target
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
for (const file of Object.keys(contracts)) await dependencies(file)
|
|
204
|
-
|
|
205
|
-
const graph = Graph.compile({
|
|
206
|
-
contracts,
|
|
207
|
-
imports,
|
|
208
|
-
modules,
|
|
209
|
-
reset: options.reset ? Reset.read() : undefined,
|
|
210
|
-
})
|
|
211
|
-
const output = graph.modules[id(context.resourcePath)]
|
|
62
|
+
const graph = await project.compile(
|
|
63
|
+
context,
|
|
64
|
+
options.mode === 'shared' ? undefined : source,
|
|
65
|
+
)
|
|
66
|
+
const output =
|
|
67
|
+
graph.modules[
|
|
68
|
+
`app/${Path.relative(root, context.resourcePath).split(Path.sep).join('/')}`
|
|
69
|
+
]
|
|
212
70
|
|
|
213
71
|
const assets = new Map<string, string>()
|
|
214
72
|
for (const [placeholder, target] of Object.entries(
|
|
@@ -361,38 +219,3 @@ async function compile(context: Context, source: string) {
|
|
|
361
219
|
|
|
362
220
|
return { code: `${output.code}\n${requests.join('\n')}`, map }
|
|
363
221
|
}
|
|
364
|
-
|
|
365
|
-
/** Whether an installed dependency links back to the directory or one of its ancestors. */
|
|
366
|
-
async function loops(directory: string): Promise<boolean> {
|
|
367
|
-
const installed = Path.join(directory, 'node_modules')
|
|
368
|
-
const entries = await Fs.readdir(installed, { withFileTypes: true }).catch(
|
|
369
|
-
() => [],
|
|
370
|
-
)
|
|
371
|
-
const candidates = (
|
|
372
|
-
await Promise.all(
|
|
373
|
-
entries.map(async (entry) => {
|
|
374
|
-
if (!entry.name.startsWith('@'))
|
|
375
|
-
return [Path.join(installed, entry.name)]
|
|
376
|
-
|
|
377
|
-
const scoped = Path.join(installed, entry.name)
|
|
378
|
-
|
|
379
|
-
return (await Fs.readdir(scoped).catch(() => [])).map((name) =>
|
|
380
|
-
Path.join(scoped, name),
|
|
381
|
-
)
|
|
382
|
-
}),
|
|
383
|
-
)
|
|
384
|
-
).flat()
|
|
385
|
-
|
|
386
|
-
for (const candidate of candidates) {
|
|
387
|
-
const stat = await Fs.lstat(candidate).catch(() => undefined)
|
|
388
|
-
if (!stat?.isSymbolicLink()) continue
|
|
389
|
-
|
|
390
|
-
const target = await Fs.realpath(candidate).catch(() => undefined)
|
|
391
|
-
if (target === undefined) continue
|
|
392
|
-
|
|
393
|
-
const relative = Path.relative(target, directory)
|
|
394
|
-
if (!relative.startsWith('..') && !Path.isAbsolute(relative)) return true
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
return false
|
|
398
|
-
}
|
package/src/variants.ts
CHANGED
|
@@ -51,6 +51,7 @@ type CheckedChoice<
|
|
|
51
51
|
values: values,
|
|
52
52
|
) => CheckedStyles<body, tokens, layers, mappings> &
|
|
53
53
|
Binding.Checked<body> &
|
|
54
|
+
Binding.TokenSlots<body, values, tokens, mappings> &
|
|
54
55
|
(string extends keyof values
|
|
55
56
|
? never
|
|
56
57
|
: values extends Record<string, string | number> &
|