@exodus/stasis-plugins 1.0.0-beta.8 → 1.0.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -3
- package/src/esbuild.js +168 -115
- package/src/metro-babel-transformer.js +129 -0
- package/src/metro-native.js +201 -0
- package/src/metro-transformer.js +68 -55
- package/src/metro.js +258 -282
- package/src/plugins.js +102 -111
- package/src/rollup.js +64 -188
- package/src/webpack.js +61 -181
package/src/rollup.js
CHANGED
|
@@ -1,65 +1,50 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
|
-
import { isUtf8 } from 'node:buffer'
|
|
3
2
|
import { existsSync } from 'node:fs'
|
|
4
3
|
import { isBuiltin } from 'node:module'
|
|
5
4
|
import { dirname, isAbsolute, resolve as resolvePath } from 'node:path'
|
|
6
5
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
7
6
|
|
|
8
7
|
import { Bundle } from '@exodus/stasis-core/bundle'
|
|
9
|
-
import {
|
|
8
|
+
import { assertCodeUtf8, assertPersistableAttributes, attestedImport, classifyOrThrow, ownOrResolvedState, rebuildRefused } from './plugins.js'
|
|
10
9
|
import { State } from '@exodus/stasis-core/state'
|
|
11
10
|
// Pre-patch snapshot, not `import { readFile }`: under --fs=async that builtin is patched, and
|
|
12
11
|
// this plugin loads after, so a direct import would route rollup's reads through the capture hook.
|
|
13
|
-
import { realReadFile } from '@exodus/stasis-core/state-util'
|
|
14
|
-
import { classifyExtension } from '@exodus/stasis-core/util'
|
|
12
|
+
import { inScope, realReadFile } from '@exodus/stasis-core/state-util'
|
|
13
|
+
import { classifyExtension, isPathWithin } from '@exodus/stasis-core/util'
|
|
15
14
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
//
|
|
15
|
+
// An on-disk file by its absolute path, not a sibling plugin's virtual module (\0-prefixed or relative) or no importer.
|
|
16
|
+
const isDiskId = (id) => Boolean(id) && isAbsolute(id) && !id.startsWith('\0')
|
|
17
|
+
|
|
18
|
+
// Rollup chains resolveId/load hooks first-non-null-wins, so this plugin goes FIRST in `plugins`: listed later, a
|
|
19
|
+
// sibling resolver answers before stasis sees the edge (file BYTES still fail closed at the load hook).
|
|
20
20
|
export class StasisRollup {
|
|
21
21
|
#seen = new Set()
|
|
22
22
|
#state
|
|
23
23
|
#resources
|
|
24
|
-
|
|
25
|
-
// Build starts observed by this instance (watch rebuilds and separate rollup() calls sharing one
|
|
26
|
-
// plugin object both re-fire buildStart); the second is refused there.
|
|
27
24
|
#captureBuildCount = 0
|
|
28
25
|
|
|
29
26
|
name = 'stasis'
|
|
30
27
|
|
|
31
28
|
constructor(options = {}, unsupported = undefined) {
|
|
32
|
-
//
|
|
33
|
-
// ordinary rollup plugins after this one) -- refuse it so a port doesn't silently drop it.
|
|
29
|
+
// Transforms run as ordinary rollup plugins after this one: refuse a port's esbuild-style bag, not drop it.
|
|
34
30
|
assert.equal(unsupported, undefined,
|
|
35
31
|
'StasisRollup takes a single options argument -- there is no esbuild-style { loaders, transform } bag (run transforms as rollup plugins after this one)')
|
|
36
|
-
|
|
37
|
-
// closed via the instanceof miss.
|
|
38
|
-
const state = options instanceof State
|
|
39
|
-
? options
|
|
40
|
-
: resolvePluginState('StasisRollup', options, process.cwd()).state
|
|
32
|
+
const state = ownOrResolvedState('StasisRollup', options)
|
|
41
33
|
this.#state = state
|
|
42
|
-
// Cache the resolved resources Set for the per-file classify hot path.
|
|
43
34
|
this.#resources = state?.config.resources ?? new Set()
|
|
44
|
-
if (!state) return //
|
|
35
|
+
if (!state) return // inert: rollup sees only `name`
|
|
45
36
|
|
|
46
|
-
//
|
|
47
|
-
// bound to its PluginContext (whose resolve/getModuleInfo the hooks need), so instance state
|
|
48
|
-
// has to arrive via the closure instead.
|
|
37
|
+
// Rollup binds `this` to its PluginContext, so the instance arrives via the closure.
|
|
49
38
|
const plugin = this
|
|
50
|
-
this.resolveId =
|
|
39
|
+
this.resolveId = function (source, importer, opts) {
|
|
51
40
|
return plugin.#resolveId(this, source, importer, opts)
|
|
52
41
|
}
|
|
53
|
-
this.load =
|
|
42
|
+
this.load = function (id) {
|
|
54
43
|
return plugin.#load(this, id)
|
|
55
44
|
}
|
|
56
45
|
|
|
57
|
-
// A warm `cache:`
|
|
58
|
-
//
|
|
59
|
-
// edges and bytes from the attestation, and a load-mode build would consume cached code no
|
|
60
|
-
// hook re-verified. Strip it (rollup still builds a fresh cache for this run) -- StasisMetro's
|
|
61
|
-
// withStasis drops Metro's cacheStores for the same hazard. `cache: true/false` carries no
|
|
62
|
-
// prior modules, so only an object is a hazard.
|
|
46
|
+
// A warm `cache:` object reuses modules and resolutions WITHOUT calling resolveId/load, so they'd go unattested
|
|
47
|
+
// at capture and unverified at load. Rollup still builds a fresh cache for this run.
|
|
63
48
|
this.options = (inputOptions) => {
|
|
64
49
|
if (typeof inputOptions.cache !== 'object' || inputOptions.cache === null) return null
|
|
65
50
|
console.warn(
|
|
@@ -71,34 +56,18 @@ export class StasisRollup {
|
|
|
71
56
|
}
|
|
72
57
|
|
|
73
58
|
if (!state.config.loadBundle) {
|
|
74
|
-
// Watch
|
|
75
|
-
// changed bytes would emit new bytes while the bundle/lockfile keep the OLD ones. Watch rebuilds
|
|
76
|
-
// (and a second rollup() call reusing this instance) re-fire buildStart, so an instance counter
|
|
77
|
-
// catches the second and errors.
|
|
59
|
+
// Watch rebuilds, and a second rollup() call reusing this instance, re-fire buildStart.
|
|
78
60
|
this.buildStart = () => {
|
|
79
61
|
plugin.#captureBuildCount += 1
|
|
80
|
-
if (plugin.#captureBuildCount
|
|
81
|
-
throw new Error(
|
|
82
|
-
'StasisRollup: watch/rebuild is not supported for capture -- run a one-shot build (rebuilds would silently attest stale content)'
|
|
83
|
-
)
|
|
62
|
+
if (plugin.#captureBuildCount > 1) throw new Error(rebuildRefused('StasisRollup'))
|
|
84
63
|
}
|
|
85
|
-
|
|
86
|
-
// Capture epilogue: flag entries from the completed graph, then persist. resolveId's
|
|
87
|
-
// opts.isEntry is never trusted for attestation -- rollup defaults it to !importer, so a
|
|
88
|
-
// sibling's bare `this.resolve(id)` probe looks exactly like an entry and would widen the
|
|
89
|
-
// attested set of runnable roots. The graph's ModuleInfo.isEntry is authoritative (real
|
|
90
|
-
// `input` entries and emitFile'd chunks -- both genuinely run as roots; probes never join
|
|
91
|
-
// the graph).
|
|
92
64
|
this.buildEnd = function (error) {
|
|
93
65
|
return plugin.#buildEnd(this, error)
|
|
94
66
|
}
|
|
95
67
|
}
|
|
96
68
|
}
|
|
97
69
|
|
|
98
|
-
// Rollup asset plugins
|
|
99
|
-
// load hook -- there is no seam to hand them attested bytes (load hooks don't chain), so a
|
|
100
|
-
// resource under bundle=load can't be served faithfully and must fail loudly, not fall back to
|
|
101
|
-
// unattested disk bytes.
|
|
70
|
+
// Rollup asset plugins read their files from disk in their own load hook, with no seam for attested bytes.
|
|
102
71
|
#refuseResourceLoad(path) {
|
|
103
72
|
throw new Error(
|
|
104
73
|
`StasisRollup: bundle=load can't serve resource '${path}' -- rollup asset plugins read ` +
|
|
@@ -107,22 +76,8 @@ export class StasisRollup {
|
|
|
107
76
|
)
|
|
108
77
|
}
|
|
109
78
|
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
// attributed EXTERNAL passes through untouched -- exactly as under StasisEsbuild, whose
|
|
113
|
-
// equivalent assert sits in onLoad, which externals never reach.
|
|
114
|
-
#assertPersistableAttributes(attrs) {
|
|
115
|
-
for (const k of Object.keys(attrs)) {
|
|
116
|
-
assert.equal(k, 'type', `unsupported import attribute: ${k}`)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Throws when `id` is a '?'/'#'-suffixed variant of an on-disk path (rollup's plugin-param
|
|
121
|
-
// convention, e.g. './icon.svg?url'): the underlying bytes feed the build, but the suffixed key
|
|
122
|
-
// can't be persisted or replayed, so skipping it would silently leave a build input unattested
|
|
123
|
-
// (a frozen run would be blind to tampering). The esbuild plugin refuses its `suffix` analogue
|
|
124
|
-
// the same way. A fully virtual id (nothing on disk under the stripped path either) is a
|
|
125
|
-
// sibling plugin's own module and stays skippable, like \0-prefixed ids.
|
|
79
|
+
// A '?'/'#'-suffixed variant of an on-disk path ('./icon.svg?url') feeds the file's bytes to the build under a key
|
|
80
|
+
// that can't be persisted, so skipping it would leave a build input unattested. A wholly virtual id is skippable.
|
|
126
81
|
#refuseSuffixedOnDisk(id) {
|
|
127
82
|
const stripped = id.split(/[?#]/u, 1)[0]
|
|
128
83
|
if (stripped !== id && existsSync(stripped)) {
|
|
@@ -134,102 +89,53 @@ export class StasisRollup {
|
|
|
134
89
|
}
|
|
135
90
|
}
|
|
136
91
|
|
|
137
|
-
// True when `id` is an on-disk file; false defers a virtual id to its owning plugin. The
|
|
138
|
-
// dangerous middle case -- a suffixed variant of a real file -- refuses instead (see above).
|
|
139
|
-
#existsOrRefuseSuffixed(id) {
|
|
140
|
-
if (existsSync(id)) return true
|
|
141
|
-
this.#refuseSuffixedOnDisk(id)
|
|
142
|
-
return false
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
#classifyOrThrow(id) {
|
|
146
|
-
const kind = classifyExtension(id, this.#resources)
|
|
147
|
-
if (kind === 'unknown') {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`StasisRollup: unsupported extension for '${id}' -- add its extension or filename to the plugin's resources option or stop importing it`
|
|
150
|
-
)
|
|
151
|
-
}
|
|
152
|
-
return kind
|
|
153
|
-
}
|
|
154
|
-
|
|
155
92
|
#inRoot(path) {
|
|
156
|
-
|
|
157
|
-
this.#state.relative(path)
|
|
158
|
-
return true
|
|
159
|
-
} catch {
|
|
160
|
-
return false
|
|
161
|
-
}
|
|
93
|
+
return isPathWithin(this.#state.root, path)
|
|
162
94
|
}
|
|
163
95
|
|
|
164
96
|
async #resolveId(ctx, source, importer, opts) {
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
// tampered bundle can't redirect `fs` to an arbitrary file.
|
|
97
|
+
// Left to the user's external config. SECURITY: never consult getImport for a builtin, so a tampered bundle
|
|
98
|
+
// can't redirect `fs`.
|
|
168
99
|
if (isBuiltin(source)) return null
|
|
169
100
|
const { isEntry, attributes: attrs = {} } = opts
|
|
170
101
|
|
|
171
|
-
// Load mode
|
|
172
|
-
// may not be on disk. Return the original absolute path so output bytes match a capture build.
|
|
102
|
+
// Load mode resolves through the bundle's import map: the file may not be on disk.
|
|
173
103
|
if (this.#state.config.loadBundle) {
|
|
174
104
|
if (isEntry) {
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// resolver: against the importer when one exists (emitFile passes it), else cwd (like the
|
|
178
|
-
// run loader). No assertEntry here: a probe is indistinguishable from an entry at this
|
|
179
|
-
// point, so the load hook asserts from the graph's authoritative isEntry instead -- and a
|
|
180
|
-
// probe that never loads is never asserted.
|
|
105
|
+
// Rollup defaults isEntry to !importer, so this covers bare sibling probes too: the load hook asserts entries
|
|
106
|
+
// from the graph instead. Anchored as rollup's resolver does, else to cwd as the run loader is.
|
|
181
107
|
if (source.startsWith('file:')) return { id: fileURLToPath(source) }
|
|
182
|
-
const base = importer
|
|
183
|
-
? dirname(importer)
|
|
184
|
-
: process.cwd()
|
|
108
|
+
const base = isDiskId(importer) ? dirname(importer) : process.cwd()
|
|
185
109
|
return { id: resolvePath(base, source) }
|
|
186
110
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
// Forward import attributes (`with { type: 'json' }`) so plugin-capture round-trips to
|
|
193
|
-
// plugin-load, both keyed under `* (with: ...)`.
|
|
194
|
-
let url, format
|
|
195
|
-
try {
|
|
196
|
-
;({ url, format } = this.#state.getImport(parentURL, source, { importAttributes: attrs }))
|
|
197
|
-
} catch (err) {
|
|
198
|
-
// No attested edge -- at a successful capture that's an EXTERNAL (never recorded as an
|
|
199
|
-
// edge), so hand it back to rollup. File BYTES still fail closed at the load hook.
|
|
200
|
-
// A non-MODULE_NOT_FOUND error is a real fault -- rethrow.
|
|
201
|
-
if (err?.code === 'ERR_MODULE_NOT_FOUND') return null
|
|
202
|
-
throw err
|
|
203
|
-
}
|
|
111
|
+
if (!isDiskId(importer) || !this.#inRoot(importer)) return null
|
|
112
|
+
assertPersistableAttributes(attrs)
|
|
113
|
+
const attested = attestedImport(this.#state, pathToFileURL(importer).toString(), source, { importAttributes: attrs })
|
|
114
|
+
if (attested === undefined) return null
|
|
115
|
+
const { url, format } = attested
|
|
204
116
|
const path = fileURLToPath(url)
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
this.#refuseResourceLoad(path)
|
|
210
|
-
}
|
|
211
|
-
// KNOWN GAP: sibling resolution metadata (moduleSideEffects, syntheticNamedExports, meta)
|
|
212
|
-
// isn't persisted in the bundle, so load mode can't replay it -- tree-shaking of a module a
|
|
213
|
-
// sibling marked side-effect-free at capture may differ from the capture build's output.
|
|
117
|
+
// An out-of-scope resource comes from disk through the user's asset plugin, as at capture.
|
|
118
|
+
if (Bundle.isResourceFormat(format) && inScope(this.#state, url)) this.#refuseResourceLoad(path)
|
|
119
|
+
// KNOWN GAP: a sibling's resolution metadata (moduleSideEffects, syntheticNamedExports, meta) isn't in the
|
|
120
|
+
// bundle, so tree-shaking may differ from the capture build's.
|
|
214
121
|
return { id: path }
|
|
215
122
|
}
|
|
216
123
|
|
|
217
|
-
// Capture:
|
|
218
|
-
// re-entry), record the observed edge, and pass the resolution through unchanged.
|
|
124
|
+
// Capture: the remaining resolveId hooks and rollup's own algorithm, passed through with the edge recorded.
|
|
219
125
|
const resolution = await ctx.resolve(source, importer, { ...opts, skipSelf: true })
|
|
220
126
|
if (!resolution || resolution.external) return resolution
|
|
221
127
|
const { id } = resolution
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
128
|
+
if (!isDiskId(id)) return resolution
|
|
129
|
+
if (!existsSync(id)) {
|
|
130
|
+
this.#refuseSuffixedOnDisk(id)
|
|
131
|
+
return resolution
|
|
132
|
+
}
|
|
133
|
+
classifyOrThrow('StasisRollup', id, this.#resources)
|
|
226
134
|
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (!isEntry && importer && isAbsolute(importer) && !importer.startsWith('\0') && this.#inRoot(importer)) {
|
|
232
|
-
this.#assertPersistableAttributes(attrs)
|
|
135
|
+
// Code and resource targets alike, but never for an entry, flagged from the graph in buildEnd (opts.isEntry is
|
|
136
|
+
// a sibling's probe too), nor an out-of-root importer, which load mode defers too.
|
|
137
|
+
if (!isEntry && isDiskId(importer) && this.#inRoot(importer)) {
|
|
138
|
+
assertPersistableAttributes(attrs)
|
|
233
139
|
if (!this.#inRoot(id)) {
|
|
234
140
|
throw new Error(
|
|
235
141
|
`StasisRollup: '${source}' imported from ${importer} resolves outside the project root and can't be attested: ${id}`
|
|
@@ -241,30 +147,21 @@ export class StasisRollup {
|
|
|
241
147
|
}
|
|
242
148
|
|
|
243
149
|
async #load(ctx, id) {
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
// In scope = full (everything under state.root) or node_modules-only per scope; ids other
|
|
247
|
-
// plugins resolved land here too, so the gate can't rely on our own resolveId alone.
|
|
150
|
+
// getFile verifies the hash and throws on a file the bundle doesn't carry: no disk fallback. Ids other plugins
|
|
151
|
+
// resolved land here too.
|
|
248
152
|
if (this.#state.config.loadBundle) {
|
|
249
|
-
if (id
|
|
250
|
-
if (!this.#inRoot(id)) return null // out of the project root -- not ours
|
|
153
|
+
if (!isDiskId(id) || !this.#inRoot(id)) return null
|
|
251
154
|
const url = pathToFileURL(id).toString()
|
|
252
|
-
//
|
|
253
|
-
// a sibling's bare resolve() probe carries isEntry too but never loads, so only genuine
|
|
254
|
-
// roots reach this gate.
|
|
155
|
+
// The graph's isEntry: a sibling's resolve() probe carries isEntry too but never loads.
|
|
255
156
|
if (this.#state.config.full && ctx.getModuleInfo(id)?.isEntry) this.#state.assertEntry(url)
|
|
256
|
-
if (!this.#state
|
|
157
|
+
if (!inScope(this.#state, url)) return null
|
|
257
158
|
const { source, format } = this.#state.getFile(url)
|
|
258
159
|
if (Bundle.isResourceFormat(format)) this.#refuseResourceLoad(id)
|
|
259
|
-
// Code is stored as UTF-8 text, exactly what rollup's parser expects.
|
|
260
160
|
return source
|
|
261
161
|
}
|
|
262
162
|
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
// check: ENOENT defers -- unless the id is a suffixed variant of an on-disk file, which
|
|
266
|
-
// refuses -- and any other read error is real.
|
|
267
|
-
if (id.startsWith('\0') || !isAbsolute(id)) return null
|
|
163
|
+
// A sibling resolver may have minted ids our resolveId never saw. The read is the existence check.
|
|
164
|
+
if (!isDiskId(id)) return null
|
|
268
165
|
let source
|
|
269
166
|
try {
|
|
270
167
|
source = await realReadFile(id)
|
|
@@ -273,55 +170,34 @@ export class StasisRollup {
|
|
|
273
170
|
this.#refuseSuffixedOnDisk(id)
|
|
274
171
|
return null
|
|
275
172
|
}
|
|
276
|
-
const
|
|
173
|
+
const resource = classifyOrThrow('StasisRollup', id, this.#resources) === 'resource'
|
|
277
174
|
if (!this.#inRoot(id)) {
|
|
278
175
|
throw new Error(`StasisRollup: module is outside the project root and can't be attested: ${id}`)
|
|
279
176
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (kind === 'resource') {
|
|
283
|
-
// Mark as a resource (State picks 'resource' vs 'resource:base64' from bytes). Return null:
|
|
284
|
-
// loading it into the graph is the user's asset plugin's job.
|
|
285
|
-
if (!this.#seen.has(id)) {
|
|
286
|
-
this.#seen.add(id)
|
|
287
|
-
this.#state.addFile(url, { source, resource: true, reason: 'rollup' })
|
|
288
|
-
}
|
|
289
|
-
return null
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Code-classified files must be UTF-8; refuse non-UTF-8 rather than silently encode as a resource.
|
|
293
|
-
assert.ok(isUtf8(source), `StasisRollup: code-classified file has non-UTF-8 bytes: ${id}`)
|
|
294
|
-
|
|
177
|
+
if (!resource) assertCodeUtf8('StasisRollup', id, source)
|
|
295
178
|
if (!this.#seen.has(id)) {
|
|
296
179
|
this.#seen.add(id)
|
|
297
|
-
this.#state.addFile(
|
|
180
|
+
this.#state.addFile(pathToFileURL(id).toString(), { source, resource, reason: 'rollup' })
|
|
298
181
|
}
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// still run after, symmetrically with load mode).
|
|
302
|
-
return source.toString('utf8')
|
|
182
|
+
// A resource is the user's asset plugin's to load; code is served as recorded (transform hooks still run after).
|
|
183
|
+
return resource ? null : source.toString('utf8')
|
|
303
184
|
}
|
|
304
185
|
|
|
305
|
-
//
|
|
306
|
-
//
|
|
307
|
-
// the user's good file). buildEnd, not closeBundle: the capture is complete once the graph is,
|
|
308
|
-
// and closeBundle only fires when the caller remembers bundle.close().
|
|
186
|
+
// Flags the graph's real entries (resolveId's opts.isEntry would take a sibling's bare probe for one), then writes
|
|
187
|
+
// a standalone or sidecar State on a clean build. buildEnd, not closeBundle, which only bundle.close() fires.
|
|
309
188
|
async #buildEnd(ctx, error) {
|
|
310
189
|
if (error) return
|
|
311
190
|
const entryIds = [...ctx.getModuleIds()].filter((id) => {
|
|
312
|
-
if (!this.#seen.has(id)) return false
|
|
191
|
+
if (!this.#seen.has(id)) return false
|
|
313
192
|
const info = ctx.getModuleInfo(id)
|
|
314
193
|
return Boolean(info?.isEntry) && !info.isExternal
|
|
315
194
|
})
|
|
316
|
-
//
|
|
317
|
-
// RESOURCE entry fails loudly here ('a resource can't be an entry'), matching StasisEsbuild,
|
|
318
|
-
// instead of silently writing an artifact whose entries list omits the build's real entry.
|
|
195
|
+
// addFile re-verifies the bytes, and refuses a RESOURCE entry, as StasisEsbuild does.
|
|
319
196
|
const sources = await Promise.all(entryIds.map((id) => realReadFile(id)))
|
|
320
197
|
for (const [i, id] of entryIds.entries()) {
|
|
321
198
|
const resource = classifyExtension(id, this.#resources) === 'resource'
|
|
322
199
|
this.#state.addFile(pathToFileURL(id).toString(), { source: sources[i], isEntry: true, resource, reason: 'rollup' })
|
|
323
200
|
}
|
|
324
|
-
// The preload writes itself (hooks.js); standalone/sidecar States are written here.
|
|
325
201
|
if (this.#state !== State.preload) this.#state.write()
|
|
326
202
|
}
|
|
327
203
|
}
|