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.
- package/dist/bundler.d.ts +34 -0
- package/dist/bundler.js +320 -0
- package/dist/dependency-graph.d.ts +22 -0
- package/dist/dependency-graph.js +128 -0
- package/dist/fs.d.ts +20 -0
- package/dist/fs.js +107 -0
- package/dist/hmr-runtime.d.ts +14 -0
- package/dist/hmr-runtime.js +231 -0
- package/dist/incremental-bundler.d.ts +71 -0
- package/dist/incremental-bundler.js +646 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -0
- package/dist/module-cache.d.ts +22 -0
- package/dist/module-cache.js +31 -0
- package/dist/plugins/data-bx-path.d.ts +2 -0
- package/dist/plugins/data-bx-path.js +197 -0
- package/dist/resolver.d.ts +18 -0
- package/dist/resolver.js +84 -0
- package/dist/source-map.d.ts +36 -0
- package/dist/source-map.js +186 -0
- package/dist/transforms/react-refresh.d.ts +5 -0
- package/dist/transforms/react-refresh.js +92 -0
- package/dist/transforms/typescript.d.ts +2 -0
- package/dist/transforms/typescript.js +20 -0
- package/dist/types.d.ts +99 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +31 -0
- package/dist/utils.js +208 -0
- package/package.json +22 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class ModuleCache {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.modules = new Map();
|
|
4
|
+
this.npmPackages = new Map();
|
|
5
|
+
}
|
|
6
|
+
getModule(id) {
|
|
7
|
+
return this.modules.get(id);
|
|
8
|
+
}
|
|
9
|
+
setModule(id, data) {
|
|
10
|
+
this.modules.set(id, data);
|
|
11
|
+
}
|
|
12
|
+
invalidateModule(id) {
|
|
13
|
+
this.modules.delete(id);
|
|
14
|
+
}
|
|
15
|
+
isValid(id, currentSourceHash) {
|
|
16
|
+
const cached = this.modules.get(id);
|
|
17
|
+
return cached !== undefined && cached.sourceHash === currentSourceHash;
|
|
18
|
+
}
|
|
19
|
+
getNpmPackage(specifier) {
|
|
20
|
+
return this.npmPackages.get(specifier);
|
|
21
|
+
}
|
|
22
|
+
setNpmPackage(specifier, code) {
|
|
23
|
+
this.npmPackages.set(specifier, code);
|
|
24
|
+
}
|
|
25
|
+
hasNpmPackage(specifier) {
|
|
26
|
+
return this.npmPackages.has(specifier);
|
|
27
|
+
}
|
|
28
|
+
invalidateNpmPackages() {
|
|
29
|
+
this.npmPackages.clear();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
function isJSX(filename) {
|
|
2
|
+
return filename.endsWith(".tsx") || filename.endsWith(".jsx");
|
|
3
|
+
}
|
|
4
|
+
function isTagNameChar(c) {
|
|
5
|
+
return ((c >= 65 && c <= 90) ||
|
|
6
|
+
(c >= 97 && c <= 122) || // A-Z, a-z
|
|
7
|
+
(c >= 48 && c <= 57) || // 0-9
|
|
8
|
+
c === 95 ||
|
|
9
|
+
c === 46); // _, .
|
|
10
|
+
}
|
|
11
|
+
function isTagStartChar(c) {
|
|
12
|
+
return (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95; // A-Z, a-z, _
|
|
13
|
+
}
|
|
14
|
+
function isWordChar(c) {
|
|
15
|
+
return ((c >= 65 && c <= 90) ||
|
|
16
|
+
(c >= 97 && c <= 122) || // A-Z, a-z
|
|
17
|
+
(c >= 48 && c <= 57) || // 0-9
|
|
18
|
+
c === 95 ||
|
|
19
|
+
c === 36); // _, $
|
|
20
|
+
}
|
|
21
|
+
const JSX_KEYWORDS = new Set([
|
|
22
|
+
"return",
|
|
23
|
+
"yield",
|
|
24
|
+
"default",
|
|
25
|
+
"case",
|
|
26
|
+
"throw",
|
|
27
|
+
"new",
|
|
28
|
+
"await",
|
|
29
|
+
]);
|
|
30
|
+
/**
|
|
31
|
+
* Check if `<` at position `pos` is in a JSX context (not a comparison operator).
|
|
32
|
+
* Looks backwards to find the preceding non-whitespace token and decides:
|
|
33
|
+
* - After `)` or `]` -> comparison (e.g. `fn() < x`, `arr[0] < x`)
|
|
34
|
+
* - After a word char -> comparison UNLESS the word is a JSX keyword (return, yield, etc.)
|
|
35
|
+
* - After anything else (operators, brackets, =, etc.) -> JSX
|
|
36
|
+
*/
|
|
37
|
+
function isJsxContext(src, pos) {
|
|
38
|
+
let j = pos - 1;
|
|
39
|
+
while (j >= 0) {
|
|
40
|
+
const c = src.charCodeAt(j);
|
|
41
|
+
if (c !== 32 && c !== 9 && c !== 10 && c !== 13)
|
|
42
|
+
break;
|
|
43
|
+
j--;
|
|
44
|
+
}
|
|
45
|
+
if (j < 0)
|
|
46
|
+
return true; // start of file
|
|
47
|
+
const c = src.charCodeAt(j);
|
|
48
|
+
// After ) or ] -> likely comparison
|
|
49
|
+
if (c === 41 || c === 93)
|
|
50
|
+
return false;
|
|
51
|
+
// After a word character -> check if it's a JSX-preceding keyword
|
|
52
|
+
if (isWordChar(c)) {
|
|
53
|
+
const end = j + 1;
|
|
54
|
+
while (j >= 0 && isWordChar(src.charCodeAt(j)))
|
|
55
|
+
j--;
|
|
56
|
+
const word = src.slice(j + 1, end);
|
|
57
|
+
return JSX_KEYWORDS.has(word);
|
|
58
|
+
}
|
|
59
|
+
// After anything else (operators, brackets, etc.) -> likely JSX
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
function injectDataBxPath(src, filename) {
|
|
63
|
+
const len = src.length;
|
|
64
|
+
let result = "";
|
|
65
|
+
let i = 0;
|
|
66
|
+
let line = 1;
|
|
67
|
+
let col = 1;
|
|
68
|
+
// State: 0=normal, 1=single-quote, 2=double-quote, 3=template, 4=line-comment, 5=block-comment
|
|
69
|
+
let state = 0;
|
|
70
|
+
while (i < len) {
|
|
71
|
+
const cc = src.charCodeAt(i);
|
|
72
|
+
// Handle escape sequences in strings/template literals
|
|
73
|
+
if ((state === 1 || state === 2 || state === 3) && cc === 92) {
|
|
74
|
+
// backslash
|
|
75
|
+
result += src[i];
|
|
76
|
+
i++;
|
|
77
|
+
col++;
|
|
78
|
+
if (i < len) {
|
|
79
|
+
result += src[i];
|
|
80
|
+
if (src.charCodeAt(i) === 10) {
|
|
81
|
+
line++;
|
|
82
|
+
col = 1;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
col++;
|
|
86
|
+
}
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (state === 0) {
|
|
92
|
+
if (cc === 39) {
|
|
93
|
+
state = 1; // '
|
|
94
|
+
}
|
|
95
|
+
else if (cc === 34) {
|
|
96
|
+
state = 2; // "
|
|
97
|
+
}
|
|
98
|
+
else if (cc === 96) {
|
|
99
|
+
state = 3; // `
|
|
100
|
+
}
|
|
101
|
+
else if (cc === 47 && i + 1 < len) {
|
|
102
|
+
// /
|
|
103
|
+
const next = src.charCodeAt(i + 1);
|
|
104
|
+
if (next === 47) {
|
|
105
|
+
state = 4; // //
|
|
106
|
+
}
|
|
107
|
+
else if (next === 42) {
|
|
108
|
+
state = 5; // /*
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (cc === 60 &&
|
|
112
|
+
i + 1 < len &&
|
|
113
|
+
isTagStartChar(src.charCodeAt(i + 1))) {
|
|
114
|
+
// Potential JSX: < followed by [A-Za-z_]
|
|
115
|
+
if (isJsxContext(src, i)) {
|
|
116
|
+
const tagLine = line;
|
|
117
|
+
const tagCol = col;
|
|
118
|
+
// Emit '<'
|
|
119
|
+
result += "<";
|
|
120
|
+
i++;
|
|
121
|
+
col++;
|
|
122
|
+
// Extract tag name (supports dotted: Motion.div)
|
|
123
|
+
let tagName = "";
|
|
124
|
+
while (i < len && isTagNameChar(src.charCodeAt(i))) {
|
|
125
|
+
tagName += src[i];
|
|
126
|
+
result += src[i];
|
|
127
|
+
i++;
|
|
128
|
+
col++;
|
|
129
|
+
}
|
|
130
|
+
// Skip fragments and generics
|
|
131
|
+
const nextCc = i < len ? src.charCodeAt(i) : 0;
|
|
132
|
+
const isFragment = tagName === "Fragment" || tagName === "React.Fragment";
|
|
133
|
+
const isGeneric = nextCc === 60; // followed by <
|
|
134
|
+
if (!isFragment && !isGeneric && tagName.length > 0) {
|
|
135
|
+
const pathVal = filename + ":" + tagLine + ":" + tagCol;
|
|
136
|
+
const firstChar = tagName.charCodeAt(0);
|
|
137
|
+
// Lowercase tags (HTML elements): use data- attribute (React passes it to DOM)
|
|
138
|
+
// Uppercase/dotted tags (components): use dataSet (RNW renders as data-* on host element)
|
|
139
|
+
if (firstChar >= 97 && firstChar <= 122) {
|
|
140
|
+
result += ' data-bx-path="' + pathVal + '"';
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
result += ' dataSet={{"bx-path":"' + pathVal + '"}}';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
else if (state === 1) {
|
|
151
|
+
if (cc === 39)
|
|
152
|
+
state = 0;
|
|
153
|
+
}
|
|
154
|
+
else if (state === 2) {
|
|
155
|
+
if (cc === 34)
|
|
156
|
+
state = 0;
|
|
157
|
+
}
|
|
158
|
+
else if (state === 3) {
|
|
159
|
+
if (cc === 96)
|
|
160
|
+
state = 0;
|
|
161
|
+
}
|
|
162
|
+
else if (state === 4) {
|
|
163
|
+
if (cc === 10)
|
|
164
|
+
state = 0;
|
|
165
|
+
}
|
|
166
|
+
else if (state === 5) {
|
|
167
|
+
if (cc === 42 && i + 1 < len && src.charCodeAt(i + 1) === 47) {
|
|
168
|
+
// */
|
|
169
|
+
result += "*/";
|
|
170
|
+
col += 2;
|
|
171
|
+
i += 2;
|
|
172
|
+
state = 0;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
result += src[i];
|
|
177
|
+
if (cc === 10) {
|
|
178
|
+
line++;
|
|
179
|
+
col = 1;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
col++;
|
|
183
|
+
}
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
export function createDataBxPathPlugin() {
|
|
189
|
+
return {
|
|
190
|
+
name: "data-bx-path",
|
|
191
|
+
transformSource({ src, filename }) {
|
|
192
|
+
if (!isJSX(filename))
|
|
193
|
+
return null;
|
|
194
|
+
return { src: injectDataBxPath(src, filename) };
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { VirtualFS } from "./fs.js";
|
|
2
|
+
import { ResolverConfig } from "./types.js";
|
|
3
|
+
export declare class Resolver {
|
|
4
|
+
private fs;
|
|
5
|
+
private sourceExts;
|
|
6
|
+
private pathAliases;
|
|
7
|
+
constructor(fs: VirtualFS, config: ResolverConfig);
|
|
8
|
+
/** Expand tsconfig path aliases. Returns null if no alias matched. */
|
|
9
|
+
private expandAlias;
|
|
10
|
+
/** Check if a require target is an npm package (not a relative/absolute path) */
|
|
11
|
+
isNpmPackage(target: string): boolean;
|
|
12
|
+
/** Resolve a relative require path against the current module's directory */
|
|
13
|
+
resolvePath(from: string, to: string): string;
|
|
14
|
+
/** Check if a path is an asset file (image, font, css, etc.) */
|
|
15
|
+
isAssetFile(filePath: string): boolean;
|
|
16
|
+
/** Try to find the actual file path (with extension, or index file) */
|
|
17
|
+
resolveFile(resolved: string): string | null;
|
|
18
|
+
}
|
package/dist/resolver.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export class Resolver {
|
|
2
|
+
constructor(fs, config) {
|
|
3
|
+
// Compiled path aliases: [prefix to match, prefix to replace with]
|
|
4
|
+
this.pathAliases = [];
|
|
5
|
+
this.fs = fs;
|
|
6
|
+
this.sourceExts = config.sourceExts;
|
|
7
|
+
// Compile tsconfig "paths" into simple prefix pairs
|
|
8
|
+
if (config.paths) {
|
|
9
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
10
|
+
if (!targets.length)
|
|
11
|
+
continue;
|
|
12
|
+
// "@/*" → prefix "@/", "./*" → replacement "/"
|
|
13
|
+
const from = pattern.replace(/\*$/, "");
|
|
14
|
+
const to = "/" + targets[0].replace(/^\.\/?/, "").replace(/\*$/, "");
|
|
15
|
+
this.pathAliases.push([from, to]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** Expand tsconfig path aliases. Returns null if no alias matched. */
|
|
20
|
+
expandAlias(target) {
|
|
21
|
+
for (const [from, to] of this.pathAliases) {
|
|
22
|
+
if (target.startsWith(from)) {
|
|
23
|
+
return to + target.slice(from.length);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
/** Check if a require target is an npm package (not a relative/absolute path) */
|
|
29
|
+
isNpmPackage(target) {
|
|
30
|
+
if (target[0] === "." || target[0] === "/")
|
|
31
|
+
return false;
|
|
32
|
+
if (this.expandAlias(target) !== null)
|
|
33
|
+
return false;
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
/** Resolve a relative require path against the current module's directory */
|
|
37
|
+
resolvePath(from, to) {
|
|
38
|
+
// Expand path aliases first (e.g. "@/hooks/foo" → "/hooks/foo")
|
|
39
|
+
const expanded = this.expandAlias(to);
|
|
40
|
+
if (expanded)
|
|
41
|
+
return expanded;
|
|
42
|
+
if (this.isNpmPackage(to))
|
|
43
|
+
return to;
|
|
44
|
+
// Get directory of 'from'
|
|
45
|
+
const parts = from.split("/");
|
|
46
|
+
parts.pop(); // remove filename
|
|
47
|
+
const dir = parts.join("/") || "/";
|
|
48
|
+
// Resolve relative path
|
|
49
|
+
const segments = (dir + "/" + to).split("/").filter(Boolean);
|
|
50
|
+
const resolved = [];
|
|
51
|
+
for (let i = 0; i < segments.length; i++) {
|
|
52
|
+
if (segments[i] === "..")
|
|
53
|
+
resolved.pop();
|
|
54
|
+
else if (segments[i] !== ".")
|
|
55
|
+
resolved.push(segments[i]);
|
|
56
|
+
}
|
|
57
|
+
return "/" + resolved.join("/");
|
|
58
|
+
}
|
|
59
|
+
/** Check if a path is an asset file (image, font, css, etc.) */
|
|
60
|
+
isAssetFile(filePath) {
|
|
61
|
+
return /\.(png|jpe?g|gif|svg|webp|bmp|ico|ttf|otf|woff2?|mp[34]|wav|aac|pdf|css)$/i.test(filePath);
|
|
62
|
+
}
|
|
63
|
+
/** Try to find the actual file path (with extension, or index file) */
|
|
64
|
+
resolveFile(resolved) {
|
|
65
|
+
if (this.fs.exists(resolved))
|
|
66
|
+
return resolved;
|
|
67
|
+
// Asset files resolve to themselves even if not in VFS
|
|
68
|
+
if (this.isAssetFile(resolved))
|
|
69
|
+
return resolved;
|
|
70
|
+
// Try each configured source extension
|
|
71
|
+
for (const ext of this.sourceExts) {
|
|
72
|
+
const withExt = resolved + "." + ext;
|
|
73
|
+
if (this.fs.exists(withExt))
|
|
74
|
+
return withExt;
|
|
75
|
+
}
|
|
76
|
+
// Try index files
|
|
77
|
+
for (const ext of this.sourceExts) {
|
|
78
|
+
const indexPath = resolved + "/index." + ext;
|
|
79
|
+
if (this.fs.exists(indexPath))
|
|
80
|
+
return indexPath;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Source map v3 interface */
|
|
2
|
+
export interface RawSourceMap {
|
|
3
|
+
version: number;
|
|
4
|
+
file?: string;
|
|
5
|
+
sources: string[];
|
|
6
|
+
sourcesContent?: string[];
|
|
7
|
+
mappings: string;
|
|
8
|
+
names: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Decoded segment: either [genCol] (unmapped) or
|
|
12
|
+
* [genCol, srcIdx, origLine, origCol] or
|
|
13
|
+
* [genCol, srcIdx, origLine, origCol, nameIdx]
|
|
14
|
+
*/
|
|
15
|
+
export type Segment = [number] | [number, number, number, number] | [number, number, number, number, number];
|
|
16
|
+
export declare function encodeVLQ(value: number): string;
|
|
17
|
+
export declare function decodeVLQ(str: string, offset: number): {
|
|
18
|
+
value: number;
|
|
19
|
+
next: number;
|
|
20
|
+
};
|
|
21
|
+
export declare function decodeMappings(mappings: string): Segment[][];
|
|
22
|
+
export declare function encodeMappings(decoded: Segment[][]): string;
|
|
23
|
+
export interface ModuleSourceMapInput {
|
|
24
|
+
sourceFile: string;
|
|
25
|
+
sourceContent: string;
|
|
26
|
+
map: RawSourceMap;
|
|
27
|
+
generatedLineOffset: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function buildCombinedSourceMap(modules: ModuleSourceMapInput[]): RawSourceMap;
|
|
30
|
+
export declare function inlineSourceMap(map: RawSourceMap): string;
|
|
31
|
+
/**
|
|
32
|
+
* Shift all origLine values in a source map by `lineOffset`.
|
|
33
|
+
* Use negative offset to compensate for lines prepended by plugins.
|
|
34
|
+
*/
|
|
35
|
+
export declare function shiftSourceMapOrigLines(map: RawSourceMap, lineOffset: number): RawSourceMap;
|
|
36
|
+
export declare function countNewlines(s: string): number;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
2
|
+
const BASE64_DECODE = new Array(128).fill(-1);
|
|
3
|
+
for (let i = 0; i < BASE64.length; i++)
|
|
4
|
+
BASE64_DECODE[BASE64.charCodeAt(i)] = i;
|
|
5
|
+
export function encodeVLQ(value) {
|
|
6
|
+
let vlq = value < 0 ? (-value << 1) | 1 : value << 1;
|
|
7
|
+
let result = "";
|
|
8
|
+
do {
|
|
9
|
+
let digit = vlq & 0x1f;
|
|
10
|
+
vlq >>>= 5;
|
|
11
|
+
if (vlq > 0)
|
|
12
|
+
digit |= 0x20;
|
|
13
|
+
result += BASE64[digit];
|
|
14
|
+
} while (vlq > 0);
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
export function decodeVLQ(str, offset) {
|
|
18
|
+
let result = 0;
|
|
19
|
+
let shift = 0;
|
|
20
|
+
let continuation;
|
|
21
|
+
let i = offset;
|
|
22
|
+
do {
|
|
23
|
+
const digit = BASE64_DECODE[str.charCodeAt(i++)];
|
|
24
|
+
continuation = (digit & 0x20) !== 0;
|
|
25
|
+
result += (digit & 0x1f) << shift;
|
|
26
|
+
shift += 5;
|
|
27
|
+
} while (continuation);
|
|
28
|
+
const isNegative = (result & 1) !== 0;
|
|
29
|
+
result >>= 1;
|
|
30
|
+
return { value: isNegative ? -result : result, next: i };
|
|
31
|
+
}
|
|
32
|
+
export function decodeMappings(mappings) {
|
|
33
|
+
const lines = [];
|
|
34
|
+
let srcIdx = 0;
|
|
35
|
+
let origLine = 0;
|
|
36
|
+
let origCol = 0;
|
|
37
|
+
let nameIdx = 0;
|
|
38
|
+
for (const lineStr of mappings.split(";")) {
|
|
39
|
+
const segments = [];
|
|
40
|
+
let genCol = 0;
|
|
41
|
+
if (lineStr) {
|
|
42
|
+
for (const segStr of lineStr.split(",")) {
|
|
43
|
+
if (!segStr)
|
|
44
|
+
continue;
|
|
45
|
+
let pos = 0;
|
|
46
|
+
const fields = [];
|
|
47
|
+
while (pos < segStr.length) {
|
|
48
|
+
const { value, next } = decodeVLQ(segStr, pos);
|
|
49
|
+
fields.push(value);
|
|
50
|
+
pos = next;
|
|
51
|
+
}
|
|
52
|
+
if (fields.length >= 4) {
|
|
53
|
+
genCol += fields[0];
|
|
54
|
+
srcIdx += fields[1];
|
|
55
|
+
origLine += fields[2];
|
|
56
|
+
origCol += fields[3];
|
|
57
|
+
if (fields.length >= 5) {
|
|
58
|
+
nameIdx += fields[4];
|
|
59
|
+
segments.push([genCol, srcIdx, origLine, origCol, nameIdx]);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
segments.push([genCol, srcIdx, origLine, origCol]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (fields.length >= 1) {
|
|
66
|
+
genCol += fields[0];
|
|
67
|
+
segments.push([genCol]);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
lines.push(segments);
|
|
72
|
+
}
|
|
73
|
+
return lines;
|
|
74
|
+
}
|
|
75
|
+
export function encodeMappings(decoded) {
|
|
76
|
+
let prevSrcIdx = 0;
|
|
77
|
+
let prevOrigLine = 0;
|
|
78
|
+
let prevOrigCol = 0;
|
|
79
|
+
let prevNameIdx = 0;
|
|
80
|
+
const lineStrs = [];
|
|
81
|
+
for (const segments of decoded) {
|
|
82
|
+
let prevGenCol = 0;
|
|
83
|
+
const segStrs = [];
|
|
84
|
+
for (const seg of segments) {
|
|
85
|
+
let result = encodeVLQ(seg[0] - prevGenCol);
|
|
86
|
+
prevGenCol = seg[0];
|
|
87
|
+
if (seg.length >= 4) {
|
|
88
|
+
result += encodeVLQ(seg[1] - prevSrcIdx);
|
|
89
|
+
prevSrcIdx = seg[1];
|
|
90
|
+
result += encodeVLQ(seg[2] - prevOrigLine);
|
|
91
|
+
prevOrigLine = seg[2];
|
|
92
|
+
result += encodeVLQ(seg[3] - prevOrigCol);
|
|
93
|
+
prevOrigCol = seg[3];
|
|
94
|
+
if (seg.length >= 5) {
|
|
95
|
+
result += encodeVLQ(seg[4] - prevNameIdx);
|
|
96
|
+
prevNameIdx = seg[4];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
segStrs.push(result);
|
|
100
|
+
}
|
|
101
|
+
lineStrs.push(segStrs.join(","));
|
|
102
|
+
}
|
|
103
|
+
return lineStrs.join(";");
|
|
104
|
+
}
|
|
105
|
+
export function buildCombinedSourceMap(modules) {
|
|
106
|
+
const sources = [];
|
|
107
|
+
const sourcesContent = [];
|
|
108
|
+
const names = [];
|
|
109
|
+
const allLines = [];
|
|
110
|
+
for (const mod of modules) {
|
|
111
|
+
const sourceIndex = sources.length;
|
|
112
|
+
sources.push(mod.sourceFile);
|
|
113
|
+
sourcesContent.push(mod.sourceContent);
|
|
114
|
+
const nameOffset = names.length;
|
|
115
|
+
if (mod.map.names) {
|
|
116
|
+
names.push(...mod.map.names);
|
|
117
|
+
}
|
|
118
|
+
const decoded = decodeMappings(mod.map.mappings);
|
|
119
|
+
for (let i = 0; i < decoded.length; i++) {
|
|
120
|
+
const lineIdx = mod.generatedLineOffset + i;
|
|
121
|
+
while (allLines.length <= lineIdx) {
|
|
122
|
+
allLines.push([]);
|
|
123
|
+
}
|
|
124
|
+
for (const seg of decoded[i]) {
|
|
125
|
+
if (seg.length >= 4) {
|
|
126
|
+
let remapped;
|
|
127
|
+
if (seg.length === 5) {
|
|
128
|
+
remapped = [seg[0], sourceIndex, seg[2], seg[3], seg[4] + nameOffset];
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
remapped = [seg[0], sourceIndex, seg[2], seg[3]];
|
|
132
|
+
}
|
|
133
|
+
allLines[lineIdx].push(remapped);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
allLines[lineIdx].push(seg);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
version: 3,
|
|
143
|
+
sources,
|
|
144
|
+
sourcesContent,
|
|
145
|
+
names,
|
|
146
|
+
mappings: encodeMappings(allLines),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function toBase64(str) {
|
|
150
|
+
const bytes = new TextEncoder().encode(str);
|
|
151
|
+
let binary = "";
|
|
152
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
153
|
+
binary += String.fromCharCode(bytes[i]);
|
|
154
|
+
}
|
|
155
|
+
return btoa(binary);
|
|
156
|
+
}
|
|
157
|
+
export function inlineSourceMap(map) {
|
|
158
|
+
return ("//# sourceMappingURL=data:application/json;base64," +
|
|
159
|
+
toBase64(JSON.stringify(map)));
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Shift all origLine values in a source map by `lineOffset`.
|
|
163
|
+
* Use negative offset to compensate for lines prepended by plugins.
|
|
164
|
+
*/
|
|
165
|
+
export function shiftSourceMapOrigLines(map, lineOffset) {
|
|
166
|
+
const decoded = decodeMappings(map.mappings);
|
|
167
|
+
for (const line of decoded) {
|
|
168
|
+
for (const seg of line) {
|
|
169
|
+
if (seg.length >= 4) {
|
|
170
|
+
seg[2] += lineOffset;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
...map,
|
|
176
|
+
mappings: encodeMappings(decoded),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export function countNewlines(s) {
|
|
180
|
+
let count = 0;
|
|
181
|
+
for (let i = 0; i < s.length; i++) {
|
|
182
|
+
if (s.charCodeAt(i) === 10)
|
|
183
|
+
count++;
|
|
184
|
+
}
|
|
185
|
+
return count;
|
|
186
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Transformer } from "../types.js";
|
|
2
|
+
/** Wrap a base transformer with React Refresh instrumentation for .tsx/.jsx files */
|
|
3
|
+
export declare function createReactRefreshTransformer(base: Transformer): Transformer;
|
|
4
|
+
/** Pre-built React Refresh transformer wrapping the default TypeScript transformer */
|
|
5
|
+
export declare const reactRefreshTransformer: Transformer;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { countNewlines } from "../source-map.js";
|
|
2
|
+
import { typescriptTransformer } from "./typescript.js";
|
|
3
|
+
function isJsxFile(filename) {
|
|
4
|
+
return filename.endsWith(".tsx") || filename.endsWith(".jsx");
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Detect React component names from source code.
|
|
8
|
+
* Heuristic: any function or const/let with an uppercase first letter.
|
|
9
|
+
* We scan BOTH original source (catches `export default function App`)
|
|
10
|
+
* and transformed output (catches sucrase rewrites like `function App`).
|
|
11
|
+
*/
|
|
12
|
+
function detectComponents(originalSrc, transformedCode) {
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const components = [];
|
|
15
|
+
// Patterns to match component-like declarations
|
|
16
|
+
const patterns = [
|
|
17
|
+
// function Foo(, export function Foo(, export default function Foo(
|
|
18
|
+
/(?:export\s+(?:default\s+)?)?function\s+([A-Z][a-zA-Z0-9]*)\s*\(/g,
|
|
19
|
+
// const Foo =, let Foo =, var Foo = (covers arrow components, React.memo, etc.)
|
|
20
|
+
/(?:export\s+)?(?:const|let|var)\s+([A-Z][a-zA-Z0-9]*)\s*=/g,
|
|
21
|
+
];
|
|
22
|
+
for (const src of [originalSrc, transformedCode]) {
|
|
23
|
+
for (const pattern of patterns) {
|
|
24
|
+
const re = new RegExp(pattern.source, pattern.flags);
|
|
25
|
+
let match;
|
|
26
|
+
while ((match = re.exec(src)) !== null) {
|
|
27
|
+
const name = match[1];
|
|
28
|
+
if (!seen.has(name)) {
|
|
29
|
+
seen.add(name);
|
|
30
|
+
components.push(name);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return components;
|
|
36
|
+
}
|
|
37
|
+
/** Wrap a base transformer with React Refresh instrumentation for .tsx/.jsx files */
|
|
38
|
+
export function createReactRefreshTransformer(base) {
|
|
39
|
+
return {
|
|
40
|
+
transform(params) {
|
|
41
|
+
const result = base.transform(params);
|
|
42
|
+
if (!isJsxFile(params.filename)) {
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
const components = detectComponents(params.src, result.code);
|
|
46
|
+
if (components.length === 0) {
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
// Preamble: set up refresh hooks scoped to this module
|
|
50
|
+
const preamble = 'var _prevRefreshReg = window.$RefreshReg$;\n' +
|
|
51
|
+
'var _prevRefreshSig = window.$RefreshSig$;\n' +
|
|
52
|
+
'var _refreshModuleId = ' + JSON.stringify(params.filename) + ';\n' +
|
|
53
|
+
'window.$RefreshReg$ = function(type, id) {\n' +
|
|
54
|
+
' if (window.__REACT_REFRESH_RUNTIME__) {\n' +
|
|
55
|
+
' window.__REACT_REFRESH_RUNTIME__.register(type, _refreshModuleId + " " + id);\n' +
|
|
56
|
+
' }\n' +
|
|
57
|
+
'};\n' +
|
|
58
|
+
'window.$RefreshSig$ = function() {\n' +
|
|
59
|
+
' if (window.__REACT_REFRESH_RUNTIME__) {\n' +
|
|
60
|
+
' return window.__REACT_REFRESH_RUNTIME__.createSignatureFunctionForTransform();\n' +
|
|
61
|
+
' }\n' +
|
|
62
|
+
' return function(type) { return type; };\n' +
|
|
63
|
+
'};\n';
|
|
64
|
+
// Postamble: register each component and accept HMR
|
|
65
|
+
let postamble = '\n';
|
|
66
|
+
for (const name of components) {
|
|
67
|
+
postamble +=
|
|
68
|
+
'if (typeof ' + name + ' === "function") {\n' +
|
|
69
|
+
' $RefreshReg$(' + name + ', ' + JSON.stringify(name) + ');\n' +
|
|
70
|
+
'}\n';
|
|
71
|
+
}
|
|
72
|
+
postamble +=
|
|
73
|
+
'window.$RefreshReg$ = _prevRefreshReg;\n' +
|
|
74
|
+
'window.$RefreshSig$ = _prevRefreshSig;\n' +
|
|
75
|
+
'if (module.hot) {\n' +
|
|
76
|
+
' module.hot.accept();\n' +
|
|
77
|
+
'}\n';
|
|
78
|
+
// Offset source map to account for preamble lines
|
|
79
|
+
let sourceMap = result.sourceMap;
|
|
80
|
+
if (sourceMap) {
|
|
81
|
+
const preambleLines = countNewlines(preamble);
|
|
82
|
+
sourceMap = {
|
|
83
|
+
...sourceMap,
|
|
84
|
+
mappings: ";".repeat(preambleLines) + sourceMap.mappings,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { code: preamble + result.code + postamble, sourceMap };
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/** Pre-built React Refresh transformer wrapping the default TypeScript transformer */
|
|
92
|
+
export const reactRefreshTransformer = createReactRefreshTransformer(typescriptTransformer);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { transform } from "sucrase";
|
|
2
|
+
export const typescriptTransformer = {
|
|
3
|
+
transform({ src, filename }) {
|
|
4
|
+
const ext = filename.slice(filename.lastIndexOf("."));
|
|
5
|
+
const transforms = ["imports"];
|
|
6
|
+
if (ext === ".ts" || ext === ".tsx")
|
|
7
|
+
transforms.unshift("typescript");
|
|
8
|
+
if (ext === ".tsx" || ext === ".jsx")
|
|
9
|
+
transforms.push("jsx");
|
|
10
|
+
const result = transform(src, {
|
|
11
|
+
transforms,
|
|
12
|
+
filePath: filename,
|
|
13
|
+
sourceMapOptions: { compiledFilename: filename },
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
code: result.code,
|
|
17
|
+
sourceMap: result.sourceMap,
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
};
|