zyzz 0.0.7 → 0.0.8
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/babel/index.d.ts +4 -1
- package/dist/babel/index.d.ts.map +1 -1
- package/dist/babel/index.js +6 -3
- package/dist/babel/index.js.map +1 -1
- package/dist/babel/internal/Compilation.d.ts +12 -0
- package/dist/babel/internal/Compilation.d.ts.map +1 -0
- package/dist/babel/internal/Compilation.js +3 -0
- package/dist/babel/internal/Compilation.js.map +1 -0
- package/dist/compiler/Graph.d.ts +6 -1
- package/dist/compiler/Graph.d.ts.map +1 -1
- package/dist/compiler/Graph.js +160 -36
- package/dist/compiler/Graph.js.map +1 -1
- package/dist/compiler/Transform.d.ts.map +1 -1
- package/dist/compiler/Transform.js +2 -1
- package/dist/compiler/Transform.js.map +1 -1
- package/dist/compiler/internal/Stylesheets.d.ts +8 -0
- package/dist/compiler/internal/Stylesheets.d.ts.map +1 -1
- package/dist/compiler/internal/Stylesheets.js +4 -0
- package/dist/compiler/internal/Stylesheets.js.map +1 -1
- package/dist/metro/Graph.d.ts +9 -1
- package/dist/metro/Graph.d.ts.map +1 -1
- package/dist/metro/Graph.js +5 -4
- package/dist/metro/Graph.js.map +1 -1
- package/dist/metro/transformer.d.ts +0 -1
- package/dist/metro/transformer.d.ts.map +1 -1
- package/dist/metro/transformer.js +19 -1
- package/dist/metro/transformer.js.map +1 -1
- package/dist/next/index.d.ts +3 -3
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/index.js +25 -30
- package/dist/next/index.js.map +1 -1
- package/dist/next/internal/Project.d.ts +4 -4
- package/dist/next/internal/Project.d.ts.map +1 -1
- package/dist/next/internal/Project.js +153 -107
- package/dist/next/internal/Project.js.map +1 -1
- package/dist/next/loader.d.ts +3 -1
- package/dist/next/loader.d.ts.map +1 -1
- package/dist/next/loader.js +152 -99
- package/dist/next/loader.js.map +1 -1
- package/dist/node/Host.d.ts.map +1 -1
- package/dist/node/Host.js +11 -3
- package/dist/node/Host.js.map +1 -1
- package/dist/node/internal/Snapshot.d.ts +12 -0
- package/dist/node/internal/Snapshot.d.ts.map +1 -0
- package/dist/node/internal/Snapshot.js +74 -0
- package/dist/node/internal/Snapshot.js.map +1 -0
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +10 -5
- package/dist/unplugin/index.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +19 -4
- package/dist/vite/index.js.map +1 -1
- package/package.json +1 -1
- package/src/babel/index.ts +10 -4
- package/src/babel/internal/Compilation.ts +13 -0
- package/src/compiler/Graph.ts +213 -47
- package/src/compiler/Transform.ts +2 -1
- package/src/compiler/internal/Stylesheets.ts +9 -0
- package/src/metro/Graph.ts +5 -3
- package/src/metro/transformer.ts +26 -1
- package/src/next/index.ts +26 -31
- package/src/next/internal/Project.ts +196 -155
- package/src/next/loader.ts +181 -124
- package/src/node/Host.ts +15 -3
- package/src/node/internal/Snapshot.ts +79 -0
- package/src/unplugin/index.ts +10 -5
- package/src/vite/index.ts +24 -4
|
@@ -2,15 +2,13 @@
|
|
|
2
2
|
import * as Contract from '../../compiler/internal/Contract.js'
|
|
3
3
|
import * as Fs from 'node:fs/promises'
|
|
4
4
|
import * as Graph from '../../compiler/Graph.js'
|
|
5
|
-
import type * as NativeFs from 'node:fs'
|
|
6
5
|
import * as Path from 'node:path'
|
|
7
6
|
import * as Reset from '../../node/Reset.js'
|
|
7
|
+
import * as Stylesheets from '../../compiler/internal/Stylesheets.js'
|
|
8
8
|
import * as Syntax from '../../compiler/internal/Syntax.js'
|
|
9
9
|
|
|
10
10
|
/** Resolution and dependency notifications supplied by the active loader invocation. */
|
|
11
11
|
export type Context = {
|
|
12
|
-
/** Track graph membership changes. */
|
|
13
|
-
addContextDependency(directory: string): void
|
|
14
12
|
/** Track source and contract edits. */
|
|
15
13
|
addDependency(file: string): void
|
|
16
14
|
/** Resolve with the active bundler's aliases and conditions. */
|
|
@@ -28,10 +26,6 @@ export type Context = {
|
|
|
28
26
|
/** Creates project state owned by a loader configuration in one worker process. */
|
|
29
27
|
export function create(options: create.Options) {
|
|
30
28
|
let compiler = Graph.create()
|
|
31
|
-
const directories = new Map<
|
|
32
|
-
string,
|
|
33
|
-
{ items: NativeFs.Dirent[]; track: boolean; version: string }
|
|
34
|
-
>()
|
|
35
29
|
const files = new Map<string, { source: string; version: string }>()
|
|
36
30
|
const libraries = new Map<
|
|
37
31
|
string,
|
|
@@ -46,18 +40,19 @@ export function create(options: create.Options) {
|
|
|
46
40
|
|
|
47
41
|
return {
|
|
48
42
|
/** Compiles a fresh snapshot while retaining unchanged syntax and graph results. */
|
|
49
|
-
compile(context: Context, source
|
|
43
|
+
compile(context: Context, source: string) {
|
|
50
44
|
// Loader calls may overlap, but only one immutable snapshot updates the compiler at a time.
|
|
51
45
|
const result = pending.then(async () => {
|
|
46
|
+
const tracked = new Set<string>()
|
|
52
47
|
try {
|
|
53
|
-
return await compile(context, source)
|
|
48
|
+
return await compile(context, source, tracked)
|
|
54
49
|
} catch (error) {
|
|
55
|
-
// Failed
|
|
50
|
+
// Failed resolution or extraction may leave a partial filesystem snapshot.
|
|
56
51
|
compiler = Graph.create()
|
|
57
|
-
directories.clear()
|
|
58
52
|
files.clear()
|
|
59
53
|
libraries.clear()
|
|
60
54
|
syntax.clear()
|
|
55
|
+
for (const file of tracked) context.addDependency(file)
|
|
61
56
|
throw error
|
|
62
57
|
}
|
|
63
58
|
})
|
|
@@ -72,19 +67,37 @@ export function create(options: create.Options) {
|
|
|
72
67
|
},
|
|
73
68
|
}
|
|
74
69
|
|
|
75
|
-
async function compile(
|
|
70
|
+
async function compile(
|
|
71
|
+
context: Context,
|
|
72
|
+
source: string,
|
|
73
|
+
tracked: Set<string>,
|
|
74
|
+
) {
|
|
76
75
|
const root = options.root
|
|
76
|
+
const runtime = new Set<string>()
|
|
77
|
+
function track(file: string) {
|
|
78
|
+
if (tracked.has(file)) return
|
|
79
|
+
tracked.add(file)
|
|
80
|
+
}
|
|
77
81
|
const programs = new Map<string, ReturnType<typeof Syntax.parse>>()
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
+
const reads = new Map<string, Promise<string>>()
|
|
83
|
+
function read(file: string) {
|
|
84
|
+
let pending = reads.get(file)
|
|
85
|
+
if (!pending) {
|
|
86
|
+
pending = load(file)
|
|
87
|
+
reads.set(file, pending)
|
|
88
|
+
}
|
|
89
|
+
return pending
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function load(file: string) {
|
|
82
93
|
const version = await stamp(file)
|
|
83
94
|
let entry = files.get(file)
|
|
84
95
|
if (entry?.version !== version) {
|
|
85
96
|
entry = { source: await Fs.readFile(file, 'utf8'), version }
|
|
86
|
-
files.set(file, entry)
|
|
87
97
|
}
|
|
98
|
+
files.delete(file)
|
|
99
|
+
files.set(file, entry)
|
|
100
|
+
if (files.size > 256) files.delete(files.keys().next().value!)
|
|
88
101
|
return entry.source
|
|
89
102
|
}
|
|
90
103
|
|
|
@@ -94,7 +107,21 @@ export function create(options: create.Options) {
|
|
|
94
107
|
Record<string, string | null>
|
|
95
108
|
> = Object.create(null)
|
|
96
109
|
const contracts: Record<string, string> = Object.create(null)
|
|
97
|
-
const
|
|
110
|
+
const resolver = context.getResolve({})
|
|
111
|
+
const resolutions = new Map<string, Promise<string | false | undefined>>()
|
|
112
|
+
function resolve(directory: string, specifier: string) {
|
|
113
|
+
const key = JSON.stringify([directory, specifier])
|
|
114
|
+
let pending = resolutions.get(key)
|
|
115
|
+
if (!pending) {
|
|
116
|
+
pending = new Promise<string | false | undefined>((accept, reject) => {
|
|
117
|
+
resolver(directory, specifier, (error, value) =>
|
|
118
|
+
error ? reject(error) : accept(value),
|
|
119
|
+
)
|
|
120
|
+
})
|
|
121
|
+
resolutions.set(key, pending)
|
|
122
|
+
}
|
|
123
|
+
return pending
|
|
124
|
+
}
|
|
98
125
|
const id = (file: string) =>
|
|
99
126
|
`app/${Path.relative(root, file).split(Path.sep).join('/')}`
|
|
100
127
|
const eligible = (file: string) =>
|
|
@@ -107,70 +134,148 @@ export function create(options: create.Options) {
|
|
|
107
134
|
part === '..' || part === 'node_modules' || part.startsWith('.'),
|
|
108
135
|
)
|
|
109
136
|
|
|
137
|
+
function parse(name: string, source: string) {
|
|
138
|
+
let entry = syntax.get(name)
|
|
139
|
+
if (entry?.source !== source)
|
|
140
|
+
entry = { source, parsed: Syntax.parse({ moduleId: name, source }) }
|
|
141
|
+
syntax.delete(name)
|
|
142
|
+
syntax.set(name, entry)
|
|
143
|
+
if (syntax.size > 256) syntax.delete(syntax.keys().next().value!)
|
|
144
|
+
return entry.parsed
|
|
145
|
+
}
|
|
146
|
+
|
|
110
147
|
async function visit(file: string, text?: string): Promise<void> {
|
|
111
148
|
const name = id(file)
|
|
112
149
|
if (Object.hasOwn(modules, name)) return
|
|
113
150
|
|
|
114
|
-
|
|
115
|
-
seenFiles.add(file)
|
|
151
|
+
track(file)
|
|
116
152
|
modules[name] = text ?? (await read(file))
|
|
117
153
|
const links: Record<string, string | null> = Object.create(null)
|
|
118
154
|
imports[name] = links
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
155
|
+
const parsed = parse(name, modules[name]!)
|
|
156
|
+
programs.set(name, parsed)
|
|
129
157
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
(
|
|
133
|
-
node.type !== '
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
158
|
+
const resolutions = await Promise.all(
|
|
159
|
+
parsed.program.body.map(async (node) => {
|
|
160
|
+
if (
|
|
161
|
+
(node.type !== 'ImportDeclaration' &&
|
|
162
|
+
node.type !== 'ExportNamedDeclaration' &&
|
|
163
|
+
node.type !== 'ExportAllDeclaration') ||
|
|
164
|
+
!node.source
|
|
165
|
+
)
|
|
166
|
+
return undefined
|
|
167
|
+
if (
|
|
168
|
+
node.type === 'ImportDeclaration'
|
|
169
|
+
? node.importKind === 'type'
|
|
170
|
+
: node.exportKind === 'type'
|
|
171
|
+
)
|
|
172
|
+
return undefined
|
|
144
173
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
174
|
+
const specifier = node.source.value
|
|
175
|
+
links[specifier] = null
|
|
176
|
+
// Next owns this virtual module, which has no physical stylesheet contract.
|
|
177
|
+
if (specifier === 'next/root-params') return undefined
|
|
178
|
+
if (
|
|
179
|
+
specifier === 'zyzz' ||
|
|
180
|
+
(specifier.startsWith('zyzz/') && specifier !== 'zyzz/default') ||
|
|
181
|
+
specifier.startsWith('node:')
|
|
182
|
+
)
|
|
183
|
+
return undefined
|
|
155
184
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
const resolved = await resolve(Path.dirname(file), specifier)
|
|
186
|
+
return { node, specifier, resolved }
|
|
187
|
+
}),
|
|
188
|
+
)
|
|
189
|
+
for (const resolution of resolutions) {
|
|
190
|
+
if (!resolution) continue
|
|
191
|
+
const { node, specifier, resolved } = resolution
|
|
163
192
|
if (!resolved || !/\.[cm]?[jt]sx?$/.test(resolved)) continue
|
|
164
193
|
if (eligible(resolved)) {
|
|
194
|
+
if (node.type === 'ImportDeclaration' && node.specifiers.length) {
|
|
195
|
+
track(resolved)
|
|
196
|
+
const parsed = parse(id(resolved), await read(resolved))
|
|
197
|
+
const functions = new Set<string>()
|
|
198
|
+
let defaultBinding: string | undefined
|
|
199
|
+
for (const statement of parsed.program.body) {
|
|
200
|
+
if (
|
|
201
|
+
statement.type === 'ExportDefaultDeclaration' &&
|
|
202
|
+
[
|
|
203
|
+
'FunctionDeclaration',
|
|
204
|
+
'ClassDeclaration',
|
|
205
|
+
'ArrowFunctionExpression',
|
|
206
|
+
'FunctionExpression',
|
|
207
|
+
].includes(statement.declaration.type)
|
|
208
|
+
) {
|
|
209
|
+
functions.add('default')
|
|
210
|
+
if (
|
|
211
|
+
statement.declaration.type === 'FunctionDeclaration' ||
|
|
212
|
+
statement.declaration.type === 'ClassDeclaration'
|
|
213
|
+
)
|
|
214
|
+
defaultBinding = statement.declaration.id?.name
|
|
215
|
+
}
|
|
216
|
+
if (statement.type !== 'ExportNamedDeclaration') continue
|
|
217
|
+
const declaration = statement.declaration
|
|
218
|
+
if (
|
|
219
|
+
(declaration?.type === 'FunctionDeclaration' ||
|
|
220
|
+
declaration?.type === 'ClassDeclaration') &&
|
|
221
|
+
declaration.id
|
|
222
|
+
)
|
|
223
|
+
functions.add(declaration.id.name)
|
|
224
|
+
if (declaration?.type === 'VariableDeclaration')
|
|
225
|
+
for (const item of declaration.declarations) {
|
|
226
|
+
if (
|
|
227
|
+
item.id.type === 'Identifier' &&
|
|
228
|
+
item.init &&
|
|
229
|
+
[
|
|
230
|
+
'ArrowFunctionExpression',
|
|
231
|
+
'FunctionExpression',
|
|
232
|
+
'ClassExpression',
|
|
233
|
+
].includes(item.init.type)
|
|
234
|
+
)
|
|
235
|
+
functions.add(item.id.name)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
for (const statement of parsed.program.body) {
|
|
239
|
+
const declaration =
|
|
240
|
+
statement.type === 'ExportNamedDeclaration'
|
|
241
|
+
? statement.declaration
|
|
242
|
+
: statement
|
|
243
|
+
if (
|
|
244
|
+
declaration?.type === 'TSModuleDeclaration' &&
|
|
245
|
+
declaration.id.type === 'Identifier'
|
|
246
|
+
) {
|
|
247
|
+
functions.delete(declaration.id.name)
|
|
248
|
+
if (declaration.id.name === defaultBinding)
|
|
249
|
+
functions.delete('default')
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (
|
|
253
|
+
!parsed.errors.length &&
|
|
254
|
+
node.specifiers.every((specifier) =>
|
|
255
|
+
specifier.type === 'ImportDefaultSpecifier'
|
|
256
|
+
? functions.has('default')
|
|
257
|
+
: specifier.type === 'ImportSpecifier' &&
|
|
258
|
+
(specifier.importKind === 'type' ||
|
|
259
|
+
functions.has(
|
|
260
|
+
specifier.imported.type === 'Identifier'
|
|
261
|
+
? specifier.imported.name
|
|
262
|
+
: specifier.imported.value,
|
|
263
|
+
)),
|
|
264
|
+
)
|
|
265
|
+
) {
|
|
266
|
+
runtime.add(resolved)
|
|
267
|
+
track(resolved)
|
|
268
|
+
continue
|
|
269
|
+
}
|
|
270
|
+
}
|
|
165
271
|
links[specifier] = id(resolved)
|
|
166
272
|
await visit(resolved)
|
|
167
273
|
continue
|
|
168
274
|
}
|
|
169
|
-
|
|
170
275
|
const sidecar = `${resolved}.zyzz.json`
|
|
171
276
|
try {
|
|
172
277
|
contracts[resolved] = await read(sidecar)
|
|
173
|
-
|
|
278
|
+
track(sidecar)
|
|
174
279
|
links[specifier] = resolved
|
|
175
280
|
} catch (error) {
|
|
176
281
|
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
@@ -178,48 +283,7 @@ export function create(options: create.Options) {
|
|
|
178
283
|
}
|
|
179
284
|
}
|
|
180
285
|
|
|
181
|
-
|
|
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)
|
|
286
|
+
await visit(context.resourcePath, source)
|
|
223
287
|
const loaded = new Set<string>()
|
|
224
288
|
async function dependencies(file: string): Promise<void> {
|
|
225
289
|
if (loaded.has(file)) return
|
|
@@ -240,13 +304,7 @@ export function create(options: create.Options) {
|
|
|
240
304
|
for (const dependency of library.dependencies) {
|
|
241
305
|
let owner = file
|
|
242
306
|
for (const specifier of dependency) {
|
|
243
|
-
const target = await
|
|
244
|
-
(accept, reject) => {
|
|
245
|
-
resolve(Path.dirname(owner), specifier, (error, value) =>
|
|
246
|
-
error ? reject(error) : accept(value),
|
|
247
|
-
)
|
|
248
|
-
},
|
|
249
|
-
)
|
|
307
|
+
const target = await resolve(Path.dirname(owner), specifier)
|
|
250
308
|
if (!target || !Path.isAbsolute(target))
|
|
251
309
|
throw new Error('Unable to resolve packed stylesheet dependency.')
|
|
252
310
|
|
|
@@ -255,7 +313,7 @@ export function create(options: create.Options) {
|
|
|
255
313
|
if (!Object.hasOwn(contracts, target)) {
|
|
256
314
|
const sidecar = `${target}.zyzz.json`
|
|
257
315
|
contracts[target] = await read(sidecar)
|
|
258
|
-
|
|
316
|
+
track(sidecar)
|
|
259
317
|
}
|
|
260
318
|
await dependencies(target)
|
|
261
319
|
owner = target
|
|
@@ -266,20 +324,36 @@ export function create(options: create.Options) {
|
|
|
266
324
|
|
|
267
325
|
const graph = compiler.compile({
|
|
268
326
|
[Syntax.cache]: programs,
|
|
327
|
+
[Stylesheets.entry]: id(context.resourcePath),
|
|
269
328
|
contracts,
|
|
329
|
+
development: options.development,
|
|
270
330
|
imports,
|
|
271
331
|
modules,
|
|
272
332
|
reset,
|
|
273
333
|
})
|
|
274
334
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
335
|
+
const entry = id(context.resourcePath)
|
|
336
|
+
const output = graph.modules[entry]!
|
|
337
|
+
// Unchanged runtime modules rely on Next's import graph, not transitive style inputs.
|
|
338
|
+
if (output.code === source && !output.css) {
|
|
339
|
+
context.addDependency(context.resourcePath)
|
|
340
|
+
for (const file of runtime) context.addDependency(file)
|
|
341
|
+
for (const file of tracked)
|
|
342
|
+
if (file.endsWith('.zyzz.json')) context.addDependency(file)
|
|
343
|
+
for (const target of Object.values(imports[entry] ?? {})) {
|
|
344
|
+
if (!target) continue
|
|
345
|
+
context.addDependency(
|
|
346
|
+
target.startsWith('app/')
|
|
347
|
+
? Path.join(root, target.slice(4))
|
|
348
|
+
: `${target}.zyzz.json`,
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
for (const file of tracked) context.addDependency(file)
|
|
353
|
+
}
|
|
354
|
+
|
|
279
355
|
for (const file of libraries.keys())
|
|
280
356
|
if (!Object.hasOwn(contracts, file)) libraries.delete(file)
|
|
281
|
-
for (const name of syntax.keys())
|
|
282
|
-
if (!programs.has(name)) syntax.delete(name)
|
|
283
357
|
|
|
284
358
|
return graph
|
|
285
359
|
}
|
|
@@ -287,8 +361,10 @@ export function create(options: create.Options) {
|
|
|
287
361
|
|
|
288
362
|
/** Project cache settings. */
|
|
289
363
|
export declare namespace create {
|
|
290
|
-
/** Isolates source
|
|
364
|
+
/** Isolates imported source graphs and shared stylesheet options. */
|
|
291
365
|
type Options = {
|
|
366
|
+
/** Keep development class names stable across value edits. */
|
|
367
|
+
readonly development?: boolean | undefined
|
|
292
368
|
/** Include the packaged reset in shared CSS. */
|
|
293
369
|
readonly reset?: boolean | undefined
|
|
294
370
|
/** Absolute application directory. */
|
|
@@ -300,38 +376,3 @@ async function stamp(file: string) {
|
|
|
300
376
|
const stat = await Fs.stat(file, { bigint: true })
|
|
301
377
|
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`
|
|
302
378
|
}
|
|
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
|
-
}
|