zyzz 0.0.7 → 0.0.9
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 +7 -7
- package/dist/Style.d.ts +2 -2
- package/dist/Style.d.ts.map +1 -1
- package/dist/Style.js +4 -4
- 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/Value.d.ts +5 -5
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +18 -64
- 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 +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 +13 -9
- package/dist/unplugin/index.js.map +1 -1
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +130 -27
- package/dist/vite/index.js.map +1 -1
- package/package.json +1 -1
- package/src/Style.ts +6 -6
- 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/Value.ts +40 -88
- 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 +12 -8
- package/src/vite/index.ts +161 -35
package/src/next/loader.ts
CHANGED
|
@@ -3,16 +3,19 @@ import * as AtRules from '../compiler/internal/AtRules.js'
|
|
|
3
3
|
import * as Crypto from 'node:crypto'
|
|
4
4
|
import * as Fs from 'node:fs/promises'
|
|
5
5
|
import * as Path from 'node:path'
|
|
6
|
+
import * as Stylesheets from '../compiler/internal/Stylesheets.js'
|
|
6
7
|
import * as Project from './internal/Project.js'
|
|
7
8
|
|
|
8
9
|
type Context = Project.Context & {
|
|
9
10
|
async(): (error: Error | null, code?: string, map?: object) => void
|
|
10
11
|
getOptions(): {
|
|
11
12
|
bundler: string
|
|
12
|
-
|
|
13
|
+
development?: boolean | undefined
|
|
14
|
+
mode?: 'style' | undefined
|
|
13
15
|
reset?: boolean | undefined
|
|
14
16
|
root: string
|
|
15
17
|
}
|
|
18
|
+
resourceQuery?: string | undefined
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
// Next owns loader worker lifetimes. Each project retains only its latest graph in that worker.
|
|
@@ -32,6 +35,15 @@ async function compile(context: Context, source: string) {
|
|
|
32
35
|
const options = context.getOptions()
|
|
33
36
|
const root = options.root
|
|
34
37
|
const directory = Path.resolve(root, '.zyzz', 'next')
|
|
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
|
+
|
|
35
47
|
const eligible = (file: string) =>
|
|
36
48
|
/\.[cm]?[jt]sx?$/.test(file) &&
|
|
37
49
|
!/\.(?:d|test|test-d|bench|bench-d)\.[cm]?[jt]sx?$/.test(file) &&
|
|
@@ -50,97 +62,25 @@ async function compile(context: Context, source: string) {
|
|
|
50
62
|
)
|
|
51
63
|
return { code: source, map: undefined }
|
|
52
64
|
|
|
53
|
-
if (
|
|
54
|
-
return { code: source, map: undefined }
|
|
65
|
+
if (!eligible(context.resourcePath)) return { code: source, map: undefined }
|
|
55
66
|
|
|
56
|
-
const key = JSON.stringify([
|
|
67
|
+
const key = JSON.stringify([
|
|
68
|
+
root,
|
|
69
|
+
options.bundler,
|
|
70
|
+
options.reset ?? false,
|
|
71
|
+
options.development ?? false,
|
|
72
|
+
])
|
|
57
73
|
let project = projects.get(key)
|
|
58
74
|
if (!project) {
|
|
59
75
|
project = Project.create(options)
|
|
60
76
|
projects.set(key, project)
|
|
61
77
|
}
|
|
62
|
-
const graph = await project.compile(
|
|
63
|
-
context,
|
|
64
|
-
options.mode === 'shared' ? undefined : source,
|
|
65
|
-
)
|
|
78
|
+
const graph = await project.compile(context, source)
|
|
66
79
|
const output =
|
|
67
80
|
graph.modules[
|
|
68
81
|
`app/${Path.relative(root, context.resourcePath).split(Path.sep).join('/')}`
|
|
69
82
|
]
|
|
70
83
|
|
|
71
|
-
const assets = new Map<string, string>()
|
|
72
|
-
for (const [placeholder, target] of Object.entries(
|
|
73
|
-
graph.sharedAssets ?? {},
|
|
74
|
-
)) {
|
|
75
|
-
const raw = target.split(/[?#]/)[0]!
|
|
76
|
-
const file = await Fs.realpath(
|
|
77
|
-
target.startsWith('app/')
|
|
78
|
-
? Path.join(root, decodeURIComponent(raw.slice(4)))
|
|
79
|
-
: decodeURIComponent(raw),
|
|
80
|
-
)
|
|
81
|
-
const identity = graph.sharedAssetOwners?.[placeholder]
|
|
82
|
-
let owner = root
|
|
83
|
-
if (identity && Path.isAbsolute(identity)) {
|
|
84
|
-
owner = Path.dirname(identity)
|
|
85
|
-
for (;;) {
|
|
86
|
-
try {
|
|
87
|
-
await Fs.access(Path.join(owner, 'package.json'))
|
|
88
|
-
break
|
|
89
|
-
} catch (error) {
|
|
90
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
|
|
91
|
-
const parent = Path.dirname(owner)
|
|
92
|
-
if (parent === owner)
|
|
93
|
-
throw new Error('Packed assets require an owning package.json.')
|
|
94
|
-
owner = parent
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const relative = Path.relative(await Fs.realpath(owner), file)
|
|
100
|
-
if (
|
|
101
|
-
!identity ||
|
|
102
|
-
relative === '..' ||
|
|
103
|
-
relative.startsWith(`..${Path.sep}`) ||
|
|
104
|
-
Path.isAbsolute(relative)
|
|
105
|
-
)
|
|
106
|
-
throw new Error('Asset path escapes its owning package.')
|
|
107
|
-
|
|
108
|
-
context.addDependency(file)
|
|
109
|
-
assets.set(
|
|
110
|
-
placeholder,
|
|
111
|
-
Path.relative(
|
|
112
|
-
options.bundler === 'turbopack'
|
|
113
|
-
? Path.dirname(context.resourcePath)
|
|
114
|
-
: directory,
|
|
115
|
-
file,
|
|
116
|
-
)
|
|
117
|
-
.split(Path.sep)
|
|
118
|
-
.map(encodeURIComponent)
|
|
119
|
-
.join('/') + target.slice(raw.length),
|
|
120
|
-
)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const shared = graph.sharedCss
|
|
124
|
-
? AtRules.transform({
|
|
125
|
-
code: Buffer.from(graph.sharedCss),
|
|
126
|
-
filename: 'shared.css',
|
|
127
|
-
inputSourceMap: JSON.stringify(graph.sharedCssMap),
|
|
128
|
-
sourceMap: true,
|
|
129
|
-
visitor: {
|
|
130
|
-
Url: (url) =>
|
|
131
|
-
assets.has(url.url)
|
|
132
|
-
? { ...url, url: assets.get(url.url)! }
|
|
133
|
-
: undefined,
|
|
134
|
-
},
|
|
135
|
-
})
|
|
136
|
-
: undefined
|
|
137
|
-
if (options.mode === 'shared')
|
|
138
|
-
return {
|
|
139
|
-
code: shared?.code.toString() ?? '',
|
|
140
|
-
map: shared?.map
|
|
141
|
-
? (JSON.parse(shared.map.toString()) as object)
|
|
142
|
-
: undefined,
|
|
143
|
-
}
|
|
144
84
|
if (!output) throw new Error('Missing Next.js source compilation.')
|
|
145
85
|
// Bundlers resolve map sources relative to the loader resource, not graph identities.
|
|
146
86
|
const sources = (names: readonly (string | null)[]) =>
|
|
@@ -153,63 +93,180 @@ async function compile(context: Context, source: string) {
|
|
|
153
93
|
sources: sources(output.map.sources),
|
|
154
94
|
}
|
|
155
95
|
const cssMap = { ...output.cssMap, sources: sources(output.cssMap.sources) }
|
|
156
|
-
if (options.mode === 'style') return { code: output.css, map: cssMap }
|
|
157
96
|
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
)
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
|
191
178
|
}
|
|
179
|
+
|
|
192
180
|
const styles = [
|
|
193
|
-
|
|
194
|
-
|
|
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) },
|
|
195
197
|
]
|
|
196
198
|
const requests: string[] = []
|
|
197
199
|
await Fs.mkdir(directory, { recursive: true })
|
|
198
|
-
|
|
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()) {
|
|
199
210
|
if (!stylesheet.css) continue
|
|
200
211
|
const css =
|
|
201
212
|
stylesheet.css +
|
|
202
213
|
(stylesheet.map
|
|
203
214
|
? `\n/*# sourceMappingURL=data:application/json;base64,${Buffer.from(stylesheet.map).toString('base64')} */`
|
|
204
215
|
: '')
|
|
205
|
-
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')
|
|
206
230
|
const file = Path.join(directory, `${hash}.css`)
|
|
207
|
-
|
|
208
|
-
await Fs.
|
|
209
|
-
|
|
210
|
-
|
|
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}`
|
|
211
268
|
}
|
|
212
|
-
const relative = Path.relative(Path.dirname(context.resourcePath),
|
|
269
|
+
const relative = Path.relative(Path.dirname(context.resourcePath), request)
|
|
213
270
|
.split(Path.sep)
|
|
214
271
|
.join('/')
|
|
215
272
|
requests.push(
|
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
|
})()
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Retains unchanged source text and syntax within an adapter instance. @module */
|
|
2
|
+
import * as Fs from 'node:fs'
|
|
3
|
+
import * as Syntax from '../../compiler/internal/Syntax.js'
|
|
4
|
+
|
|
5
|
+
/** Creates a source cache; resolution and directory discovery remain owned by the host. */
|
|
6
|
+
export function create() {
|
|
7
|
+
const files = new Map<string, { source: string; version: string }>()
|
|
8
|
+
const syntax = new Map<
|
|
9
|
+
string,
|
|
10
|
+
{ parsed: ReturnType<typeof Syntax.parse>; source: string }
|
|
11
|
+
>()
|
|
12
|
+
|
|
13
|
+
function parse(options: Syntax.parse.Options) {
|
|
14
|
+
let entry = syntax.get(options.moduleId)
|
|
15
|
+
if (entry?.source !== options.source) {
|
|
16
|
+
entry = { parsed: Syntax.parse(options), source: options.source }
|
|
17
|
+
syntax.set(options.moduleId, entry)
|
|
18
|
+
}
|
|
19
|
+
return entry.parsed
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
clear() {
|
|
24
|
+
files.clear()
|
|
25
|
+
syntax.clear()
|
|
26
|
+
},
|
|
27
|
+
delete(file: string, moduleId: string) {
|
|
28
|
+
files.delete(file)
|
|
29
|
+
syntax.delete(moduleId)
|
|
30
|
+
},
|
|
31
|
+
parse,
|
|
32
|
+
programs(modules: Readonly<Record<string, string>>) {
|
|
33
|
+
return new Map(
|
|
34
|
+
Object.entries(modules).map(([moduleId, source]) => [
|
|
35
|
+
moduleId,
|
|
36
|
+
parse({ moduleId, source }),
|
|
37
|
+
]),
|
|
38
|
+
)
|
|
39
|
+
},
|
|
40
|
+
async read(file: string) {
|
|
41
|
+
try {
|
|
42
|
+
const stat = await Fs.promises.stat(file, { bigint: true })
|
|
43
|
+
const version = `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`
|
|
44
|
+
let entry = files.get(file)
|
|
45
|
+
if (entry?.version !== version) {
|
|
46
|
+
entry = { source: await Fs.promises.readFile(file, 'utf8'), version }
|
|
47
|
+
files.set(file, entry)
|
|
48
|
+
}
|
|
49
|
+
return entry.source
|
|
50
|
+
} catch (error) {
|
|
51
|
+
files.delete(file)
|
|
52
|
+
throw error
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
readSync(file: string) {
|
|
56
|
+
try {
|
|
57
|
+
const stat = Fs.statSync(file, { bigint: true })
|
|
58
|
+
const version = `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`
|
|
59
|
+
let entry = files.get(file)
|
|
60
|
+
if (entry?.version !== version) {
|
|
61
|
+
entry = { source: Fs.readFileSync(file, 'utf8'), version }
|
|
62
|
+
files.set(file, entry)
|
|
63
|
+
}
|
|
64
|
+
return entry.source
|
|
65
|
+
} catch (error) {
|
|
66
|
+
files.delete(file)
|
|
67
|
+
throw error
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
retain(
|
|
71
|
+
paths: ReadonlySet<string>,
|
|
72
|
+
modules: Readonly<Record<string, string>>,
|
|
73
|
+
) {
|
|
74
|
+
for (const file of files.keys()) if (!paths.has(file)) files.delete(file)
|
|
75
|
+
for (const id of syntax.keys())
|
|
76
|
+
if (!Object.hasOwn(modules, id)) syntax.delete(id)
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
}
|
package/src/unplugin/index.ts
CHANGED
|
@@ -4,7 +4,8 @@ import * as Crypto from 'node:crypto'
|
|
|
4
4
|
import * as Fs from 'node:fs/promises'
|
|
5
5
|
import * as Path from 'node:path'
|
|
6
6
|
import * as Reset from '../node/Reset.js'
|
|
7
|
-
import * as
|
|
7
|
+
import * as Snapshot from '../node/internal/Snapshot.js'
|
|
8
|
+
import * as Syntax from '../compiler/internal/Syntax.js'
|
|
8
9
|
import { createUnplugin } from 'unplugin'
|
|
9
10
|
import type { UnpluginBuildContext } from 'unplugin'
|
|
10
11
|
import * as Graph from '../compiler/Graph.js'
|
|
@@ -30,6 +31,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
30
31
|
(options = {}, meta) => {
|
|
31
32
|
const root = Path.resolve(options.root ?? process.cwd())
|
|
32
33
|
const compiler = Graph.create()
|
|
34
|
+
const snapshot = Snapshot.create()
|
|
33
35
|
let graph: Graph.compile.ReturnType | undefined
|
|
34
36
|
let pending: Promise<void> | undefined
|
|
35
37
|
let files = new Set<string>()
|
|
@@ -138,7 +140,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
138
140
|
sourcePattern.test(file) &&
|
|
139
141
|
!excludedPattern.test(file)
|
|
140
142
|
) {
|
|
141
|
-
const source = await
|
|
143
|
+
const source = await snapshot.read(file)
|
|
142
144
|
|
|
143
145
|
files.add(file)
|
|
144
146
|
sources.set(file, source)
|
|
@@ -205,7 +207,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
205
207
|
|
|
206
208
|
files.add(sidecar)
|
|
207
209
|
try {
|
|
208
|
-
contracts[file] = await
|
|
210
|
+
contracts[file] = await snapshot.read(sidecar)
|
|
209
211
|
} catch (error) {
|
|
210
212
|
if (
|
|
211
213
|
!required &&
|
|
@@ -235,8 +237,9 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
235
237
|
await scan(root)
|
|
236
238
|
for (const [file, source] of sources) {
|
|
237
239
|
const links = (imports[identity(file)] = Object.create(null))
|
|
238
|
-
for (const node of
|
|
239
|
-
|
|
240
|
+
for (const node of snapshot.parse({
|
|
241
|
+
moduleId: identity(file),
|
|
242
|
+
source,
|
|
240
243
|
}).program.body) {
|
|
241
244
|
if (
|
|
242
245
|
(node.type !== 'ImportDeclaration' &&
|
|
@@ -282,6 +285,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
282
285
|
}
|
|
283
286
|
|
|
284
287
|
const result = compiler.compile({
|
|
288
|
+
[Syntax.cache]: snapshot.programs(modules),
|
|
285
289
|
compiler: options.compiler,
|
|
286
290
|
reset: options.reset ? Reset.read() : undefined,
|
|
287
291
|
contracts,
|
|
@@ -500,6 +504,7 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
500
504
|
Catalogs.read,
|
|
501
505
|
),
|
|
502
506
|
).join('\n')
|
|
507
|
+
snapshot.retain(files, modules)
|
|
503
508
|
graph = result
|
|
504
509
|
if (meta.framework === 'webpack') emit(this)
|
|
505
510
|
if (meta.framework === 'rollup')
|
|
@@ -572,9 +577,8 @@ const portable = createUnplugin<Options | undefined, false>(
|
|
|
572
577
|
},
|
|
573
578
|
webpack(compiler) {
|
|
574
579
|
compiler.hooks.watchRun.tap('zyzz', () => {
|
|
575
|
-
//
|
|
576
|
-
for (const file of compiler.
|
|
577
|
-
if (directories.has(file)) compiler.inputFileSystem?.purge?.(file)
|
|
580
|
+
// The graph rereads every source, including files absent from Webpack's invalidation batch.
|
|
581
|
+
for (const file of files) compiler.inputFileSystem?.purge?.(file)
|
|
578
582
|
})
|
|
579
583
|
},
|
|
580
584
|
}
|