browser-metro 1.0.5

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.
@@ -0,0 +1,231 @@
1
+ import { buildBundlePreamble } from "./utils.js";
2
+ /**
3
+ * HMR-capable bundle runtime template.
4
+ *
5
+ * Called at emit time as an IIFE with:
6
+ * modules - the module map object
7
+ * reverseDeps - JSON reverse dependency map
8
+ * entryId - entry file path string
9
+ * reactRefreshEnabled - boolean
10
+ */
11
+ export const HMR_RUNTIME_TEMPLATE = `(function(modules, reverseDeps, entryId, reactRefreshEnabled) {
12
+ var cache = {};
13
+ var hotState = {};
14
+
15
+ function initHot(id) {
16
+ if (hotState[id]) return hotState[id];
17
+ var state = {
18
+ acceptCallbacks: [],
19
+ disposeCallbacks: [],
20
+ data: {},
21
+ declined: false,
22
+ accepted: false
23
+ };
24
+ hotState[id] = state;
25
+ return state;
26
+ }
27
+
28
+ function createHotAPI(id) {
29
+ var state = initHot(id);
30
+ return {
31
+ accept: function(cb) {
32
+ state.accepted = true;
33
+ if (cb) state.acceptCallbacks.push(cb);
34
+ },
35
+ dispose: function(cb) {
36
+ if (cb) state.disposeCallbacks.push(cb);
37
+ },
38
+ decline: function() {
39
+ state.declined = true;
40
+ },
41
+ get data() {
42
+ return state.data;
43
+ }
44
+ };
45
+ }
46
+
47
+ function require(id) {
48
+ if (cache[id]) return cache[id].exports;
49
+ if (!modules[id]) throw new Error('Module not found: ' + id);
50
+ var module = cache[id] = { exports: {}, hot: createHotAPI(id) };
51
+ modules[id].call(module.exports, module, module.exports, require);
52
+ return module.exports;
53
+ }
54
+
55
+ // Walk reverse deps to find nearest module.hot.accept() boundary
56
+ function findAcceptBoundary(changedId) {
57
+ var visited = {};
58
+ var queue = [changedId];
59
+ var boundaries = [];
60
+
61
+ while (queue.length > 0) {
62
+ var current = queue.shift();
63
+ if (visited[current]) continue;
64
+ visited[current] = true;
65
+
66
+ var state = hotState[current];
67
+ if (state && state.declined) {
68
+ return null;
69
+ }
70
+ if (state && state.accepted) {
71
+ boundaries.push(current);
72
+ continue;
73
+ }
74
+ // Walk upward through reverse deps
75
+ var parents = reverseDeps[current];
76
+ if (!parents || parents.length === 0) {
77
+ return null;
78
+ }
79
+ for (var i = 0; i < parents.length; i++) {
80
+ queue.push(parents[i]);
81
+ }
82
+ }
83
+
84
+ return boundaries.length > 0 ? boundaries : null;
85
+ }
86
+
87
+ function applyUpdate(updatedModules, removedModules, newReverseDeps) {
88
+ // Merge updated reverse deps so boundary walk sees new modules
89
+ if (newReverseDeps) {
90
+ for (var key in newReverseDeps) {
91
+ reverseDeps[key] = newReverseDeps[key];
92
+ }
93
+ }
94
+
95
+ // Phase 1: Check boundaries FIRST, before any state mutation.
96
+ // The dispose phase used to reset accepted=false here, which wiped
97
+ // out the accept registrations before the boundary walk could see them.
98
+ var needsReload = false;
99
+ var modulesToReExecute = new Set();
100
+
101
+ for (var uid in updatedModules) {
102
+ var boundaries = findAcceptBoundary(uid);
103
+ if (!boundaries) {
104
+ needsReload = true;
105
+ break;
106
+ }
107
+ for (var b = 0; b < boundaries.length; b++) {
108
+ modulesToReExecute.add(boundaries[b]);
109
+ }
110
+ modulesToReExecute.add(uid);
111
+ }
112
+
113
+ if (needsReload) {
114
+ console.warn('[HMR] No accept boundary found, requesting full reload');
115
+ window.parent.postMessage({ type: 'hmr-full-reload' }, '*');
116
+ return;
117
+ }
118
+
119
+ // Phase 2: Run dispose callbacks and reset state for modules about to be re-executed
120
+ modulesToReExecute.forEach(function(id) {
121
+ var state = hotState[id];
122
+ if (state) {
123
+ var newData = {};
124
+ for (var i = 0; i < state.disposeCallbacks.length; i++) {
125
+ state.disposeCallbacks[i](newData);
126
+ }
127
+ state.data = newData;
128
+ state.disposeCallbacks = [];
129
+ state.acceptCallbacks = [];
130
+ state.accepted = false;
131
+ }
132
+ });
133
+
134
+ // Phase 3: Update module factories
135
+ for (var mid in updatedModules) {
136
+ modules[mid] = new Function('module', 'exports', 'require', updatedModules[mid]);
137
+ }
138
+
139
+ // Phase 4: Remove deleted modules
140
+ for (var r = 0; r < removedModules.length; r++) {
141
+ var rmId = removedModules[r];
142
+ delete modules[rmId];
143
+ delete cache[rmId];
144
+ delete hotState[rmId];
145
+ }
146
+
147
+ // Phase 5: Re-execute modules. Clear ALL caches first so that nested
148
+ // require() calls during re-execution pick up the new factories rather
149
+ // than stale cached exports (e.g. entry requiring a dependency that
150
+ // hasn't been re-executed yet in this loop).
151
+ modulesToReExecute.forEach(function(execId) {
152
+ delete cache[execId];
153
+ });
154
+ modulesToReExecute.forEach(function(execId) {
155
+ try {
156
+ require(execId);
157
+ } catch(err) {
158
+ console.error('[HMR] Error re-executing module ' + execId + ':', err);
159
+ window.parent.postMessage({ type: 'hmr-full-reload' }, '*');
160
+ }
161
+ });
162
+
163
+ // Phase 6: React Refresh -- tell React to re-render with new component definitions
164
+ if (reactRefreshEnabled && window.__REACT_REFRESH_RUNTIME__) {
165
+ try {
166
+ window.__REACT_REFRESH_RUNTIME__.performReactRefresh();
167
+ } catch(err) {
168
+ console.error('[HMR] React Refresh error:', err);
169
+ }
170
+ }
171
+
172
+ console.log('[HMR] Updated ' + Object.keys(updatedModules).length + ' module(s)');
173
+ }
174
+
175
+ // Expose HMR API globally
176
+ window.__BUNDLER_HMR__ = {
177
+ applyUpdate: applyUpdate,
178
+ modules: modules,
179
+ cache: cache,
180
+ hotState: hotState
181
+ };
182
+
183
+ // Listen for HMR updates from parent window
184
+ window.addEventListener('message', function(e) {
185
+ var data = e.data;
186
+ if (data && data.type === 'hmr-update') {
187
+ applyUpdate(data.updatedModules || {}, data.removedModules || [], data.reverseDepsMap);
188
+ }
189
+ });
190
+
191
+ // Initialize React Refresh runtime BEFORE executing the entry module,
192
+ // so $RefreshReg$ and $RefreshSig$ are available when component modules load.
193
+ if (reactRefreshEnabled) {
194
+ try {
195
+ var RefreshRuntime = require('react-refresh/runtime');
196
+ RefreshRuntime.injectIntoGlobalHook(window);
197
+ window.__REACT_REFRESH_RUNTIME__ = RefreshRuntime;
198
+ window.$RefreshReg$ = function() {};
199
+ window.$RefreshSig$ = function() { return function(type) { return type; }; };
200
+ } catch(e) {
201
+ console.warn('[HMR] React Refresh runtime not available:', e.message || e);
202
+ }
203
+ }
204
+
205
+ // Execute entry module
206
+ require(entryId);
207
+ })`;
208
+ /**
209
+ * Emit an HMR-capable bundle string.
210
+ */
211
+ export function emitHmrBundle(moduleMap, entryFile, reverseDepsMap, reactRefresh, env, routerShim) {
212
+ const moduleEntries = Object.keys(moduleMap)
213
+ .map((id) => {
214
+ return (JSON.stringify(id) +
215
+ ": function(module, exports, require) {\n" +
216
+ moduleMap[id] +
217
+ "\n}");
218
+ })
219
+ .join(",\n\n");
220
+ return (buildBundlePreamble(env, routerShim) +
221
+ HMR_RUNTIME_TEMPLATE +
222
+ "({\n" +
223
+ moduleEntries +
224
+ "\n}, " +
225
+ JSON.stringify(reverseDepsMap) +
226
+ ", " +
227
+ JSON.stringify(entryFile) +
228
+ ", " +
229
+ String(reactRefresh) +
230
+ ");\n");
231
+ }
@@ -0,0 +1,71 @@
1
+ import { VirtualFS } from "./fs.js";
2
+ import { DependencyGraph } from "./dependency-graph.js";
3
+ import { ModuleCache } from "./module-cache.js";
4
+ import type { BundlerConfig, FileChange, IncrementalBuildResult } from "./types.js";
5
+ export declare class IncrementalBundler {
6
+ private fs;
7
+ private resolver;
8
+ private config;
9
+ private plugins;
10
+ readonly graph: DependencyGraph;
11
+ readonly cache: ModuleCache;
12
+ private moduleMap;
13
+ private sourceMapMap;
14
+ private entryFile;
15
+ private packageVersions;
16
+ private transitiveDepsVersions;
17
+ constructor(fs: VirtualFS, config: BundlerConfig);
18
+ /** Read tsconfig.json "compilerOptions.paths" from the VirtualFS */
19
+ private static readTsconfigPaths;
20
+ /** Run the full pre-transform -> Sucrase -> post-transform pipeline */
21
+ private runTransform;
22
+ /** Build a resolve callback that consults plugins then falls back to default resolution */
23
+ private makeResolveTarget;
24
+ /** Collect module aliases from all plugins */
25
+ private getModuleAliases;
26
+ /** Collect module shims from all plugins */
27
+ private getShimModules;
28
+ /** Scan npm packages in the module map for require calls not yet fetched */
29
+ private findTransitiveNpmDeps;
30
+ /** Transform a single file using the configured transformer */
31
+ private transformFile;
32
+ /** Read dependency versions from the project's package.json */
33
+ private getPackageVersions;
34
+ /** Resolve an npm specifier to a versioned form.
35
+ * Priority: user's package.json > transitive dep versions from manifests > bare name */
36
+ private resolveNpmSpecifier;
37
+ /** Fetch a pre-bundled npm package from the package server */
38
+ private fetchPackage;
39
+ /**
40
+ * Process a single local file: transform, rewrite requires, extract deps,
41
+ * update cache and graph. Returns the list of npm deps found.
42
+ */
43
+ private processFile;
44
+ /**
45
+ * Walk the dependency tree starting from a file, processing all reachable modules.
46
+ *
47
+ * Returns all files that were visited and processed, so callers can include
48
+ * them in HMR updatedModules. Without this, only the direct dependency would
49
+ * be sent to the iframe — transitive deps (e.g. hooks/index.ts → hooks/useAuth.ts)
50
+ * would be missing from the HMR payload, causing "Module not found" at runtime.
51
+ *
52
+ * Files that don't exist yet on the VFS are silently skipped. During AI streaming,
53
+ * a barrel file (e.g. seeds/index.ts) may reference a sibling (e.g. seeds/users.ts)
54
+ * that hasn't been created yet. Without this guard, processFile() would throw
55
+ * "File not found" and crash the entire rebuild. The missing file will be picked
56
+ * up on a subsequent rebuild once it's created.
57
+ */
58
+ private walkDeps;
59
+ /** Fetch all npm packages that aren't already cached */
60
+ private fetchNpmPackages;
61
+ /** Emit the bundle using HMR runtime or standard IIFE */
62
+ private emitBundle;
63
+ /** Compute source map inputs with correct line offsets for each module */
64
+ private buildSourceMapInputs;
65
+ /** Initial full build */
66
+ build(entryFile: string): Promise<IncrementalBuildResult>;
67
+ /** Incremental rebuild based on file changes */
68
+ rebuild(changes: FileChange[]): Promise<IncrementalBuildResult>;
69
+ /** Update the virtual filesystem */
70
+ updateFS(fs: VirtualFS): void;
71
+ }