@exodus/stasis-plugins 1.0.0-beta.2 → 1.0.0-beta.3
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 +2 -2
- package/src/esbuild.js +62 -112
- package/src/metro-transformer.js +32 -72
- package/src/metro.js +315 -253
- package/src/plugins.js +49 -87
- package/src/webpack.js +84 -294
package/src/plugins.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
// Plugin <-> preload coordination (resolvePluginState): decides which State a bundler
|
|
2
|
-
//
|
|
3
|
-
// helpers (pathExt / parseResourcesOption / classifyExtension) live in util.js -- they're
|
|
4
|
-
// pure and shared with State's `--fs` capture, so both paths classify against one source
|
|
5
|
-
// of truth. Kept out of state.js because the coordination only consults State.preload +
|
|
6
|
-
// Config helpers, never State internals.
|
|
1
|
+
// Plugin <-> preload coordination (resolvePluginState): decides which State a bundler plugin
|
|
2
|
+
// drives, given its options and the ambient preload.
|
|
7
3
|
|
|
8
4
|
import assert from 'node:assert/strict'
|
|
9
5
|
|
|
@@ -12,58 +8,42 @@ import { ensureStateForLoader, isLoaderInstalled } from '@exodus/stasis-core/hoo
|
|
|
12
8
|
import { State } from '@exodus/stasis-core/state'
|
|
13
9
|
import { extSetsEqual, parseResourcesOption } from '@exodus/stasis-core/util'
|
|
14
10
|
|
|
15
|
-
// Decides which State a bundler plugin
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// off per-plugin (via bundleFile, a sidecar). A process that genuinely needs a non-default
|
|
25
|
-
// lockfile location sets it process-wide (Config / EXODUS_STASIS_LOCK_FILE), not per-plugin.
|
|
26
|
-
//
|
|
27
|
-
// Rules:
|
|
28
|
-
// 1. Plugin asking for any lockfile mode other than 'none' / 'ignore' requires a preload.
|
|
29
|
-
// Without one it's a hard throw -- the lockfile would otherwise only cover the bundled
|
|
30
|
-
// app and silently miss every dependency the bundler itself pulled in.
|
|
31
|
-
// 2. With a preload, the plugin's lock mode must agree with the preload's.
|
|
32
|
-
// 3. Lockfile is unified: when plugin and preload align we reuse the preload State so a
|
|
33
|
-
// single lockfile is written.
|
|
34
|
-
// 4. Bundle may split: with a preload that already has bundle on, the plugin cannot disable
|
|
35
|
-
// bundle and the bundle mode must match. The plugin's bundleFile, however, may differ
|
|
36
|
-
// -- in which case we construct a sidecar State sharing the preload's lockfile data.
|
|
11
|
+
// Decides which State a bundler plugin runs against, given its options and the ambient preload.
|
|
12
|
+
// Returns { state: null, isNoop: true } (inert -- register no hooks) or { state, isNoop: false }.
|
|
13
|
+
// The lockfile is always unified (plugins can't set lockFile); only the BUNDLE may split per-plugin
|
|
14
|
+
// (bundleFile -> sidecar). Rules, referenced by number in the code below:
|
|
15
|
+
// 0. No preload, not under `stasis run`, no lock/bundle option -> inert (safe on a plain build).
|
|
16
|
+
// 1. A writing/verifying lock mode without a preload throws (it would miss the bundler's own deps).
|
|
17
|
+
// 2. With a preload, the plugin's lock mode must agree with it.
|
|
18
|
+
// 3. When plugin and preload align, reuse the preload State (single lockfile).
|
|
19
|
+
// 4. Preload with bundle on: plugin can't disable it and the mode must match; bundleFile may differ.
|
|
37
20
|
// 5. Same bundleFile (or unspecified) reuses the preload State.
|
|
38
|
-
// 6. Different bundleFile creates a sidecar.
|
|
39
|
-
// 7.
|
|
40
|
-
// plugins be env-controlled off without throwing on the lock=none/bundle=none invariant).
|
|
21
|
+
// 6. Different bundleFile creates a sidecar sharing the preload's lockfile data.
|
|
22
|
+
// 7. lock='none' AND bundle='none' with no preload is a no-op (env-controlled-off framework plugins).
|
|
41
23
|
export function resolvePluginState(label, options, cwd) {
|
|
42
24
|
validatePluginOptions(label, options)
|
|
43
|
-
const { scope, lock, bundle, bundleFile, resourcesBundleFile, debug, childProcess, resources } = options
|
|
44
|
-
// Same-instance fast path:
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// during config-parse before user code executes). Force-init now so the
|
|
48
|
-
// plugin lands in the ambient branch and inherits the loader's Config.
|
|
49
|
-
// We deliberately do NOT do this when the loader is a SEPARATE module
|
|
50
|
-
// instance (different stasis-core copy in the plugin's node_modules vs
|
|
51
|
-
// the binary's): force-init there would create a phantom State.preload
|
|
52
|
-
// with its own beforeExit/exit save handlers that race the real loader's
|
|
53
|
-
// lockfile write. Cross-instance detection lives below via env vars.
|
|
25
|
+
const { scope, lock, bundle, bundleFile, resourcesBundleFile, debug, childProcess, packageJSON, resources } = options
|
|
26
|
+
// Same-instance fast path: loader installed but lazy initState hasn't fired (webpack builds plugins
|
|
27
|
+
// before user code). Force-init so the plugin lands in the ambient branch. NOT for a separate
|
|
28
|
+
// stasis-core copy -- that would mint a phantom State.preload racing the real loader's write.
|
|
54
29
|
if (!State.preload && isLoaderInstalled()) ensureStateForLoader()
|
|
55
30
|
const ambient = State.preload
|
|
56
31
|
|
|
57
32
|
if (!ambient) {
|
|
33
|
+
// "Under stasis run" detection: the bin script always exports EXODUS_STASIS_LOCK, so it's a
|
|
34
|
+
// process-wide signal regardless of which stasis-core copy hosts this code.
|
|
35
|
+
const underStasisRun = process.env.EXODUS_STASIS_LOCK !== undefined
|
|
36
|
+
|
|
37
|
+
// Rule 0: no preload, not under `stasis run`, no lock/bundle option -> do nothing, so a bare
|
|
38
|
+
// plugin sits inertly in a plain build. Capture is governed elsewhere (env or explicit options).
|
|
39
|
+
if (!underStasisRun && lock === undefined && bundle === undefined) {
|
|
40
|
+
return { state: null, isNoop: true }
|
|
41
|
+
}
|
|
42
|
+
|
|
58
43
|
if (lock === 'none' && bundle === 'none') return { state: null, isNoop: true } // Rule 7
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
// generic "stasis needs a lockfile or a bundle" RangeError. Catch the partial
|
|
63
|
-
// case explicitly. Only fires when the OTHER field is undefined: an active
|
|
64
|
-
// bundle mode like `bundle:'load'` / `'frozen'` / `'add'` paired with
|
|
65
|
-
// `lock:'none'` is a legitimate "bundle as trust root" config that Config
|
|
66
|
-
// accepts (see config.js's bundle=load / bundle=frozen rules).
|
|
44
|
+
// A partial opt-out (one field 'none', the other omitted) would otherwise hit a misleading
|
|
45
|
+
// Rule 1 throw or Config's generic error; catch it explicitly. An active bundle mode paired
|
|
46
|
+
// with lock='none' is a legitimate "bundle as trust root" config, so only fire when the other is undefined.
|
|
67
47
|
if ((lock === 'none' && bundle === undefined) || (bundle === 'none' && lock === undefined)) {
|
|
68
48
|
throw new Error(
|
|
69
49
|
`${label}: to opt out of stasis without a preload, pass BOTH lock='none' and bundle='none' ` +
|
|
@@ -71,18 +51,9 @@ export function resolvePluginState(label, options, cwd) {
|
|
|
71
51
|
`bundle=${bundle === undefined ? 'default' : `'${bundle}'`})`
|
|
72
52
|
)
|
|
73
53
|
}
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
// process-wide signal regardless of which stasis-core copy is hosting
|
|
78
|
-
// this code. When set, the runtime's loader is providing full lockfile
|
|
79
|
-
// coverage for the process; the plugin's lockfile (at its own path,
|
|
80
|
-
// typically a sidecar like `sources.stasis.lock.json`) is a sibling
|
|
81
|
-
// artifact covering bundler-side resolutions, and the "lock=add needs
|
|
82
|
-
// a preload" rationale doesn't apply. Skip that rule under stasis run;
|
|
83
|
-
// Config below will pick up env-var defaults (lock/scope/bundle) for
|
|
84
|
-
// the standalone plugin State.
|
|
85
|
-
const underStasisRun = process.env.EXODUS_STASIS_LOCK !== undefined
|
|
54
|
+
// A writing/verifying lock (add|replace|frozen) still needs a preload unless under stasis run
|
|
55
|
+
// (whose loader gives full coverage). `lock` may be undefined here if only a writing `bundle`
|
|
56
|
+
// was passed, so the throw below distinguishes a defaulted mode from an explicit one.
|
|
86
57
|
const effectiveLock = lock ?? DEFAULT_LOCK
|
|
87
58
|
if (!underStasisRun && effectiveLock !== 'none' && effectiveLock !== 'ignore') {
|
|
88
59
|
const phrasing = lock === undefined
|
|
@@ -96,16 +67,12 @@ export function resolvePluginState(label, options, cwd) {
|
|
|
96
67
|
return { state: new State(cwd, options), isNoop: false }
|
|
97
68
|
}
|
|
98
69
|
|
|
99
|
-
// Preload
|
|
100
|
-
// necessarily the ambient's -- the plugin shares it (reuse or sidecar below).
|
|
101
|
-
// Enforce coordination on the remaining fields before deciding which.
|
|
70
|
+
// Preload active: the lockfile is necessarily the ambient's. Enforce field coordination, then reuse or sidecar.
|
|
102
71
|
const pc = ambient.config
|
|
103
72
|
if (lock !== undefined && lock !== pc.lock) {
|
|
104
73
|
throw new Error(`${label}: lock='${lock}' conflicts with active preload lock='${pc.lock}'`)
|
|
105
74
|
}
|
|
106
|
-
// scope/debug/childProcess must agree with the preload
|
|
107
|
-
// adopts preload's config wholesale, and sidecar copies it field by field. Catch a
|
|
108
|
-
// disagreement here rather than silently overriding the user's value.
|
|
75
|
+
// scope/debug/childProcess must agree with the preload (reuse adopts it, sidecar copies it).
|
|
109
76
|
if (scope !== undefined && scope !== pc.scope) {
|
|
110
77
|
throw new Error(`${label}: scope='${scope}' conflicts with active preload scope='${pc.scope}'`)
|
|
111
78
|
}
|
|
@@ -115,10 +82,11 @@ export function resolvePluginState(label, options, cwd) {
|
|
|
115
82
|
if (childProcess !== undefined && childProcess !== pc.childProcess) {
|
|
116
83
|
throw new Error(`${label}: childProcess=${childProcess} conflicts with active preload childProcess=${pc.childProcess}`)
|
|
117
84
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
85
|
+
if (packageJSON !== undefined && packageJSON !== pc.packageJSON) {
|
|
86
|
+
throw new Error(`${label}: packageJSON=${packageJSON} conflicts with active preload packageJSON=${pc.packageJSON}`)
|
|
87
|
+
}
|
|
88
|
+
// resources is process-wide, so a plugin's allowlist must agree with the preload's -- compare
|
|
89
|
+
// parsed sets (order/dupes/case-insensitive) to prevent one plugin silently widening the asset set.
|
|
122
90
|
if (resources !== undefined) {
|
|
123
91
|
const parsed = parseResourcesOption(label, resources)
|
|
124
92
|
if (!extSetsEqual(parsed, pc.resources)) {
|
|
@@ -128,10 +96,8 @@ export function resolvePluginState(label, options, cwd) {
|
|
|
128
96
|
}
|
|
129
97
|
}
|
|
130
98
|
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
// plugin's modules, e.g. webpack+babel reading app source through --fs) from genuine run
|
|
134
|
-
// dependencies recorded before any plugin existed. See State#bundleReason.
|
|
99
|
+
// Mark the plugin attached so the preload's bundle `reason` map can distinguish files merely
|
|
100
|
+
// fs-READ during the build from genuine run dependencies recorded before any plugin existed.
|
|
135
101
|
ambient.markPluginAttached()
|
|
136
102
|
|
|
137
103
|
if (pc.bundle) {
|
|
@@ -151,8 +117,7 @@ export function resolvePluginState(label, options, cwd) {
|
|
|
151
117
|
assertOptionsMatchConfig(pc, options)
|
|
152
118
|
return { state: ambient, isNoop: false }
|
|
153
119
|
}
|
|
154
|
-
// Rule 6: different path -- sidecar inherits preload's modes, overrides bundleFile
|
|
155
|
-
// (and resourcesBundleFile if the plugin provided one for a split layout).
|
|
120
|
+
// Rule 6: different path -- sidecar inherits preload's modes, overrides bundleFile.
|
|
156
121
|
const sidecar = new State(cwd, {
|
|
157
122
|
parent: ambient,
|
|
158
123
|
scope: pc.scope,
|
|
@@ -162,8 +127,9 @@ export function resolvePluginState(label, options, cwd) {
|
|
|
162
127
|
resourcesBundleFile,
|
|
163
128
|
debug: pc.debug,
|
|
164
129
|
childProcess: pc.childProcess,
|
|
130
|
+
packageJSON: pc.packageJSON,
|
|
165
131
|
resources: [...pc.resources],
|
|
166
|
-
// carried over: sidecars skip stasis.config.json discovery
|
|
132
|
+
// carried over: sidecars skip stasis.config.json discovery
|
|
167
133
|
brotliQuality: pc.brotliQuality,
|
|
168
134
|
})
|
|
169
135
|
return { state: sidecar, isNoop: false }
|
|
@@ -171,14 +137,9 @@ export function resolvePluginState(label, options, cwd) {
|
|
|
171
137
|
|
|
172
138
|
// Preload exists but has no bundle.
|
|
173
139
|
if (bundle === undefined || bundle === 'none' || bundle === 'ignore') {
|
|
174
|
-
// Plugin
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
// cross-check rather than letting assertOptionsMatchConfig flag the difference.
|
|
178
|
-
// Defense-in-depth: the strip is only safe when the preload's bundleMode is
|
|
179
|
-
// actually in the no-bundle equivalence class. If a future bundleMode reads as
|
|
180
|
-
// `!pc.bundle` but means something else, the strip would silently mask a real
|
|
181
|
-
// disagreement -- the assert pins the contract to today's two values.
|
|
140
|
+
// Plugin wants no bundle either: reuse preload for lockfile coverage. bundle='ignore' vs the
|
|
141
|
+
// preload's 'none' both mean "no plugin bundle", so strip the bundle field from the cross-check;
|
|
142
|
+
// the assert pins that strip to today's no-bundle values so a future mode can't silently mask a disagreement.
|
|
182
143
|
assert.ok(pc.bundleMode === 'none' || pc.bundleMode === 'ignore',
|
|
183
144
|
`unexpected preload bundleMode='${pc.bundleMode}' in the no-bundle branch`)
|
|
184
145
|
const { bundle: _b, ...rest } = options
|
|
@@ -200,6 +161,7 @@ export function resolvePluginState(label, options, cwd) {
|
|
|
200
161
|
resourcesBundleFile,
|
|
201
162
|
debug: pc.debug,
|
|
202
163
|
childProcess: pc.childProcess,
|
|
164
|
+
packageJSON: pc.packageJSON,
|
|
203
165
|
resources: [...pc.resources],
|
|
204
166
|
brotliQuality: pc.brotliQuality, // carried over, like the Rule 6 sidecar above
|
|
205
167
|
})
|