zyzz 0.0.6 → 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/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/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/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/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 +28 -0
- package/dist/next/internal/Project.d.ts.map +1 -0
- package/dist/next/internal/Project.js +295 -0
- package/dist/next/internal/Project.js.map +1 -0
- package/dist/next/loader.d.ts +5 -6
- package/dist/next/loader.d.ts.map +1 -1
- package/dist/next/loader.js +161 -247
- 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/variants.d.ts +1 -1
- package/dist/variants.d.ts.map +1 -1
- package/dist/variants.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/Style.ts +42 -8
- 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/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/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 +378 -0
- package/src/next/loader.ts +195 -315
- package/src/node/Host.ts +15 -3
- package/src/node/internal/Snapshot.ts +79 -0
- package/src/unplugin/index.ts +10 -5
- package/src/variants.ts +1 -0
- package/src/vite/index.ts +24 -4
package/src/next/loader.ts
CHANGED
|
@@ -1,33 +1,26 @@
|
|
|
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
|
|
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 Stylesheets from '../compiler/internal/Stylesheets.js'
|
|
7
|
+
import * as Project from './internal/Project.js'
|
|
10
8
|
|
|
11
|
-
type Context = {
|
|
12
|
-
addContextDependency(directory: string): void
|
|
13
|
-
addDependency(file: string): void
|
|
9
|
+
type Context = Project.Context & {
|
|
14
10
|
async(): (error: Error | null, code?: string, map?: object) => void
|
|
15
11
|
getOptions(): {
|
|
16
12
|
bundler: string
|
|
17
|
-
|
|
13
|
+
development?: boolean | undefined
|
|
14
|
+
mode?: 'style' | undefined
|
|
18
15
|
reset?: boolean | undefined
|
|
19
16
|
root: string
|
|
20
17
|
}
|
|
21
|
-
|
|
22
|
-
options: object,
|
|
23
|
-
): (
|
|
24
|
-
directory: string,
|
|
25
|
-
specifier: string,
|
|
26
|
-
callback: (error: Error | null, file?: string | false) => void,
|
|
27
|
-
) => void
|
|
28
|
-
resourcePath: string
|
|
18
|
+
resourceQuery?: string | undefined
|
|
29
19
|
}
|
|
30
20
|
|
|
21
|
+
// Next owns loader worker lifetimes. Each project retains only its latest graph in that worker.
|
|
22
|
+
const projects = new Map<string, ReturnType<typeof Project.create>>()
|
|
23
|
+
|
|
31
24
|
/** Loader-runner boundary used by Webpack and Turbopack. */
|
|
32
25
|
export default function loader(this: Context, source: string): void {
|
|
33
26
|
const callback = this.async()
|
|
@@ -42,14 +35,15 @@ async function compile(context: Context, source: string) {
|
|
|
42
35
|
const options = context.getOptions()
|
|
43
36
|
const root = options.root
|
|
44
37
|
const directory = Path.resolve(root, '.zyzz', 'next')
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
if (options.mode) {
|
|
39
|
+
const hash = new URLSearchParams(context.resourceQuery).get('zyzz')
|
|
40
|
+
if (!hash || !/^[a-f0-9]{64}$/.test(hash))
|
|
41
|
+
throw new Error('Invalid generated stylesheet request.')
|
|
42
|
+
const file = Path.join(directory, `${hash}.css`)
|
|
43
|
+
context.addDependency(file)
|
|
44
|
+
return { code: await Fs.readFile(file, 'utf8'), map: undefined }
|
|
45
|
+
}
|
|
46
|
+
|
|
53
47
|
const eligible = (file: string) =>
|
|
54
48
|
/\.[cm]?[jt]sx?$/.test(file) &&
|
|
55
49
|
!/\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/.test(file) &&
|
|
@@ -68,221 +62,25 @@ async function compile(context: Context, source: string) {
|
|
|
68
62
|
)
|
|
69
63
|
return { code: source, map: undefined }
|
|
70
64
|
|
|
71
|
-
if (
|
|
72
|
-
return { code: source, map: undefined }
|
|
73
|
-
|
|
74
|
-
async function visit(file: string, text?: string): Promise<void> {
|
|
75
|
-
const name = id(file)
|
|
76
|
-
if (Object.hasOwn(modules, name)) return
|
|
77
|
-
|
|
78
|
-
context.addDependency(file)
|
|
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
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function discover(directory: string): Promise<void> {
|
|
138
|
-
const items = await Fs.readdir(directory, { withFileTypes: true })
|
|
139
|
-
|
|
140
|
-
// Recursive tracking of a package root follows every installed dependency.
|
|
141
|
-
// A dependency symlink back to an ancestor, as a package linked from its
|
|
142
|
-
// own repository, is rejected as a loop, so such a root is tracked through
|
|
143
|
-
// its files and subdirectories instead.
|
|
144
|
-
if (
|
|
145
|
-
!items.some((item) => item.name === 'node_modules') ||
|
|
146
|
-
!(await loops(directory))
|
|
147
|
-
)
|
|
148
|
-
context.addContextDependency(directory)
|
|
65
|
+
if (!eligible(context.resourcePath)) return { code: source, map: undefined }
|
|
149
66
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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)]
|
|
212
|
-
|
|
213
|
-
const assets = new Map<string, string>()
|
|
214
|
-
for (const [placeholder, target] of Object.entries(
|
|
215
|
-
graph.sharedAssets ?? {},
|
|
216
|
-
)) {
|
|
217
|
-
const raw = target.split(/[?#]/)[0]!
|
|
218
|
-
const file = await Fs.realpath(
|
|
219
|
-
target.startsWith('app/')
|
|
220
|
-
? Path.join(root, decodeURIComponent(raw.slice(4)))
|
|
221
|
-
: decodeURIComponent(raw),
|
|
222
|
-
)
|
|
223
|
-
const identity = graph.sharedAssetOwners?.[placeholder]
|
|
224
|
-
let owner = root
|
|
225
|
-
if (identity && Path.isAbsolute(identity)) {
|
|
226
|
-
owner = Path.dirname(identity)
|
|
227
|
-
for (;;) {
|
|
228
|
-
try {
|
|
229
|
-
await Fs.access(Path.join(owner, 'package.json'))
|
|
230
|
-
break
|
|
231
|
-
} catch (error) {
|
|
232
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
233
|
-
const parent = Path.dirname(owner)
|
|
234
|
-
if (parent === owner)
|
|
235
|
-
throw new Error('Packed assets require an owning package.json.')
|
|
236
|
-
owner = parent
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const relative = Path.relative(await Fs.realpath(owner), file)
|
|
242
|
-
if (
|
|
243
|
-
!identity ||
|
|
244
|
-
relative === '..' ||
|
|
245
|
-
relative.startsWith(`..${Path.sep}`) ||
|
|
246
|
-
Path.isAbsolute(relative)
|
|
247
|
-
)
|
|
248
|
-
throw new Error('Asset path escapes its owning package.')
|
|
249
|
-
|
|
250
|
-
context.addDependency(file)
|
|
251
|
-
assets.set(
|
|
252
|
-
placeholder,
|
|
253
|
-
Path.relative(
|
|
254
|
-
options.bundler === 'turbopack'
|
|
255
|
-
? Path.dirname(context.resourcePath)
|
|
256
|
-
: directory,
|
|
257
|
-
file,
|
|
258
|
-
)
|
|
259
|
-
.split(Path.sep)
|
|
260
|
-
.map(encodeURIComponent)
|
|
261
|
-
.join('/') + target.slice(raw.length),
|
|
262
|
-
)
|
|
67
|
+
const key = JSON.stringify([
|
|
68
|
+
root,
|
|
69
|
+
options.bundler,
|
|
70
|
+
options.reset ?? false,
|
|
71
|
+
options.development ?? false,
|
|
72
|
+
])
|
|
73
|
+
let project = projects.get(key)
|
|
74
|
+
if (!project) {
|
|
75
|
+
project = Project.create(options)
|
|
76
|
+
projects.set(key, project)
|
|
263
77
|
}
|
|
78
|
+
const graph = await project.compile(context, source)
|
|
79
|
+
const output =
|
|
80
|
+
graph.modules[
|
|
81
|
+
`app/${Path.relative(root, context.resourcePath).split(Path.sep).join('/')}`
|
|
82
|
+
]
|
|
264
83
|
|
|
265
|
-
const shared = graph.sharedCss
|
|
266
|
-
? AtRules.transform({
|
|
267
|
-
code: Buffer.from(graph.sharedCss),
|
|
268
|
-
filename: 'shared.css',
|
|
269
|
-
inputSourceMap: JSON.stringify(graph.sharedCssMap),
|
|
270
|
-
sourceMap: true,
|
|
271
|
-
visitor: {
|
|
272
|
-
Url: (url) =>
|
|
273
|
-
assets.has(url.url)
|
|
274
|
-
? { ...url, url: assets.get(url.url)! }
|
|
275
|
-
: undefined,
|
|
276
|
-
},
|
|
277
|
-
})
|
|
278
|
-
: undefined
|
|
279
|
-
if (options.mode === 'shared')
|
|
280
|
-
return {
|
|
281
|
-
code: shared?.code.toString() ?? '',
|
|
282
|
-
map: shared?.map
|
|
283
|
-
? (JSON.parse(shared.map.toString()) as object)
|
|
284
|
-
: undefined,
|
|
285
|
-
}
|
|
286
84
|
if (!output) throw new Error('Missing Next.js source compilation.')
|
|
287
85
|
// Bundlers resolve map sources relative to the loader resource, not graph identities.
|
|
288
86
|
const sources = (names: readonly (string | null)[]) =>
|
|
@@ -295,63 +93,180 @@ async function compile(context: Context, source: string) {
|
|
|
295
93
|
sources: sources(output.map.sources),
|
|
296
94
|
}
|
|
297
95
|
const cssMap = { ...output.cssMap, sources: sources(output.cssMap.sources) }
|
|
298
|
-
if (options.mode === 'style') return { code: output.css, map: cssMap }
|
|
299
96
|
|
|
300
|
-
if (
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
)
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
97
|
+
if (
|
|
98
|
+
output.code === source &&
|
|
99
|
+
!output.css &&
|
|
100
|
+
!/(?:^|[/\\])(?:layout|_app)\.[cm]?[jt]sx?$/.test(context.resourcePath) &&
|
|
101
|
+
!graph.sharedCssMap?.sources.some((name) => name !== 'zyzz/reset.css') &&
|
|
102
|
+
!graph[Stylesheets.packed]?.length
|
|
103
|
+
)
|
|
104
|
+
return { code: source, map }
|
|
105
|
+
|
|
106
|
+
const shared = await compileShared({
|
|
107
|
+
css: graph.sharedCss ?? '',
|
|
108
|
+
map: graph.sharedCssMap!,
|
|
109
|
+
assets: graph.sharedAssets ?? {},
|
|
110
|
+
owners: graph.sharedAssetOwners ?? {},
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
async function compileShared(
|
|
114
|
+
stylesheet: ReturnType<typeof Stylesheets.render>,
|
|
115
|
+
) {
|
|
116
|
+
const assets = new Map<string, string>()
|
|
117
|
+
for (const [placeholder, target] of Object.entries(stylesheet.assets)) {
|
|
118
|
+
const raw = target.split(/[?#]/)[0]!
|
|
119
|
+
const file = await Fs.realpath(
|
|
120
|
+
target.startsWith('app/')
|
|
121
|
+
? Path.join(root, decodeURIComponent(raw.slice(4)))
|
|
122
|
+
: decodeURIComponent(raw),
|
|
123
|
+
)
|
|
124
|
+
const identity = stylesheet.owners[placeholder]
|
|
125
|
+
let owner = root
|
|
126
|
+
if (identity && Path.isAbsolute(identity)) {
|
|
127
|
+
owner = Path.dirname(identity)
|
|
128
|
+
for (;;) {
|
|
129
|
+
try {
|
|
130
|
+
await Fs.access(Path.join(owner, 'package.json'))
|
|
131
|
+
break
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
134
|
+
const parent = Path.dirname(owner)
|
|
135
|
+
if (parent === owner)
|
|
136
|
+
throw new Error('Packed assets require an owning package.json.')
|
|
137
|
+
owner = parent
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const relative = Path.relative(await Fs.realpath(owner), file)
|
|
143
|
+
if (
|
|
144
|
+
!identity ||
|
|
145
|
+
relative === '..' ||
|
|
146
|
+
relative.startsWith(`..${Path.sep}`) ||
|
|
147
|
+
Path.isAbsolute(relative)
|
|
148
|
+
)
|
|
149
|
+
throw new Error('Asset path escapes its owning package.')
|
|
150
|
+
|
|
151
|
+
context.addDependency(file)
|
|
152
|
+
assets.set(
|
|
153
|
+
placeholder,
|
|
154
|
+
Path.relative(directory, file)
|
|
155
|
+
.split(Path.sep)
|
|
156
|
+
.map(encodeURIComponent)
|
|
157
|
+
.join('/') + target.slice(raw.length),
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return stylesheet.css
|
|
162
|
+
? AtRules.transform({
|
|
163
|
+
code: Buffer.from(stylesheet.css),
|
|
164
|
+
filename: 'shared.css',
|
|
165
|
+
inputSourceMap: JSON.stringify({
|
|
166
|
+
...stylesheet.map,
|
|
167
|
+
sources: sources(stylesheet.map.sources),
|
|
168
|
+
}),
|
|
169
|
+
sourceMap: true,
|
|
170
|
+
visitor: {
|
|
171
|
+
Url: (url) =>
|
|
172
|
+
assets.has(url.url)
|
|
173
|
+
? { ...url, url: assets.get(url.url)! }
|
|
174
|
+
: undefined,
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
: undefined
|
|
333
178
|
}
|
|
179
|
+
|
|
334
180
|
const styles = [
|
|
335
|
-
|
|
336
|
-
|
|
181
|
+
...(await Promise.all(
|
|
182
|
+
(graph[Stylesheets.packed] ?? []).map(async (resource) => {
|
|
183
|
+
const output = await compileShared(resource)
|
|
184
|
+
return {
|
|
185
|
+
css: output?.code.toString(),
|
|
186
|
+
map: output?.map?.toString(),
|
|
187
|
+
id: resource.id,
|
|
188
|
+
}
|
|
189
|
+
}),
|
|
190
|
+
)),
|
|
191
|
+
{
|
|
192
|
+
id: undefined,
|
|
193
|
+
css: shared?.code.toString(),
|
|
194
|
+
map: shared?.map?.toString(),
|
|
195
|
+
},
|
|
196
|
+
{ id: undefined, css: output.css, map: JSON.stringify(cssMap) },
|
|
337
197
|
]
|
|
338
198
|
const requests: string[] = []
|
|
339
199
|
await Fs.mkdir(directory, { recursive: true })
|
|
340
|
-
|
|
200
|
+
try {
|
|
201
|
+
await Fs.writeFile(
|
|
202
|
+
Path.join(directory, 'package.json'),
|
|
203
|
+
JSON.stringify({ sideEffects: true, type: 'module' }),
|
|
204
|
+
{ flag: 'wx' },
|
|
205
|
+
)
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
|
|
208
|
+
}
|
|
209
|
+
for (const [index, stylesheet] of styles.entries()) {
|
|
341
210
|
if (!stylesheet.css) continue
|
|
342
211
|
const css =
|
|
343
212
|
stylesheet.css +
|
|
344
213
|
(stylesheet.map
|
|
345
214
|
? `\n/*# sourceMappingURL=data:application/json;base64,${Buffer.from(stylesheet.map).toString('base64')} */`
|
|
346
215
|
: '')
|
|
347
|
-
const hash = Crypto.createHash('sha256')
|
|
216
|
+
const hash = Crypto.createHash('sha256')
|
|
217
|
+
.update(
|
|
218
|
+
options.development &&
|
|
219
|
+
!(
|
|
220
|
+
index === (graph[Stylesheets.packed]?.length ?? 0) &&
|
|
221
|
+
graph.sharedCssMap?.sources.every(
|
|
222
|
+
(name) => name === 'zyzz/reset.css',
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
? (stylesheet.id ??
|
|
226
|
+
JSON.stringify([context.resourcePath, index, graph.dependencies]))
|
|
227
|
+
: css,
|
|
228
|
+
)
|
|
229
|
+
.digest('hex')
|
|
348
230
|
const file = Path.join(directory, `${hash}.css`)
|
|
349
|
-
|
|
350
|
-
await Fs.
|
|
351
|
-
|
|
352
|
-
|
|
231
|
+
if (options.development) {
|
|
232
|
+
const previous = await Fs.readFile(file, 'utf8').catch(
|
|
233
|
+
(error: NodeJS.ErrnoException) => {
|
|
234
|
+
if (error.code !== 'ENOENT') throw error
|
|
235
|
+
return undefined
|
|
236
|
+
},
|
|
237
|
+
)
|
|
238
|
+
if (previous !== css) {
|
|
239
|
+
const temporary = `${file}.${Crypto.randomUUID()}.tmp`
|
|
240
|
+
await Fs.writeFile(temporary, css)
|
|
241
|
+
await Fs.rename(temporary, file)
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
try {
|
|
245
|
+
await Fs.writeFile(file, css, { flag: 'wx' })
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
let request = file
|
|
251
|
+
if (options.development) {
|
|
252
|
+
// Server-component CSS needs a client graph edge for Next's native CSS HMR.
|
|
253
|
+
request = `${file}.js`
|
|
254
|
+
const stylesheet =
|
|
255
|
+
options.bundler === 'turbopack'
|
|
256
|
+
? `./style.css?zyzz=${hash}`
|
|
257
|
+
: `./${hash}.css`
|
|
258
|
+
const code = `'use client';import ${JSON.stringify(stylesheet)};`
|
|
259
|
+
const existing = await Fs.readFile(request, 'utf8').catch(
|
|
260
|
+
(error: NodeJS.ErrnoException) => {
|
|
261
|
+
if (error.code !== 'ENOENT') throw error
|
|
262
|
+
return undefined
|
|
263
|
+
},
|
|
264
|
+
)
|
|
265
|
+
if (existing !== code) await Fs.writeFile(request, code)
|
|
266
|
+
} else if (options.bundler === 'turbopack') {
|
|
267
|
+
request = Path.join(directory, 'style.css') + `?zyzz=${hash}`
|
|
353
268
|
}
|
|
354
|
-
const relative = Path.relative(Path.dirname(context.resourcePath),
|
|
269
|
+
const relative = Path.relative(Path.dirname(context.resourcePath), request)
|
|
355
270
|
.split(Path.sep)
|
|
356
271
|
.join('/')
|
|
357
272
|
requests.push(
|
|
@@ -361,38 +276,3 @@ async function compile(context: Context, source: string) {
|
|
|
361
276
|
|
|
362
277
|
return { code: `${output.code}\n${requests.join('\n')}`, map }
|
|
363
278
|
}
|
|
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/node/Host.ts
CHANGED
|
@@ -13,6 +13,7 @@ import * as Graph from '../compiler/Graph.js'
|
|
|
13
13
|
import * as AtRules from '../compiler/internal/AtRules.js'
|
|
14
14
|
import * as Catalogs from '../compiler/internal/Catalogs.js'
|
|
15
15
|
import * as Relative from '../compiler/internal/Relative.js'
|
|
16
|
+
import * as Snapshot from './internal/Snapshot.js'
|
|
16
17
|
import * as Syntax from '../compiler/internal/Syntax.js'
|
|
17
18
|
import * as Source from '../compiler/Source.js'
|
|
18
19
|
import * as Transform from '../compiler/Transform.js'
|
|
@@ -124,6 +125,7 @@ export async function create(options: create.Options): Promise<Runtime> {
|
|
|
124
125
|
const manifestPath = Path.join(outDir, '.zyzz.json')
|
|
125
126
|
|
|
126
127
|
const compiler = Graph.create()
|
|
128
|
+
const snapshot = Snapshot.create()
|
|
127
129
|
const stylesheets = new WeakMap<Transform.compile.ReturnType, Stylesheet>()
|
|
128
130
|
let closed = false
|
|
129
131
|
let closing: Promise<void> | undefined
|
|
@@ -250,7 +252,7 @@ export async function create(options: create.Options): Promise<Runtime> {
|
|
|
250
252
|
`Source path conflicts with host control files: ${name}`,
|
|
251
253
|
)
|
|
252
254
|
|
|
253
|
-
sources[name] = await
|
|
255
|
+
sources[name] = await snapshot.read(input)
|
|
254
256
|
}
|
|
255
257
|
|
|
256
258
|
const modules = Object.fromEntries(
|
|
@@ -306,7 +308,7 @@ export async function create(options: create.Options): Promise<Runtime> {
|
|
|
306
308
|
|
|
307
309
|
observe(`${file}.zyzz.json`)
|
|
308
310
|
try {
|
|
309
|
-
contracts[file] = await
|
|
311
|
+
contracts[file] = await snapshot.read(`${file}.zyzz.json`)
|
|
310
312
|
} catch (error) {
|
|
311
313
|
if (
|
|
312
314
|
!required &&
|
|
@@ -363,7 +365,7 @@ export async function create(options: create.Options): Promise<Runtime> {
|
|
|
363
365
|
const file = Path.join(root, name)
|
|
364
366
|
const resolutions = (imports[moduleId] = Object.create(null))
|
|
365
367
|
|
|
366
|
-
for (const node of
|
|
368
|
+
for (const node of snapshot.parse({ moduleId, source }).program.body) {
|
|
367
369
|
if (
|
|
368
370
|
(node.type !== 'ImportDeclaration' &&
|
|
369
371
|
node.type !== 'ExportNamedDeclaration' &&
|
|
@@ -439,6 +441,7 @@ export async function create(options: create.Options): Promise<Runtime> {
|
|
|
439
441
|
}
|
|
440
442
|
|
|
441
443
|
const graph = compiler.compile({
|
|
444
|
+
[Syntax.cache]: snapshot.programs(modules),
|
|
442
445
|
compiler: options.compiler,
|
|
443
446
|
native,
|
|
444
447
|
modules,
|
|
@@ -446,6 +449,14 @@ export async function create(options: create.Options): Promise<Runtime> {
|
|
|
446
449
|
imports,
|
|
447
450
|
})
|
|
448
451
|
|
|
452
|
+
snapshot.retain(
|
|
453
|
+
new Set([
|
|
454
|
+
...inputs,
|
|
455
|
+
...Object.keys(contracts).map((file) => `${file}.zyzz.json`),
|
|
456
|
+
]),
|
|
457
|
+
modules,
|
|
458
|
+
)
|
|
459
|
+
|
|
449
460
|
const artifactNames = [
|
|
450
461
|
'zyzz.css',
|
|
451
462
|
'zyzz.css.map',
|
|
@@ -818,6 +829,7 @@ export async function create(options: create.Options): Promise<Runtime> {
|
|
|
818
829
|
|
|
819
830
|
closing = (async () => {
|
|
820
831
|
await tail
|
|
832
|
+
snapshot.clear()
|
|
821
833
|
await lock.close()
|
|
822
834
|
await Fs.rm(lockPath)
|
|
823
835
|
})()
|