@exodus/stasis-plugins 1.0.0-beta.1 → 1.0.0-beta.10
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 +1 -1
- package/package.json +8 -6
- package/src/esbuild.js +208 -118
- package/src/metro-transformer.js +32 -71
- package/src/metro.js +343 -175
- package/src/plugins.js +51 -86
- package/src/rollup.js +327 -0
- package/src/webpack.js +84 -290
package/src/metro-transformer.js
CHANGED
|
@@ -5,55 +5,19 @@ import { pathToFileURL } from 'node:url'
|
|
|
5
5
|
|
|
6
6
|
import { State } from '@exodus/stasis-core/state'
|
|
7
7
|
|
|
8
|
-
// Companion to the StasisMetro serializer
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// HOW IT WIRES IN (metro.config.js, load mode only):
|
|
18
|
-
// module.exports = {
|
|
19
|
-
// transformer: {
|
|
20
|
-
// transformerPath: require.resolve('@exodus/stasis/metro-transformer'),
|
|
21
|
-
// },
|
|
22
|
-
// }
|
|
23
|
-
// // run with EXODUS_STASIS_BUNDLE=load (+ EXODUS_STASIS_BUNDLE_FILE / LOCK / SCOPE),
|
|
24
|
-
// // OR a stasis.config.json with "bundle":"load". Do NOT also wire the StasisMetro
|
|
25
|
-
// // serializer plugin in load mode -- capture and load are separate wirings.
|
|
26
|
-
//
|
|
27
|
-
// Metro calls `transformerPath`'s `transform(config, projectRoot, filename, data,
|
|
28
|
-
// options)` in each worker with `data` = the file's on-disk bytes (a Buffer) and
|
|
29
|
-
// `filename` project-relative. We wrap the user's real transformer (default
|
|
30
|
-
// `metro-transform-worker`, override via EXODUS_STASIS_METRO_BASE_TRANSFORMER) and,
|
|
31
|
-
// in load mode, replace `data` with the bundle's attested bytes before delegating --
|
|
32
|
-
// so the transform consumes bundle bytes, hash-verified, fail-closed.
|
|
33
|
-
//
|
|
34
|
-
// WHAT THIS DOES AND DOESN'T GUARANTEE:
|
|
35
|
-
// - The bytes that get TRANSFORMED are the bundle's, verified by State.getFile
|
|
36
|
-
// (against the lockfile when one is loaded; against the bundle's own bytes under
|
|
37
|
-
// lock=none). A tampered on-disk source is therefore ignored for the build output
|
|
38
|
-
// and, under a lockfile, rejected outright. An in-scope file the bundle doesn't
|
|
39
|
-
// carry throws -- never a silent disk fallback. Out-of-scope files pass their disk
|
|
40
|
-
// bytes through untouched (mirrors webpack/esbuild load scope handling).
|
|
41
|
-
// - KNOWN LIMITATION: this does NOT let Metro build with the sources fully absent
|
|
42
|
-
// from disk. Metro reads each file from disk in the worker (and hashes it in the
|
|
43
|
-
// main process for its transform cache) BEFORE this transformer runs, and resolves
|
|
44
|
-
// package.json via its own fs in the main process. Serving those reads from the
|
|
45
|
-
// bundle would need fs interception across the main + worker processes (stasis
|
|
46
|
-
// --mock territory), which is larger than this seam. So today's guarantee is
|
|
47
|
-
// "build the bundle's attested bytes, fail closed on disk drift," not "build from
|
|
48
|
-
// a bundle with no source tree." This mirrors the documented node_modules-crossing
|
|
49
|
-
// gap in the webpack load path.
|
|
8
|
+
// Companion to the StasisMetro serializer: the LOAD half of stasis for Metro. Load only READS an
|
|
9
|
+
// immutable bundle and every worker reads the same bytes (no IPC), so a per-worker transformer is
|
|
10
|
+
// the natural seam. Wire it permanently in metro.config.js (transformerPath); outside load mode it
|
|
11
|
+
// is a transparent pass-through, and under load the serializer is too -- the mode picks the active
|
|
12
|
+
// half. In load mode, wrap the user's real transformer and replace Metro's on-disk `data` with the
|
|
13
|
+
// bundle's hash-verified bytes (fail-closed; out-of-scope files pass their disk bytes through).
|
|
14
|
+
// KNOWN LIMITATION: does NOT build with sources absent from disk -- Metro reads + hashes each file
|
|
15
|
+
// before this runs, so the guarantee is "build attested bytes, fail closed on disk drift."
|
|
50
16
|
|
|
51
17
|
const require = createRequire(import.meta.url)
|
|
52
18
|
|
|
53
|
-
// The real transformer we wrap
|
|
54
|
-
//
|
|
55
|
-
// in front of it. Resolved + required lazily (per worker process) so a non-load build
|
|
56
|
-
// pays nothing and we don't require Metro internals at import time.
|
|
19
|
+
// The real transformer we wrap (default matches Metro's; override for a custom TS/Babel preset).
|
|
20
|
+
// Required lazily per worker so a non-load build pays nothing.
|
|
57
21
|
const BASE_TRANSFORMER = process.env.EXODUS_STASIS_METRO_BASE_TRANSFORMER || 'metro-transform-worker'
|
|
58
22
|
|
|
59
23
|
let base
|
|
@@ -62,25 +26,30 @@ function getBase() {
|
|
|
62
26
|
return base
|
|
63
27
|
}
|
|
64
28
|
|
|
65
|
-
// Per-worker load State, built once
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// pass-through, so wiring it permanently in metro.config.js is safe.
|
|
29
|
+
// Per-worker load State, built once from the usual env + stasis.config.json. Non-load mode -> null
|
|
30
|
+
// -> transparent pass-through, so wiring it permanently is safe. A construction failure is cached
|
|
31
|
+
// and rethrown on every call -- never throw-once-then-silently-pass-through.
|
|
69
32
|
let stateInited = false
|
|
70
33
|
let loadState = null
|
|
34
|
+
let stateError = null
|
|
71
35
|
function getLoadState() {
|
|
72
36
|
if (!stateInited) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
37
|
+
try {
|
|
38
|
+
const state = new State(process.cwd())
|
|
39
|
+
loadState = state.config.loadBundle ? state : null
|
|
40
|
+
} catch (err) {
|
|
41
|
+
stateError = err
|
|
42
|
+
throw err
|
|
43
|
+
} finally {
|
|
44
|
+
stateInited = true
|
|
45
|
+
}
|
|
76
46
|
}
|
|
47
|
+
if (stateError) throw stateError
|
|
77
48
|
return loadState
|
|
78
49
|
}
|
|
79
50
|
|
|
80
|
-
// True iff `absolute` is
|
|
81
|
-
//
|
|
82
|
-
// out-of-scope (system files, scratch dirs) and defer to disk. Mirrors the webpack
|
|
83
|
-
// plugin's load-mode inScope.
|
|
51
|
+
// True iff `absolute` is in scope for load mode. state.relative throws outside the project root
|
|
52
|
+
// (out-of-scope: system files, scratch) -> defer to disk. Mirrors the webpack plugin's inScope.
|
|
84
53
|
function inScope(state, absolute) {
|
|
85
54
|
try {
|
|
86
55
|
state.relative(absolute)
|
|
@@ -94,14 +63,11 @@ function inScope(state, absolute) {
|
|
|
94
63
|
export function transform(config, projectRoot, filename, data, options) {
|
|
95
64
|
const state = getLoadState()
|
|
96
65
|
if (state) {
|
|
97
|
-
// `filename` is project-relative in Metro's
|
|
98
|
-
// projectRoot (resolvePath ignores projectRoot when filename is already absolute).
|
|
66
|
+
// `filename` is project-relative in Metro's contract; resolve against projectRoot.
|
|
99
67
|
const absolute = resolvePath(projectRoot, filename)
|
|
100
68
|
if (inScope(state, absolute)) {
|
|
101
|
-
// getFile re-verifies the hash
|
|
102
|
-
//
|
|
103
|
-
// surfaces here as a hard build error, not a silent disk fallback. Code sources
|
|
104
|
-
// come back as strings; resources as Buffers (already decoded from base64).
|
|
69
|
+
// getFile re-verifies the hash and throws on a file the bundle doesn't carry (fail-closed,
|
|
70
|
+
// no disk fallback). Code sources come back as strings; resources as Buffers.
|
|
105
71
|
const { source } = state.getFile(pathToFileURL(absolute).toString())
|
|
106
72
|
data = Buffer.isBuffer(source) ? source : Buffer.from(source)
|
|
107
73
|
}
|
|
@@ -110,14 +76,9 @@ export function transform(config, projectRoot, filename, data, options) {
|
|
|
110
76
|
}
|
|
111
77
|
|
|
112
78
|
export function getCacheKey(config) {
|
|
113
|
-
// Fold the
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
// on-disk bytes into its cache key BEFORE we substitute, so when disk == bundle the
|
|
117
|
-
// content hash alone wouldn't tell a load result apart from a plain build), so their
|
|
118
|
-
// cached results must not cross-pollinate. Derive the marker from the resolved load
|
|
119
|
-
// State -- the SAME source of truth as transform() -- not the env var, so load activated
|
|
120
|
-
// via stasis.config.json is keyed correctly too; the bundle path keys it to the bundle.
|
|
79
|
+
// Fold in the base transformer's cache key, then add a stasis-load discriminator so load results
|
|
80
|
+
// (which can differ from a disk build even when disk == bundle) don't cross-pollinate the cache.
|
|
81
|
+
// Derive the marker from the resolved load State (not the env) so config.json activation keys too.
|
|
121
82
|
const baseKey = typeof getBase().getCacheKey === 'function' ? getBase().getCacheKey(config) : ''
|
|
122
83
|
const state = getLoadState()
|
|
123
84
|
const marker = state ? `load:${state.config.bundleFile || 'default'}` : 'off'
|