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,99 @@
1
+ import type { RawSourceMap } from "./source-map.js";
2
+ export interface FileEntry {
3
+ content: string;
4
+ isExternal: boolean;
5
+ }
6
+ export interface FileMap {
7
+ [path: string]: FileEntry;
8
+ }
9
+ export interface ModuleMap {
10
+ [id: string]: string;
11
+ }
12
+ export interface TransformResult {
13
+ code: string;
14
+ sourceMap?: RawSourceMap;
15
+ }
16
+ export interface TransformParams {
17
+ src: string;
18
+ filename: string;
19
+ }
20
+ export interface Transformer {
21
+ transform(params: TransformParams): TransformResult;
22
+ }
23
+ export interface ResolverConfig {
24
+ sourceExts: string[];
25
+ paths?: Record<string, string[]>;
26
+ }
27
+ export interface BundlerPlugin {
28
+ name: string;
29
+ /** Runs BEFORE Sucrase. Receives raw .tsx/.ts source (JSX still intact). */
30
+ transformSource?(params: {
31
+ src: string;
32
+ filename: string;
33
+ }): {
34
+ src: string;
35
+ } | null;
36
+ /** Runs AFTER Sucrase. Receives CommonJS output. */
37
+ transformOutput?(params: {
38
+ code: string;
39
+ filename: string;
40
+ }): {
41
+ code: string;
42
+ } | null;
43
+ /** Custom module resolution. Return a resolved path or npm name, or null to fall through. */
44
+ resolveRequest?(context: {
45
+ fromFile: string;
46
+ }, moduleName: string): string | null;
47
+ /**
48
+ * Module aliases. Returns a map of `{ source: target }`.
49
+ * The bundler injects shim modules so `require(source)` re-exports `target`.
50
+ * Works for ALL require calls -- local files and npm packages alike.
51
+ */
52
+ moduleAliases?(): Record<string, string>;
53
+ /**
54
+ * Module shims. Returns a map of `{ moduleName: inlineCode }`.
55
+ * Replaces npm packages with lightweight inline implementations.
56
+ * Shimmed modules are NOT fetched from the package server.
57
+ */
58
+ shimModules?(): Record<string, string>;
59
+ }
60
+ export interface BundlerConfig {
61
+ resolver: ResolverConfig;
62
+ transformer: Transformer;
63
+ server: {
64
+ packageServerUrl: string;
65
+ };
66
+ hmr?: {
67
+ enabled: boolean;
68
+ reactRefresh?: boolean;
69
+ };
70
+ plugins?: BundlerPlugin[];
71
+ env?: Record<string, string>;
72
+ routerShim?: boolean;
73
+ /** URL prefix for external assets, e.g. "/projects/expo-real" */
74
+ assetPublicPath?: string;
75
+ }
76
+ export interface FileChange {
77
+ path: string;
78
+ type: "create" | "update" | "delete";
79
+ }
80
+ export interface ContentChange {
81
+ path: string;
82
+ type: "create" | "update" | "delete";
83
+ content?: string;
84
+ }
85
+ export interface HmrUpdate {
86
+ updatedModules: Record<string, string>;
87
+ removedModules: string[];
88
+ requiresReload: boolean;
89
+ reloadReason?: string;
90
+ reverseDepsMap?: Record<string, string[]>;
91
+ }
92
+ export interface IncrementalBuildResult {
93
+ bundle: string;
94
+ hmrUpdate: HmrUpdate | null;
95
+ type: "full" | "incremental";
96
+ rebuiltModules: string[];
97
+ removedModules: string[];
98
+ buildTime: number;
99
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,31 @@
1
+ /** Extract all require('...') call targets from source */
2
+ export declare function findRequires(source: string): string[];
3
+ /**
4
+ * Rewrite require calls using a resolve callback.
5
+ * resolveTarget returns a string to rewrite to, or null to leave unchanged.
6
+ */
7
+ export declare function rewriteRequires(source: string, fromFile: string, resolveTarget: (target: string) => string | null): string;
8
+ /**
9
+ * Build the bundle preamble (Metro-style). Defines `process` global so npm
10
+ * packages that check `process.env.NODE_ENV` work in the browser.
11
+ * Optionally injects public env vars (EXPO_PUBLIC_*, NEXT_PUBLIC_*).
12
+ * Optionally appends the router shim for virtualizing History/Location APIs.
13
+ */
14
+ export declare function buildBundlePreamble(env?: Record<string, string>, routerShim?: boolean): string;
15
+ /**
16
+ * Build a self-contained IIFE that virtualizes the History API.
17
+ *
18
+ * The iframe is loaded via a blob URL with the initial route encoded in the
19
+ * hash fragment (e.g. blob:origin/uuid#/explore). Location properties are
20
+ * [LegacyUnforgeable] so we can't override them, but we CAN call the real
21
+ * replaceState to change the blob URL's pathname from the UUID to the
22
+ * virtual path. After that, location.pathname returns the correct value
23
+ * for Expo Router's initial route match.
24
+ *
25
+ * All subsequent pushState/replaceState calls are intercepted so no further
26
+ * real URL changes occur. The current virtual route is stored in
27
+ * window.__ROUTER_SHIM_HASH__ for the parent to read across iframe rebuilds.
28
+ */
29
+ export declare function buildRouterShim(): string;
30
+ /** Fast djb2 hash for cache invalidation */
31
+ export declare function hashString(str: string): string;
package/dist/utils.js ADDED
@@ -0,0 +1,208 @@
1
+ const REQUIRE_RE = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2
+ // Matches lines that are single-line comments or JSDoc/block comment continuations
3
+ const COMMENT_LINE_RE = /^\s*(?:\/\/|\/?\*)/;
4
+ /** Extract all require('...') call targets from source */
5
+ export function findRequires(source) {
6
+ if (!source)
7
+ return [];
8
+ const requires = [];
9
+ const lines = source.split("\n");
10
+ for (let i = 0; i < lines.length; i++) {
11
+ const line = lines[i];
12
+ // Skip comment lines to avoid picking up require() in JSDoc examples
13
+ if (COMMENT_LINE_RE.test(line))
14
+ continue;
15
+ const re = new RegExp(REQUIRE_RE.source, REQUIRE_RE.flags);
16
+ let match;
17
+ while ((match = re.exec(line)) !== null) {
18
+ requires.push(match[1]);
19
+ }
20
+ }
21
+ return requires;
22
+ }
23
+ /**
24
+ * Rewrite require calls using a resolve callback.
25
+ * resolveTarget returns a string to rewrite to, or null to leave unchanged.
26
+ */
27
+ export function rewriteRequires(source, fromFile, resolveTarget) {
28
+ return source.replace(REQUIRE_RE, (full, target) => {
29
+ const resolved = resolveTarget(target);
30
+ if (resolved === null)
31
+ return full;
32
+ return 'require("' + resolved + '")';
33
+ });
34
+ }
35
+ const PUBLIC_ENV_PREFIXES = ["EXPO_PUBLIC_", "NEXT_PUBLIC_"];
36
+ /**
37
+ * Build the bundle preamble (Metro-style). Defines `process` global so npm
38
+ * packages that check `process.env.NODE_ENV` work in the browser.
39
+ * Optionally injects public env vars (EXPO_PUBLIC_*, NEXT_PUBLIC_*).
40
+ * Optionally appends the router shim for virtualizing History/Location APIs.
41
+ */
42
+ export function buildBundlePreamble(env, routerShim) {
43
+ let preamble = "var process = globalThis.process || {};\n" +
44
+ "process.env = process.env || {};\n" +
45
+ 'process.env.NODE_ENV = process.env.NODE_ENV || "development";\n';
46
+ if (env) {
47
+ for (const [key, value] of Object.entries(env)) {
48
+ if (PUBLIC_ENV_PREFIXES.some((p) => key.startsWith(p))) {
49
+ preamble += "process.env." + key + " = " + JSON.stringify(value) + ";\n";
50
+ }
51
+ }
52
+ }
53
+ if (routerShim) {
54
+ preamble += buildRouterShim();
55
+ }
56
+ return preamble;
57
+ }
58
+ /**
59
+ * Build a self-contained IIFE that virtualizes the History API.
60
+ *
61
+ * The iframe is loaded via a blob URL with the initial route encoded in the
62
+ * hash fragment (e.g. blob:origin/uuid#/explore). Location properties are
63
+ * [LegacyUnforgeable] so we can't override them, but we CAN call the real
64
+ * replaceState to change the blob URL's pathname from the UUID to the
65
+ * virtual path. After that, location.pathname returns the correct value
66
+ * for Expo Router's initial route match.
67
+ *
68
+ * All subsequent pushState/replaceState calls are intercepted so no further
69
+ * real URL changes occur. The current virtual route is stored in
70
+ * window.__ROUTER_SHIM_HASH__ for the parent to read across iframe rebuilds.
71
+ */
72
+ export function buildRouterShim() {
73
+ return `(function() {
74
+ var rawHash = location.hash.slice(1) || '/';
75
+
76
+ var hashIdx = rawHash.indexOf('#');
77
+ var virtualHash = '';
78
+ var pathAndSearch = rawHash;
79
+ if (hashIdx > 0) {
80
+ virtualHash = rawHash.slice(hashIdx);
81
+ pathAndSearch = rawHash.slice(0, hashIdx);
82
+ }
83
+
84
+ var searchIdx = pathAndSearch.indexOf('?');
85
+ var virtualPathname = searchIdx >= 0 ? pathAndSearch.slice(0, searchIdx) : pathAndSearch;
86
+ var virtualSearch = searchIdx >= 0 ? pathAndSearch.slice(searchIdx) : '';
87
+
88
+ if (virtualPathname.charAt(0) !== '/') virtualPathname = '/' + virtualPathname;
89
+
90
+ var virtualOrigin = location.origin || 'http://localhost';
91
+ var virtualHref = virtualOrigin + virtualPathname + virtualSearch + virtualHash;
92
+
93
+ // Override document.URL (configurable, unlike location.*)
94
+ try {
95
+ Object.defineProperty(Document.prototype, 'URL', {
96
+ get: function() { return virtualHref; },
97
+ configurable: true
98
+ });
99
+ } catch(e) {}
100
+
101
+ // Wrap URL constructor so new URL(location.href) returns the virtual URL
102
+ // instead of parsing the blob UUID as a pathname.
103
+ var OrigURL = URL;
104
+ var _URL = function(url, base) {
105
+ var u = String(url);
106
+ if (u.indexOf('blob:') === 0) u = virtualHref;
107
+ if (arguments.length > 1) return new OrigURL(u, base);
108
+ return new OrigURL(u);
109
+ };
110
+ _URL.prototype = OrigURL.prototype;
111
+ _URL.createObjectURL = OrigURL.createObjectURL;
112
+ _URL.revokeObjectURL = OrigURL.revokeObjectURL;
113
+ _URL.canParse = OrigURL.canParse;
114
+ window.URL = _URL;
115
+
116
+ // Save real replaceState before overriding
117
+ var _realReplaceState = history.replaceState;
118
+ // Base blob URL without hash, for constructing absolute URLs in sync()
119
+ var _blobBase = location.href.split('#')[0];
120
+
121
+ var stack = [{ state: null, pathname: virtualPathname, search: virtualSearch, hash: virtualHash }];
122
+ var stackIndex = 0;
123
+
124
+ function currentEntry() { return stack[stackIndex]; }
125
+
126
+ function sync() {
127
+ var e = currentEntry();
128
+ virtualPathname = e.pathname;
129
+ virtualSearch = e.search;
130
+ virtualHash = e.hash;
131
+ virtualHref = virtualOrigin + virtualPathname + virtualSearch + virtualHash;
132
+ var routeHash = '#' + e.pathname + e.search;
133
+ window.__ROUTER_SHIM_HASH__ = routeHash;
134
+ // Update the real blob URL hash, but only if it actually changed.
135
+ var newUrl = _blobBase + routeHash;
136
+ if (newUrl !== location.href) {
137
+ try { _realReplaceState.call(history, e.state, '', newUrl); } catch(e) {}
138
+ }
139
+ }
140
+
141
+ function parseUrl(url) {
142
+ var s = String(url);
143
+ if (s.indexOf('blob:') === 0) {
144
+ try { var inner = new OrigURL(s.slice(5)); s = inner.pathname + inner.search + inner.hash; } catch(e) {}
145
+ }
146
+ try {
147
+ var u = new OrigURL(s, virtualOrigin);
148
+ return { pathname: u.pathname, search: u.search, hash: u.hash };
149
+ } catch(e) {}
150
+ var pathname = s, search = '', hash = '';
151
+ var hi = pathname.indexOf('#');
152
+ if (hi >= 0) { hash = pathname.slice(hi); pathname = pathname.slice(0, hi); }
153
+ var si = pathname.indexOf('?');
154
+ if (si >= 0) { search = pathname.slice(si); pathname = pathname.slice(0, si); }
155
+ if (!pathname) pathname = '/';
156
+ if (pathname.charAt(0) !== '/') pathname = '/' + pathname;
157
+ return { pathname: pathname, search: search, hash: hash };
158
+ }
159
+
160
+ history.pushState = function(state, title, url) {
161
+ if (url != null) {
162
+ var p = parseUrl(url);
163
+ // Ignore hash: it's our own route hash echoed back from location.hash
164
+ stack = stack.slice(0, stackIndex + 1);
165
+ stack.push({ state: state, pathname: p.pathname, search: p.search, hash: '' });
166
+ stackIndex = stack.length - 1;
167
+ sync();
168
+ }
169
+ };
170
+
171
+ history.replaceState = function(state, title, url) {
172
+ if (url != null) {
173
+ var p = parseUrl(url);
174
+ stack[stackIndex] = { state: state, pathname: p.pathname, search: p.search, hash: '' };
175
+ sync();
176
+ }
177
+ };
178
+
179
+ history.go = function(n) {
180
+ if (!n) return;
181
+ var ni = stackIndex + n;
182
+ if (ni < 0) ni = 0;
183
+ if (ni >= stack.length) ni = stack.length - 1;
184
+ if (ni === stackIndex) return;
185
+ stackIndex = ni;
186
+ sync();
187
+ window.dispatchEvent(new PopStateEvent('popstate', { state: currentEntry().state }));
188
+ };
189
+ history.back = function() { history.go(-1); };
190
+ history.forward = function() { history.go(1); };
191
+
192
+ Object.defineProperty(history, 'state', {
193
+ get: function() { return currentEntry().state; },
194
+ configurable: true
195
+ });
196
+
197
+ sync();
198
+ })();
199
+ `;
200
+ }
201
+ /** Fast djb2 hash for cache invalidation */
202
+ export function hashString(str) {
203
+ let hash = 5381;
204
+ for (let i = 0; i < str.length; i++) {
205
+ hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0;
206
+ }
207
+ return (hash >>> 0).toString(36);
208
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "browser-metro",
3
+ "version": "1.0.5",
4
+ "description": "A browser-based JavaScript/TypeScript bundler with HMR support",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "prepublishOnly": "tsc"
14
+ },
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "sucrase": "^3.35.0"
18
+ },
19
+ "devDependencies": {
20
+ "typescript": "^5.7.0"
21
+ }
22
+ }