@teyik0/furin 0.1.0-alpha.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/dist/adapter/bun.d.ts +3 -0
- package/dist/build/client.d.ts +14 -0
- package/dist/build/compile-entry.d.ts +22 -0
- package/dist/build/entry-template.d.ts +13 -0
- package/dist/build/hydrate.d.ts +20 -0
- package/dist/build/index.d.ts +7 -0
- package/dist/build/index.js +2212 -0
- package/dist/build/route-types.d.ts +20 -0
- package/dist/build/scan-server.d.ts +8 -0
- package/dist/build/server-routes-entry.d.ts +22 -0
- package/dist/build/shared.d.ts +12 -0
- package/dist/build/types.d.ts +53 -0
- package/dist/cli/config.d.ts +9 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +2240 -0
- package/dist/client.d.ts +158 -0
- package/dist/client.js +20 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +23 -0
- package/dist/furin.d.ts +45 -0
- package/dist/furin.js +937 -0
- package/dist/internal.d.ts +18 -0
- package/dist/link.d.ts +119 -0
- package/dist/link.js +281 -0
- package/dist/plugin/index.d.ts +20 -0
- package/dist/plugin/index.js +1408 -0
- package/dist/plugin/transform-client.d.ts +9 -0
- package/dist/render/assemble.d.ts +13 -0
- package/dist/render/cache.d.ts +7 -0
- package/dist/render/element.d.ts +4 -0
- package/dist/render/index.d.ts +26 -0
- package/dist/render/loaders.d.ts +12 -0
- package/dist/render/shell.d.ts +17 -0
- package/dist/render/template.d.ts +4 -0
- package/dist/router.d.ts +32 -0
- package/dist/router.js +575 -0
- package/dist/runtime-env.d.ts +3 -0
- package/dist/tsconfig.dts.tsbuildinfo +1 -0
- package/dist/utils.d.ts +6 -0
- package/package.json +74 -0
- package/src/adapter/README.md +13 -0
- package/src/adapter/bun.ts +119 -0
- package/src/build/client.ts +110 -0
- package/src/build/compile-entry.ts +99 -0
- package/src/build/entry-template.ts +62 -0
- package/src/build/hydrate.ts +106 -0
- package/src/build/index.ts +120 -0
- package/src/build/route-types.ts +88 -0
- package/src/build/scan-server.ts +88 -0
- package/src/build/server-routes-entry.ts +38 -0
- package/src/build/shared.ts +80 -0
- package/src/build/types.ts +60 -0
- package/src/cli/config.ts +68 -0
- package/src/cli/index.ts +106 -0
- package/src/client.ts +237 -0
- package/src/config.ts +31 -0
- package/src/furin.ts +251 -0
- package/src/internal.ts +36 -0
- package/src/link.tsx +480 -0
- package/src/plugin/index.ts +80 -0
- package/src/plugin/transform-client.ts +372 -0
- package/src/render/assemble.ts +57 -0
- package/src/render/cache.ts +9 -0
- package/src/render/element.tsx +28 -0
- package/src/render/index.ts +312 -0
- package/src/render/loaders.ts +67 -0
- package/src/render/shell.ts +128 -0
- package/src/render/template.ts +54 -0
- package/src/router.ts +234 -0
- package/src/runtime-env.ts +6 -0
- package/src/utils.ts +68 -0
|
@@ -0,0 +1,2212 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/build/index.ts
|
|
3
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
4
|
+
import { join as join9, relative as relative3, resolve as resolve4 } from "path";
|
|
5
|
+
|
|
6
|
+
// src/adapter/bun.ts
|
|
7
|
+
import { existsSync as existsSync6, rmSync as rmSync2 } from "fs";
|
|
8
|
+
import { join as join7, resolve as resolve3 } from "path";
|
|
9
|
+
|
|
10
|
+
// src/build/client.ts
|
|
11
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
12
|
+
import { join as join4 } from "path";
|
|
13
|
+
|
|
14
|
+
// ../../node_modules/.bun/@jridgewell+sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.mjs
|
|
15
|
+
var comma = 44;
|
|
16
|
+
var semicolon = 59;
|
|
17
|
+
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
18
|
+
var intToChar = new Uint8Array(64);
|
|
19
|
+
var charToInt = new Uint8Array(128);
|
|
20
|
+
for (let i = 0;i < chars.length; i++) {
|
|
21
|
+
const c = chars.charCodeAt(i);
|
|
22
|
+
intToChar[i] = c;
|
|
23
|
+
charToInt[c] = i;
|
|
24
|
+
}
|
|
25
|
+
function encodeInteger(builder, num, relative) {
|
|
26
|
+
let delta = num - relative;
|
|
27
|
+
delta = delta < 0 ? -delta << 1 | 1 : delta << 1;
|
|
28
|
+
do {
|
|
29
|
+
let clamped = delta & 31;
|
|
30
|
+
delta >>>= 5;
|
|
31
|
+
if (delta > 0)
|
|
32
|
+
clamped |= 32;
|
|
33
|
+
builder.write(intToChar[clamped]);
|
|
34
|
+
} while (delta > 0);
|
|
35
|
+
return num;
|
|
36
|
+
}
|
|
37
|
+
var bufLength = 1024 * 16;
|
|
38
|
+
var td = typeof TextDecoder !== "undefined" ? /* @__PURE__ */ new TextDecoder : typeof Buffer !== "undefined" ? {
|
|
39
|
+
decode(buf) {
|
|
40
|
+
const out = Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
41
|
+
return out.toString();
|
|
42
|
+
}
|
|
43
|
+
} : {
|
|
44
|
+
decode(buf) {
|
|
45
|
+
let out = "";
|
|
46
|
+
for (let i = 0;i < buf.length; i++) {
|
|
47
|
+
out += String.fromCharCode(buf[i]);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var StringWriter = class {
|
|
53
|
+
constructor() {
|
|
54
|
+
this.pos = 0;
|
|
55
|
+
this.out = "";
|
|
56
|
+
this.buffer = new Uint8Array(bufLength);
|
|
57
|
+
}
|
|
58
|
+
write(v) {
|
|
59
|
+
const { buffer } = this;
|
|
60
|
+
buffer[this.pos++] = v;
|
|
61
|
+
if (this.pos === bufLength) {
|
|
62
|
+
this.out += td.decode(buffer);
|
|
63
|
+
this.pos = 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
flush() {
|
|
67
|
+
const { buffer, out, pos } = this;
|
|
68
|
+
return pos > 0 ? out + td.decode(buffer.subarray(0, pos)) : out;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function encode(decoded) {
|
|
72
|
+
const writer = new StringWriter;
|
|
73
|
+
let sourcesIndex = 0;
|
|
74
|
+
let sourceLine = 0;
|
|
75
|
+
let sourceColumn = 0;
|
|
76
|
+
let namesIndex = 0;
|
|
77
|
+
for (let i = 0;i < decoded.length; i++) {
|
|
78
|
+
const line = decoded[i];
|
|
79
|
+
if (i > 0)
|
|
80
|
+
writer.write(semicolon);
|
|
81
|
+
if (line.length === 0)
|
|
82
|
+
continue;
|
|
83
|
+
let genColumn = 0;
|
|
84
|
+
for (let j = 0;j < line.length; j++) {
|
|
85
|
+
const segment = line[j];
|
|
86
|
+
if (j > 0)
|
|
87
|
+
writer.write(comma);
|
|
88
|
+
genColumn = encodeInteger(writer, segment[0], genColumn);
|
|
89
|
+
if (segment.length === 1)
|
|
90
|
+
continue;
|
|
91
|
+
sourcesIndex = encodeInteger(writer, segment[1], sourcesIndex);
|
|
92
|
+
sourceLine = encodeInteger(writer, segment[2], sourceLine);
|
|
93
|
+
sourceColumn = encodeInteger(writer, segment[3], sourceColumn);
|
|
94
|
+
if (segment.length === 4)
|
|
95
|
+
continue;
|
|
96
|
+
namesIndex = encodeInteger(writer, segment[4], namesIndex);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return writer.flush();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ../../node_modules/.bun/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.mjs
|
|
103
|
+
class BitSet {
|
|
104
|
+
constructor(arg) {
|
|
105
|
+
this.bits = arg instanceof BitSet ? arg.bits.slice() : [];
|
|
106
|
+
}
|
|
107
|
+
add(n) {
|
|
108
|
+
this.bits[n >> 5] |= 1 << (n & 31);
|
|
109
|
+
}
|
|
110
|
+
has(n) {
|
|
111
|
+
return !!(this.bits[n >> 5] & 1 << (n & 31));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class Chunk {
|
|
116
|
+
constructor(start, end, content) {
|
|
117
|
+
this.start = start;
|
|
118
|
+
this.end = end;
|
|
119
|
+
this.original = content;
|
|
120
|
+
this.intro = "";
|
|
121
|
+
this.outro = "";
|
|
122
|
+
this.content = content;
|
|
123
|
+
this.storeName = false;
|
|
124
|
+
this.edited = false;
|
|
125
|
+
{
|
|
126
|
+
this.previous = null;
|
|
127
|
+
this.next = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
appendLeft(content) {
|
|
131
|
+
this.outro += content;
|
|
132
|
+
}
|
|
133
|
+
appendRight(content) {
|
|
134
|
+
this.intro = this.intro + content;
|
|
135
|
+
}
|
|
136
|
+
clone() {
|
|
137
|
+
const chunk = new Chunk(this.start, this.end, this.original);
|
|
138
|
+
chunk.intro = this.intro;
|
|
139
|
+
chunk.outro = this.outro;
|
|
140
|
+
chunk.content = this.content;
|
|
141
|
+
chunk.storeName = this.storeName;
|
|
142
|
+
chunk.edited = this.edited;
|
|
143
|
+
return chunk;
|
|
144
|
+
}
|
|
145
|
+
contains(index) {
|
|
146
|
+
return this.start < index && index < this.end;
|
|
147
|
+
}
|
|
148
|
+
eachNext(fn) {
|
|
149
|
+
let chunk = this;
|
|
150
|
+
while (chunk) {
|
|
151
|
+
fn(chunk);
|
|
152
|
+
chunk = chunk.next;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
eachPrevious(fn) {
|
|
156
|
+
let chunk = this;
|
|
157
|
+
while (chunk) {
|
|
158
|
+
fn(chunk);
|
|
159
|
+
chunk = chunk.previous;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
edit(content, storeName, contentOnly) {
|
|
163
|
+
this.content = content;
|
|
164
|
+
if (!contentOnly) {
|
|
165
|
+
this.intro = "";
|
|
166
|
+
this.outro = "";
|
|
167
|
+
}
|
|
168
|
+
this.storeName = storeName;
|
|
169
|
+
this.edited = true;
|
|
170
|
+
return this;
|
|
171
|
+
}
|
|
172
|
+
prependLeft(content) {
|
|
173
|
+
this.outro = content + this.outro;
|
|
174
|
+
}
|
|
175
|
+
prependRight(content) {
|
|
176
|
+
this.intro = content + this.intro;
|
|
177
|
+
}
|
|
178
|
+
reset() {
|
|
179
|
+
this.intro = "";
|
|
180
|
+
this.outro = "";
|
|
181
|
+
if (this.edited) {
|
|
182
|
+
this.content = this.original;
|
|
183
|
+
this.storeName = false;
|
|
184
|
+
this.edited = false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
split(index) {
|
|
188
|
+
const sliceIndex = index - this.start;
|
|
189
|
+
const originalBefore = this.original.slice(0, sliceIndex);
|
|
190
|
+
const originalAfter = this.original.slice(sliceIndex);
|
|
191
|
+
this.original = originalBefore;
|
|
192
|
+
const newChunk = new Chunk(index, this.end, originalAfter);
|
|
193
|
+
newChunk.outro = this.outro;
|
|
194
|
+
this.outro = "";
|
|
195
|
+
this.end = index;
|
|
196
|
+
if (this.edited) {
|
|
197
|
+
newChunk.edit("", false);
|
|
198
|
+
this.content = "";
|
|
199
|
+
} else {
|
|
200
|
+
this.content = originalBefore;
|
|
201
|
+
}
|
|
202
|
+
newChunk.next = this.next;
|
|
203
|
+
if (newChunk.next)
|
|
204
|
+
newChunk.next.previous = newChunk;
|
|
205
|
+
newChunk.previous = this;
|
|
206
|
+
this.next = newChunk;
|
|
207
|
+
return newChunk;
|
|
208
|
+
}
|
|
209
|
+
toString() {
|
|
210
|
+
return this.intro + this.content + this.outro;
|
|
211
|
+
}
|
|
212
|
+
trimEnd(rx) {
|
|
213
|
+
this.outro = this.outro.replace(rx, "");
|
|
214
|
+
if (this.outro.length)
|
|
215
|
+
return true;
|
|
216
|
+
const trimmed = this.content.replace(rx, "");
|
|
217
|
+
if (trimmed.length) {
|
|
218
|
+
if (trimmed !== this.content) {
|
|
219
|
+
this.split(this.start + trimmed.length).edit("", undefined, true);
|
|
220
|
+
if (this.edited) {
|
|
221
|
+
this.edit(trimmed, this.storeName, true);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
} else {
|
|
226
|
+
this.edit("", undefined, true);
|
|
227
|
+
this.intro = this.intro.replace(rx, "");
|
|
228
|
+
if (this.intro.length)
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
trimStart(rx) {
|
|
233
|
+
this.intro = this.intro.replace(rx, "");
|
|
234
|
+
if (this.intro.length)
|
|
235
|
+
return true;
|
|
236
|
+
const trimmed = this.content.replace(rx, "");
|
|
237
|
+
if (trimmed.length) {
|
|
238
|
+
if (trimmed !== this.content) {
|
|
239
|
+
const newChunk = this.split(this.end - trimmed.length);
|
|
240
|
+
if (this.edited) {
|
|
241
|
+
newChunk.edit(trimmed, this.storeName, true);
|
|
242
|
+
}
|
|
243
|
+
this.edit("", undefined, true);
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
} else {
|
|
247
|
+
this.edit("", undefined, true);
|
|
248
|
+
this.outro = this.outro.replace(rx, "");
|
|
249
|
+
if (this.outro.length)
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function getBtoa() {
|
|
255
|
+
if (typeof globalThis !== "undefined" && typeof globalThis.btoa === "function") {
|
|
256
|
+
return (str) => globalThis.btoa(unescape(encodeURIComponent(str)));
|
|
257
|
+
} else if (typeof Buffer === "function") {
|
|
258
|
+
return (str) => Buffer.from(str, "utf-8").toString("base64");
|
|
259
|
+
} else {
|
|
260
|
+
return () => {
|
|
261
|
+
throw new Error("Unsupported environment: `window.btoa` or `Buffer` should be supported.");
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
var btoa = /* @__PURE__ */ getBtoa();
|
|
266
|
+
|
|
267
|
+
class SourceMap {
|
|
268
|
+
constructor(properties) {
|
|
269
|
+
this.version = 3;
|
|
270
|
+
this.file = properties.file;
|
|
271
|
+
this.sources = properties.sources;
|
|
272
|
+
this.sourcesContent = properties.sourcesContent;
|
|
273
|
+
this.names = properties.names;
|
|
274
|
+
this.mappings = encode(properties.mappings);
|
|
275
|
+
if (typeof properties.x_google_ignoreList !== "undefined") {
|
|
276
|
+
this.x_google_ignoreList = properties.x_google_ignoreList;
|
|
277
|
+
}
|
|
278
|
+
if (typeof properties.debugId !== "undefined") {
|
|
279
|
+
this.debugId = properties.debugId;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
toString() {
|
|
283
|
+
return JSON.stringify(this);
|
|
284
|
+
}
|
|
285
|
+
toUrl() {
|
|
286
|
+
return "data:application/json;charset=utf-8;base64," + btoa(this.toString());
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function guessIndent(code) {
|
|
290
|
+
const lines = code.split(`
|
|
291
|
+
`);
|
|
292
|
+
const tabbed = lines.filter((line) => /^\t+/.test(line));
|
|
293
|
+
const spaced = lines.filter((line) => /^ {2,}/.test(line));
|
|
294
|
+
if (tabbed.length === 0 && spaced.length === 0) {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
if (tabbed.length >= spaced.length) {
|
|
298
|
+
return "\t";
|
|
299
|
+
}
|
|
300
|
+
const min = spaced.reduce((previous, current) => {
|
|
301
|
+
const numSpaces = /^ +/.exec(current)[0].length;
|
|
302
|
+
return Math.min(numSpaces, previous);
|
|
303
|
+
}, Infinity);
|
|
304
|
+
return new Array(min + 1).join(" ");
|
|
305
|
+
}
|
|
306
|
+
function getRelativePath(from, to) {
|
|
307
|
+
const fromParts = from.split(/[/\\]/);
|
|
308
|
+
const toParts = to.split(/[/\\]/);
|
|
309
|
+
fromParts.pop();
|
|
310
|
+
while (fromParts[0] === toParts[0]) {
|
|
311
|
+
fromParts.shift();
|
|
312
|
+
toParts.shift();
|
|
313
|
+
}
|
|
314
|
+
if (fromParts.length) {
|
|
315
|
+
let i = fromParts.length;
|
|
316
|
+
while (i--)
|
|
317
|
+
fromParts[i] = "..";
|
|
318
|
+
}
|
|
319
|
+
return fromParts.concat(toParts).join("/");
|
|
320
|
+
}
|
|
321
|
+
var toString = Object.prototype.toString;
|
|
322
|
+
function isObject(thing) {
|
|
323
|
+
return toString.call(thing) === "[object Object]";
|
|
324
|
+
}
|
|
325
|
+
function getLocator(source) {
|
|
326
|
+
const originalLines = source.split(`
|
|
327
|
+
`);
|
|
328
|
+
const lineOffsets = [];
|
|
329
|
+
for (let i = 0, pos = 0;i < originalLines.length; i++) {
|
|
330
|
+
lineOffsets.push(pos);
|
|
331
|
+
pos += originalLines[i].length + 1;
|
|
332
|
+
}
|
|
333
|
+
return function locate(index) {
|
|
334
|
+
let i = 0;
|
|
335
|
+
let j = lineOffsets.length;
|
|
336
|
+
while (i < j) {
|
|
337
|
+
const m = i + j >> 1;
|
|
338
|
+
if (index < lineOffsets[m]) {
|
|
339
|
+
j = m;
|
|
340
|
+
} else {
|
|
341
|
+
i = m + 1;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const line = i - 1;
|
|
345
|
+
const column = index - lineOffsets[line];
|
|
346
|
+
return { line, column };
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
var wordRegex = /\w/;
|
|
350
|
+
|
|
351
|
+
class Mappings {
|
|
352
|
+
constructor(hires) {
|
|
353
|
+
this.hires = hires;
|
|
354
|
+
this.generatedCodeLine = 0;
|
|
355
|
+
this.generatedCodeColumn = 0;
|
|
356
|
+
this.raw = [];
|
|
357
|
+
this.rawSegments = this.raw[this.generatedCodeLine] = [];
|
|
358
|
+
this.pending = null;
|
|
359
|
+
}
|
|
360
|
+
addEdit(sourceIndex, content, loc, nameIndex) {
|
|
361
|
+
if (content.length) {
|
|
362
|
+
const contentLengthMinusOne = content.length - 1;
|
|
363
|
+
let contentLineEnd = content.indexOf(`
|
|
364
|
+
`, 0);
|
|
365
|
+
let previousContentLineEnd = -1;
|
|
366
|
+
while (contentLineEnd >= 0 && contentLengthMinusOne > contentLineEnd) {
|
|
367
|
+
const segment2 = [this.generatedCodeColumn, sourceIndex, loc.line, loc.column];
|
|
368
|
+
if (nameIndex >= 0) {
|
|
369
|
+
segment2.push(nameIndex);
|
|
370
|
+
}
|
|
371
|
+
this.rawSegments.push(segment2);
|
|
372
|
+
this.generatedCodeLine += 1;
|
|
373
|
+
this.raw[this.generatedCodeLine] = this.rawSegments = [];
|
|
374
|
+
this.generatedCodeColumn = 0;
|
|
375
|
+
previousContentLineEnd = contentLineEnd;
|
|
376
|
+
contentLineEnd = content.indexOf(`
|
|
377
|
+
`, contentLineEnd + 1);
|
|
378
|
+
}
|
|
379
|
+
const segment = [this.generatedCodeColumn, sourceIndex, loc.line, loc.column];
|
|
380
|
+
if (nameIndex >= 0) {
|
|
381
|
+
segment.push(nameIndex);
|
|
382
|
+
}
|
|
383
|
+
this.rawSegments.push(segment);
|
|
384
|
+
this.advance(content.slice(previousContentLineEnd + 1));
|
|
385
|
+
} else if (this.pending) {
|
|
386
|
+
this.rawSegments.push(this.pending);
|
|
387
|
+
this.advance(content);
|
|
388
|
+
}
|
|
389
|
+
this.pending = null;
|
|
390
|
+
}
|
|
391
|
+
addUneditedChunk(sourceIndex, chunk, original, loc, sourcemapLocations) {
|
|
392
|
+
let originalCharIndex = chunk.start;
|
|
393
|
+
let first = true;
|
|
394
|
+
let charInHiresBoundary = false;
|
|
395
|
+
while (originalCharIndex < chunk.end) {
|
|
396
|
+
if (original[originalCharIndex] === `
|
|
397
|
+
`) {
|
|
398
|
+
loc.line += 1;
|
|
399
|
+
loc.column = 0;
|
|
400
|
+
this.generatedCodeLine += 1;
|
|
401
|
+
this.raw[this.generatedCodeLine] = this.rawSegments = [];
|
|
402
|
+
this.generatedCodeColumn = 0;
|
|
403
|
+
first = true;
|
|
404
|
+
charInHiresBoundary = false;
|
|
405
|
+
} else {
|
|
406
|
+
if (this.hires || first || sourcemapLocations.has(originalCharIndex)) {
|
|
407
|
+
const segment = [this.generatedCodeColumn, sourceIndex, loc.line, loc.column];
|
|
408
|
+
if (this.hires === "boundary") {
|
|
409
|
+
if (wordRegex.test(original[originalCharIndex])) {
|
|
410
|
+
if (!charInHiresBoundary) {
|
|
411
|
+
this.rawSegments.push(segment);
|
|
412
|
+
charInHiresBoundary = true;
|
|
413
|
+
}
|
|
414
|
+
} else {
|
|
415
|
+
this.rawSegments.push(segment);
|
|
416
|
+
charInHiresBoundary = false;
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
this.rawSegments.push(segment);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
loc.column += 1;
|
|
423
|
+
this.generatedCodeColumn += 1;
|
|
424
|
+
first = false;
|
|
425
|
+
}
|
|
426
|
+
originalCharIndex += 1;
|
|
427
|
+
}
|
|
428
|
+
this.pending = null;
|
|
429
|
+
}
|
|
430
|
+
advance(str) {
|
|
431
|
+
if (!str)
|
|
432
|
+
return;
|
|
433
|
+
const lines = str.split(`
|
|
434
|
+
`);
|
|
435
|
+
if (lines.length > 1) {
|
|
436
|
+
for (let i = 0;i < lines.length - 1; i++) {
|
|
437
|
+
this.generatedCodeLine++;
|
|
438
|
+
this.raw[this.generatedCodeLine] = this.rawSegments = [];
|
|
439
|
+
}
|
|
440
|
+
this.generatedCodeColumn = 0;
|
|
441
|
+
}
|
|
442
|
+
this.generatedCodeColumn += lines[lines.length - 1].length;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
var n = `
|
|
446
|
+
`;
|
|
447
|
+
var warned = {
|
|
448
|
+
insertLeft: false,
|
|
449
|
+
insertRight: false,
|
|
450
|
+
storeName: false
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
class MagicString {
|
|
454
|
+
constructor(string, options = {}) {
|
|
455
|
+
const chunk = new Chunk(0, string.length, string);
|
|
456
|
+
Object.defineProperties(this, {
|
|
457
|
+
original: { writable: true, value: string },
|
|
458
|
+
outro: { writable: true, value: "" },
|
|
459
|
+
intro: { writable: true, value: "" },
|
|
460
|
+
firstChunk: { writable: true, value: chunk },
|
|
461
|
+
lastChunk: { writable: true, value: chunk },
|
|
462
|
+
lastSearchedChunk: { writable: true, value: chunk },
|
|
463
|
+
byStart: { writable: true, value: {} },
|
|
464
|
+
byEnd: { writable: true, value: {} },
|
|
465
|
+
filename: { writable: true, value: options.filename },
|
|
466
|
+
indentExclusionRanges: { writable: true, value: options.indentExclusionRanges },
|
|
467
|
+
sourcemapLocations: { writable: true, value: new BitSet },
|
|
468
|
+
storedNames: { writable: true, value: {} },
|
|
469
|
+
indentStr: { writable: true, value: undefined },
|
|
470
|
+
ignoreList: { writable: true, value: options.ignoreList },
|
|
471
|
+
offset: { writable: true, value: options.offset || 0 }
|
|
472
|
+
});
|
|
473
|
+
this.byStart[0] = chunk;
|
|
474
|
+
this.byEnd[string.length] = chunk;
|
|
475
|
+
}
|
|
476
|
+
addSourcemapLocation(char) {
|
|
477
|
+
this.sourcemapLocations.add(char);
|
|
478
|
+
}
|
|
479
|
+
append(content) {
|
|
480
|
+
if (typeof content !== "string")
|
|
481
|
+
throw new TypeError("outro content must be a string");
|
|
482
|
+
this.outro += content;
|
|
483
|
+
return this;
|
|
484
|
+
}
|
|
485
|
+
appendLeft(index, content) {
|
|
486
|
+
index = index + this.offset;
|
|
487
|
+
if (typeof content !== "string")
|
|
488
|
+
throw new TypeError("inserted content must be a string");
|
|
489
|
+
this._split(index);
|
|
490
|
+
const chunk = this.byEnd[index];
|
|
491
|
+
if (chunk) {
|
|
492
|
+
chunk.appendLeft(content);
|
|
493
|
+
} else {
|
|
494
|
+
this.intro += content;
|
|
495
|
+
}
|
|
496
|
+
return this;
|
|
497
|
+
}
|
|
498
|
+
appendRight(index, content) {
|
|
499
|
+
index = index + this.offset;
|
|
500
|
+
if (typeof content !== "string")
|
|
501
|
+
throw new TypeError("inserted content must be a string");
|
|
502
|
+
this._split(index);
|
|
503
|
+
const chunk = this.byStart[index];
|
|
504
|
+
if (chunk) {
|
|
505
|
+
chunk.appendRight(content);
|
|
506
|
+
} else {
|
|
507
|
+
this.outro += content;
|
|
508
|
+
}
|
|
509
|
+
return this;
|
|
510
|
+
}
|
|
511
|
+
clone() {
|
|
512
|
+
const cloned = new MagicString(this.original, { filename: this.filename, offset: this.offset });
|
|
513
|
+
let originalChunk = this.firstChunk;
|
|
514
|
+
let clonedChunk = cloned.firstChunk = cloned.lastSearchedChunk = originalChunk.clone();
|
|
515
|
+
while (originalChunk) {
|
|
516
|
+
cloned.byStart[clonedChunk.start] = clonedChunk;
|
|
517
|
+
cloned.byEnd[clonedChunk.end] = clonedChunk;
|
|
518
|
+
const nextOriginalChunk = originalChunk.next;
|
|
519
|
+
const nextClonedChunk = nextOriginalChunk && nextOriginalChunk.clone();
|
|
520
|
+
if (nextClonedChunk) {
|
|
521
|
+
clonedChunk.next = nextClonedChunk;
|
|
522
|
+
nextClonedChunk.previous = clonedChunk;
|
|
523
|
+
clonedChunk = nextClonedChunk;
|
|
524
|
+
}
|
|
525
|
+
originalChunk = nextOriginalChunk;
|
|
526
|
+
}
|
|
527
|
+
cloned.lastChunk = clonedChunk;
|
|
528
|
+
if (this.indentExclusionRanges) {
|
|
529
|
+
cloned.indentExclusionRanges = this.indentExclusionRanges.slice();
|
|
530
|
+
}
|
|
531
|
+
cloned.sourcemapLocations = new BitSet(this.sourcemapLocations);
|
|
532
|
+
cloned.intro = this.intro;
|
|
533
|
+
cloned.outro = this.outro;
|
|
534
|
+
return cloned;
|
|
535
|
+
}
|
|
536
|
+
generateDecodedMap(options) {
|
|
537
|
+
options = options || {};
|
|
538
|
+
const sourceIndex = 0;
|
|
539
|
+
const names = Object.keys(this.storedNames);
|
|
540
|
+
const mappings = new Mappings(options.hires);
|
|
541
|
+
const locate = getLocator(this.original);
|
|
542
|
+
if (this.intro) {
|
|
543
|
+
mappings.advance(this.intro);
|
|
544
|
+
}
|
|
545
|
+
this.firstChunk.eachNext((chunk) => {
|
|
546
|
+
const loc = locate(chunk.start);
|
|
547
|
+
if (chunk.intro.length)
|
|
548
|
+
mappings.advance(chunk.intro);
|
|
549
|
+
if (chunk.edited) {
|
|
550
|
+
mappings.addEdit(sourceIndex, chunk.content, loc, chunk.storeName ? names.indexOf(chunk.original) : -1);
|
|
551
|
+
} else {
|
|
552
|
+
mappings.addUneditedChunk(sourceIndex, chunk, this.original, loc, this.sourcemapLocations);
|
|
553
|
+
}
|
|
554
|
+
if (chunk.outro.length)
|
|
555
|
+
mappings.advance(chunk.outro);
|
|
556
|
+
});
|
|
557
|
+
if (this.outro) {
|
|
558
|
+
mappings.advance(this.outro);
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
file: options.file ? options.file.split(/[/\\]/).pop() : undefined,
|
|
562
|
+
sources: [
|
|
563
|
+
options.source ? getRelativePath(options.file || "", options.source) : options.file || ""
|
|
564
|
+
],
|
|
565
|
+
sourcesContent: options.includeContent ? [this.original] : undefined,
|
|
566
|
+
names,
|
|
567
|
+
mappings: mappings.raw,
|
|
568
|
+
x_google_ignoreList: this.ignoreList ? [sourceIndex] : undefined
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
generateMap(options) {
|
|
572
|
+
return new SourceMap(this.generateDecodedMap(options));
|
|
573
|
+
}
|
|
574
|
+
_ensureindentStr() {
|
|
575
|
+
if (this.indentStr === undefined) {
|
|
576
|
+
this.indentStr = guessIndent(this.original);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
_getRawIndentString() {
|
|
580
|
+
this._ensureindentStr();
|
|
581
|
+
return this.indentStr;
|
|
582
|
+
}
|
|
583
|
+
getIndentString() {
|
|
584
|
+
this._ensureindentStr();
|
|
585
|
+
return this.indentStr === null ? "\t" : this.indentStr;
|
|
586
|
+
}
|
|
587
|
+
indent(indentStr, options) {
|
|
588
|
+
const pattern = /^[^\r\n]/gm;
|
|
589
|
+
if (isObject(indentStr)) {
|
|
590
|
+
options = indentStr;
|
|
591
|
+
indentStr = undefined;
|
|
592
|
+
}
|
|
593
|
+
if (indentStr === undefined) {
|
|
594
|
+
this._ensureindentStr();
|
|
595
|
+
indentStr = this.indentStr || "\t";
|
|
596
|
+
}
|
|
597
|
+
if (indentStr === "")
|
|
598
|
+
return this;
|
|
599
|
+
options = options || {};
|
|
600
|
+
const isExcluded = {};
|
|
601
|
+
if (options.exclude) {
|
|
602
|
+
const exclusions = typeof options.exclude[0] === "number" ? [options.exclude] : options.exclude;
|
|
603
|
+
exclusions.forEach((exclusion) => {
|
|
604
|
+
for (let i = exclusion[0];i < exclusion[1]; i += 1) {
|
|
605
|
+
isExcluded[i] = true;
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
let shouldIndentNextCharacter = options.indentStart !== false;
|
|
610
|
+
const replacer = (match) => {
|
|
611
|
+
if (shouldIndentNextCharacter)
|
|
612
|
+
return `${indentStr}${match}`;
|
|
613
|
+
shouldIndentNextCharacter = true;
|
|
614
|
+
return match;
|
|
615
|
+
};
|
|
616
|
+
this.intro = this.intro.replace(pattern, replacer);
|
|
617
|
+
let charIndex = 0;
|
|
618
|
+
let chunk = this.firstChunk;
|
|
619
|
+
while (chunk) {
|
|
620
|
+
const end = chunk.end;
|
|
621
|
+
if (chunk.edited) {
|
|
622
|
+
if (!isExcluded[charIndex]) {
|
|
623
|
+
chunk.content = chunk.content.replace(pattern, replacer);
|
|
624
|
+
if (chunk.content.length) {
|
|
625
|
+
shouldIndentNextCharacter = chunk.content[chunk.content.length - 1] === `
|
|
626
|
+
`;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
} else {
|
|
630
|
+
charIndex = chunk.start;
|
|
631
|
+
while (charIndex < end) {
|
|
632
|
+
if (!isExcluded[charIndex]) {
|
|
633
|
+
const char = this.original[charIndex];
|
|
634
|
+
if (char === `
|
|
635
|
+
`) {
|
|
636
|
+
shouldIndentNextCharacter = true;
|
|
637
|
+
} else if (char !== "\r" && shouldIndentNextCharacter) {
|
|
638
|
+
shouldIndentNextCharacter = false;
|
|
639
|
+
if (charIndex === chunk.start) {
|
|
640
|
+
chunk.prependRight(indentStr);
|
|
641
|
+
} else {
|
|
642
|
+
this._splitChunk(chunk, charIndex);
|
|
643
|
+
chunk = chunk.next;
|
|
644
|
+
chunk.prependRight(indentStr);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
charIndex += 1;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
charIndex = chunk.end;
|
|
652
|
+
chunk = chunk.next;
|
|
653
|
+
}
|
|
654
|
+
this.outro = this.outro.replace(pattern, replacer);
|
|
655
|
+
return this;
|
|
656
|
+
}
|
|
657
|
+
insert() {
|
|
658
|
+
throw new Error("magicString.insert(...) is deprecated. Use prependRight(...) or appendLeft(...)");
|
|
659
|
+
}
|
|
660
|
+
insertLeft(index, content) {
|
|
661
|
+
if (!warned.insertLeft) {
|
|
662
|
+
console.warn("magicString.insertLeft(...) is deprecated. Use magicString.appendLeft(...) instead");
|
|
663
|
+
warned.insertLeft = true;
|
|
664
|
+
}
|
|
665
|
+
return this.appendLeft(index, content);
|
|
666
|
+
}
|
|
667
|
+
insertRight(index, content) {
|
|
668
|
+
if (!warned.insertRight) {
|
|
669
|
+
console.warn("magicString.insertRight(...) is deprecated. Use magicString.prependRight(...) instead");
|
|
670
|
+
warned.insertRight = true;
|
|
671
|
+
}
|
|
672
|
+
return this.prependRight(index, content);
|
|
673
|
+
}
|
|
674
|
+
move(start, end, index) {
|
|
675
|
+
start = start + this.offset;
|
|
676
|
+
end = end + this.offset;
|
|
677
|
+
index = index + this.offset;
|
|
678
|
+
if (index >= start && index <= end)
|
|
679
|
+
throw new Error("Cannot move a selection inside itself");
|
|
680
|
+
this._split(start);
|
|
681
|
+
this._split(end);
|
|
682
|
+
this._split(index);
|
|
683
|
+
const first = this.byStart[start];
|
|
684
|
+
const last = this.byEnd[end];
|
|
685
|
+
const oldLeft = first.previous;
|
|
686
|
+
const oldRight = last.next;
|
|
687
|
+
const newRight = this.byStart[index];
|
|
688
|
+
if (!newRight && last === this.lastChunk)
|
|
689
|
+
return this;
|
|
690
|
+
const newLeft = newRight ? newRight.previous : this.lastChunk;
|
|
691
|
+
if (oldLeft)
|
|
692
|
+
oldLeft.next = oldRight;
|
|
693
|
+
if (oldRight)
|
|
694
|
+
oldRight.previous = oldLeft;
|
|
695
|
+
if (newLeft)
|
|
696
|
+
newLeft.next = first;
|
|
697
|
+
if (newRight)
|
|
698
|
+
newRight.previous = last;
|
|
699
|
+
if (!first.previous)
|
|
700
|
+
this.firstChunk = last.next;
|
|
701
|
+
if (!last.next) {
|
|
702
|
+
this.lastChunk = first.previous;
|
|
703
|
+
this.lastChunk.next = null;
|
|
704
|
+
}
|
|
705
|
+
first.previous = newLeft;
|
|
706
|
+
last.next = newRight || null;
|
|
707
|
+
if (!newLeft)
|
|
708
|
+
this.firstChunk = first;
|
|
709
|
+
if (!newRight)
|
|
710
|
+
this.lastChunk = last;
|
|
711
|
+
return this;
|
|
712
|
+
}
|
|
713
|
+
overwrite(start, end, content, options) {
|
|
714
|
+
options = options || {};
|
|
715
|
+
return this.update(start, end, content, { ...options, overwrite: !options.contentOnly });
|
|
716
|
+
}
|
|
717
|
+
update(start, end, content, options) {
|
|
718
|
+
start = start + this.offset;
|
|
719
|
+
end = end + this.offset;
|
|
720
|
+
if (typeof content !== "string")
|
|
721
|
+
throw new TypeError("replacement content must be a string");
|
|
722
|
+
if (this.original.length !== 0) {
|
|
723
|
+
while (start < 0)
|
|
724
|
+
start += this.original.length;
|
|
725
|
+
while (end < 0)
|
|
726
|
+
end += this.original.length;
|
|
727
|
+
}
|
|
728
|
+
if (end > this.original.length)
|
|
729
|
+
throw new Error("end is out of bounds");
|
|
730
|
+
if (start === end)
|
|
731
|
+
throw new Error("Cannot overwrite a zero-length range \u2013 use appendLeft or prependRight instead");
|
|
732
|
+
this._split(start);
|
|
733
|
+
this._split(end);
|
|
734
|
+
if (options === true) {
|
|
735
|
+
if (!warned.storeName) {
|
|
736
|
+
console.warn("The final argument to magicString.overwrite(...) should be an options object. See https://github.com/rich-harris/magic-string");
|
|
737
|
+
warned.storeName = true;
|
|
738
|
+
}
|
|
739
|
+
options = { storeName: true };
|
|
740
|
+
}
|
|
741
|
+
const storeName = options !== undefined ? options.storeName : false;
|
|
742
|
+
const overwrite = options !== undefined ? options.overwrite : false;
|
|
743
|
+
if (storeName) {
|
|
744
|
+
const original = this.original.slice(start, end);
|
|
745
|
+
Object.defineProperty(this.storedNames, original, {
|
|
746
|
+
writable: true,
|
|
747
|
+
value: true,
|
|
748
|
+
enumerable: true
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
const first = this.byStart[start];
|
|
752
|
+
const last = this.byEnd[end];
|
|
753
|
+
if (first) {
|
|
754
|
+
let chunk = first;
|
|
755
|
+
while (chunk !== last) {
|
|
756
|
+
if (chunk.next !== this.byStart[chunk.end]) {
|
|
757
|
+
throw new Error("Cannot overwrite across a split point");
|
|
758
|
+
}
|
|
759
|
+
chunk = chunk.next;
|
|
760
|
+
chunk.edit("", false);
|
|
761
|
+
}
|
|
762
|
+
first.edit(content, storeName, !overwrite);
|
|
763
|
+
} else {
|
|
764
|
+
const newChunk = new Chunk(start, end, "").edit(content, storeName);
|
|
765
|
+
last.next = newChunk;
|
|
766
|
+
newChunk.previous = last;
|
|
767
|
+
}
|
|
768
|
+
return this;
|
|
769
|
+
}
|
|
770
|
+
prepend(content) {
|
|
771
|
+
if (typeof content !== "string")
|
|
772
|
+
throw new TypeError("outro content must be a string");
|
|
773
|
+
this.intro = content + this.intro;
|
|
774
|
+
return this;
|
|
775
|
+
}
|
|
776
|
+
prependLeft(index, content) {
|
|
777
|
+
index = index + this.offset;
|
|
778
|
+
if (typeof content !== "string")
|
|
779
|
+
throw new TypeError("inserted content must be a string");
|
|
780
|
+
this._split(index);
|
|
781
|
+
const chunk = this.byEnd[index];
|
|
782
|
+
if (chunk) {
|
|
783
|
+
chunk.prependLeft(content);
|
|
784
|
+
} else {
|
|
785
|
+
this.intro = content + this.intro;
|
|
786
|
+
}
|
|
787
|
+
return this;
|
|
788
|
+
}
|
|
789
|
+
prependRight(index, content) {
|
|
790
|
+
index = index + this.offset;
|
|
791
|
+
if (typeof content !== "string")
|
|
792
|
+
throw new TypeError("inserted content must be a string");
|
|
793
|
+
this._split(index);
|
|
794
|
+
const chunk = this.byStart[index];
|
|
795
|
+
if (chunk) {
|
|
796
|
+
chunk.prependRight(content);
|
|
797
|
+
} else {
|
|
798
|
+
this.outro = content + this.outro;
|
|
799
|
+
}
|
|
800
|
+
return this;
|
|
801
|
+
}
|
|
802
|
+
remove(start, end) {
|
|
803
|
+
start = start + this.offset;
|
|
804
|
+
end = end + this.offset;
|
|
805
|
+
if (this.original.length !== 0) {
|
|
806
|
+
while (start < 0)
|
|
807
|
+
start += this.original.length;
|
|
808
|
+
while (end < 0)
|
|
809
|
+
end += this.original.length;
|
|
810
|
+
}
|
|
811
|
+
if (start === end)
|
|
812
|
+
return this;
|
|
813
|
+
if (start < 0 || end > this.original.length)
|
|
814
|
+
throw new Error("Character is out of bounds");
|
|
815
|
+
if (start > end)
|
|
816
|
+
throw new Error("end must be greater than start");
|
|
817
|
+
this._split(start);
|
|
818
|
+
this._split(end);
|
|
819
|
+
let chunk = this.byStart[start];
|
|
820
|
+
while (chunk) {
|
|
821
|
+
chunk.intro = "";
|
|
822
|
+
chunk.outro = "";
|
|
823
|
+
chunk.edit("");
|
|
824
|
+
chunk = end > chunk.end ? this.byStart[chunk.end] : null;
|
|
825
|
+
}
|
|
826
|
+
return this;
|
|
827
|
+
}
|
|
828
|
+
reset(start, end) {
|
|
829
|
+
start = start + this.offset;
|
|
830
|
+
end = end + this.offset;
|
|
831
|
+
if (this.original.length !== 0) {
|
|
832
|
+
while (start < 0)
|
|
833
|
+
start += this.original.length;
|
|
834
|
+
while (end < 0)
|
|
835
|
+
end += this.original.length;
|
|
836
|
+
}
|
|
837
|
+
if (start === end)
|
|
838
|
+
return this;
|
|
839
|
+
if (start < 0 || end > this.original.length)
|
|
840
|
+
throw new Error("Character is out of bounds");
|
|
841
|
+
if (start > end)
|
|
842
|
+
throw new Error("end must be greater than start");
|
|
843
|
+
this._split(start);
|
|
844
|
+
this._split(end);
|
|
845
|
+
let chunk = this.byStart[start];
|
|
846
|
+
while (chunk) {
|
|
847
|
+
chunk.reset();
|
|
848
|
+
chunk = end > chunk.end ? this.byStart[chunk.end] : null;
|
|
849
|
+
}
|
|
850
|
+
return this;
|
|
851
|
+
}
|
|
852
|
+
lastChar() {
|
|
853
|
+
if (this.outro.length)
|
|
854
|
+
return this.outro[this.outro.length - 1];
|
|
855
|
+
let chunk = this.lastChunk;
|
|
856
|
+
do {
|
|
857
|
+
if (chunk.outro.length)
|
|
858
|
+
return chunk.outro[chunk.outro.length - 1];
|
|
859
|
+
if (chunk.content.length)
|
|
860
|
+
return chunk.content[chunk.content.length - 1];
|
|
861
|
+
if (chunk.intro.length)
|
|
862
|
+
return chunk.intro[chunk.intro.length - 1];
|
|
863
|
+
} while (chunk = chunk.previous);
|
|
864
|
+
if (this.intro.length)
|
|
865
|
+
return this.intro[this.intro.length - 1];
|
|
866
|
+
return "";
|
|
867
|
+
}
|
|
868
|
+
lastLine() {
|
|
869
|
+
let lineIndex = this.outro.lastIndexOf(n);
|
|
870
|
+
if (lineIndex !== -1)
|
|
871
|
+
return this.outro.substr(lineIndex + 1);
|
|
872
|
+
let lineStr = this.outro;
|
|
873
|
+
let chunk = this.lastChunk;
|
|
874
|
+
do {
|
|
875
|
+
if (chunk.outro.length > 0) {
|
|
876
|
+
lineIndex = chunk.outro.lastIndexOf(n);
|
|
877
|
+
if (lineIndex !== -1)
|
|
878
|
+
return chunk.outro.substr(lineIndex + 1) + lineStr;
|
|
879
|
+
lineStr = chunk.outro + lineStr;
|
|
880
|
+
}
|
|
881
|
+
if (chunk.content.length > 0) {
|
|
882
|
+
lineIndex = chunk.content.lastIndexOf(n);
|
|
883
|
+
if (lineIndex !== -1)
|
|
884
|
+
return chunk.content.substr(lineIndex + 1) + lineStr;
|
|
885
|
+
lineStr = chunk.content + lineStr;
|
|
886
|
+
}
|
|
887
|
+
if (chunk.intro.length > 0) {
|
|
888
|
+
lineIndex = chunk.intro.lastIndexOf(n);
|
|
889
|
+
if (lineIndex !== -1)
|
|
890
|
+
return chunk.intro.substr(lineIndex + 1) + lineStr;
|
|
891
|
+
lineStr = chunk.intro + lineStr;
|
|
892
|
+
}
|
|
893
|
+
} while (chunk = chunk.previous);
|
|
894
|
+
lineIndex = this.intro.lastIndexOf(n);
|
|
895
|
+
if (lineIndex !== -1)
|
|
896
|
+
return this.intro.substr(lineIndex + 1) + lineStr;
|
|
897
|
+
return this.intro + lineStr;
|
|
898
|
+
}
|
|
899
|
+
slice(start = 0, end = this.original.length - this.offset) {
|
|
900
|
+
start = start + this.offset;
|
|
901
|
+
end = end + this.offset;
|
|
902
|
+
if (this.original.length !== 0) {
|
|
903
|
+
while (start < 0)
|
|
904
|
+
start += this.original.length;
|
|
905
|
+
while (end < 0)
|
|
906
|
+
end += this.original.length;
|
|
907
|
+
}
|
|
908
|
+
let result = "";
|
|
909
|
+
let chunk = this.firstChunk;
|
|
910
|
+
while (chunk && (chunk.start > start || chunk.end <= start)) {
|
|
911
|
+
if (chunk.start < end && chunk.end >= end) {
|
|
912
|
+
return result;
|
|
913
|
+
}
|
|
914
|
+
chunk = chunk.next;
|
|
915
|
+
}
|
|
916
|
+
if (chunk && chunk.edited && chunk.start !== start)
|
|
917
|
+
throw new Error(`Cannot use replaced character ${start} as slice start anchor.`);
|
|
918
|
+
const startChunk = chunk;
|
|
919
|
+
while (chunk) {
|
|
920
|
+
if (chunk.intro && (startChunk !== chunk || chunk.start === start)) {
|
|
921
|
+
result += chunk.intro;
|
|
922
|
+
}
|
|
923
|
+
const containsEnd = chunk.start < end && chunk.end >= end;
|
|
924
|
+
if (containsEnd && chunk.edited && chunk.end !== end)
|
|
925
|
+
throw new Error(`Cannot use replaced character ${end} as slice end anchor.`);
|
|
926
|
+
const sliceStart = startChunk === chunk ? start - chunk.start : 0;
|
|
927
|
+
const sliceEnd = containsEnd ? chunk.content.length + end - chunk.end : chunk.content.length;
|
|
928
|
+
result += chunk.content.slice(sliceStart, sliceEnd);
|
|
929
|
+
if (chunk.outro && (!containsEnd || chunk.end === end)) {
|
|
930
|
+
result += chunk.outro;
|
|
931
|
+
}
|
|
932
|
+
if (containsEnd) {
|
|
933
|
+
break;
|
|
934
|
+
}
|
|
935
|
+
chunk = chunk.next;
|
|
936
|
+
}
|
|
937
|
+
return result;
|
|
938
|
+
}
|
|
939
|
+
snip(start, end) {
|
|
940
|
+
const clone = this.clone();
|
|
941
|
+
clone.remove(0, start);
|
|
942
|
+
clone.remove(end, clone.original.length);
|
|
943
|
+
return clone;
|
|
944
|
+
}
|
|
945
|
+
_split(index) {
|
|
946
|
+
if (this.byStart[index] || this.byEnd[index])
|
|
947
|
+
return;
|
|
948
|
+
let chunk = this.lastSearchedChunk;
|
|
949
|
+
let previousChunk = chunk;
|
|
950
|
+
const searchForward = index > chunk.end;
|
|
951
|
+
while (chunk) {
|
|
952
|
+
if (chunk.contains(index))
|
|
953
|
+
return this._splitChunk(chunk, index);
|
|
954
|
+
chunk = searchForward ? this.byStart[chunk.end] : this.byEnd[chunk.start];
|
|
955
|
+
if (chunk === previousChunk)
|
|
956
|
+
return;
|
|
957
|
+
previousChunk = chunk;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
_splitChunk(chunk, index) {
|
|
961
|
+
if (chunk.edited && chunk.content.length) {
|
|
962
|
+
const loc = getLocator(this.original)(index);
|
|
963
|
+
throw new Error(`Cannot split a chunk that has already been edited (${loc.line}:${loc.column} \u2013 "${chunk.original}")`);
|
|
964
|
+
}
|
|
965
|
+
const newChunk = chunk.split(index);
|
|
966
|
+
this.byEnd[index] = chunk;
|
|
967
|
+
this.byStart[index] = newChunk;
|
|
968
|
+
this.byEnd[newChunk.end] = newChunk;
|
|
969
|
+
if (chunk === this.lastChunk)
|
|
970
|
+
this.lastChunk = newChunk;
|
|
971
|
+
this.lastSearchedChunk = chunk;
|
|
972
|
+
return true;
|
|
973
|
+
}
|
|
974
|
+
toString() {
|
|
975
|
+
let str = this.intro;
|
|
976
|
+
let chunk = this.firstChunk;
|
|
977
|
+
while (chunk) {
|
|
978
|
+
str += chunk.toString();
|
|
979
|
+
chunk = chunk.next;
|
|
980
|
+
}
|
|
981
|
+
return str + this.outro;
|
|
982
|
+
}
|
|
983
|
+
isEmpty() {
|
|
984
|
+
let chunk = this.firstChunk;
|
|
985
|
+
do {
|
|
986
|
+
if (chunk.intro.length && chunk.intro.trim() || chunk.content.length && chunk.content.trim() || chunk.outro.length && chunk.outro.trim())
|
|
987
|
+
return false;
|
|
988
|
+
} while (chunk = chunk.next);
|
|
989
|
+
return true;
|
|
990
|
+
}
|
|
991
|
+
length() {
|
|
992
|
+
let chunk = this.firstChunk;
|
|
993
|
+
let length = 0;
|
|
994
|
+
do {
|
|
995
|
+
length += chunk.intro.length + chunk.content.length + chunk.outro.length;
|
|
996
|
+
} while (chunk = chunk.next);
|
|
997
|
+
return length;
|
|
998
|
+
}
|
|
999
|
+
trimLines() {
|
|
1000
|
+
return this.trim("[\\r\\n]");
|
|
1001
|
+
}
|
|
1002
|
+
trim(charType) {
|
|
1003
|
+
return this.trimStart(charType).trimEnd(charType);
|
|
1004
|
+
}
|
|
1005
|
+
trimEndAborted(charType) {
|
|
1006
|
+
const rx = new RegExp((charType || "\\s") + "+$");
|
|
1007
|
+
this.outro = this.outro.replace(rx, "");
|
|
1008
|
+
if (this.outro.length)
|
|
1009
|
+
return true;
|
|
1010
|
+
let chunk = this.lastChunk;
|
|
1011
|
+
do {
|
|
1012
|
+
const end = chunk.end;
|
|
1013
|
+
const aborted = chunk.trimEnd(rx);
|
|
1014
|
+
if (chunk.end !== end) {
|
|
1015
|
+
if (this.lastChunk === chunk) {
|
|
1016
|
+
this.lastChunk = chunk.next;
|
|
1017
|
+
}
|
|
1018
|
+
this.byEnd[chunk.end] = chunk;
|
|
1019
|
+
this.byStart[chunk.next.start] = chunk.next;
|
|
1020
|
+
this.byEnd[chunk.next.end] = chunk.next;
|
|
1021
|
+
}
|
|
1022
|
+
if (aborted)
|
|
1023
|
+
return true;
|
|
1024
|
+
chunk = chunk.previous;
|
|
1025
|
+
} while (chunk);
|
|
1026
|
+
return false;
|
|
1027
|
+
}
|
|
1028
|
+
trimEnd(charType) {
|
|
1029
|
+
this.trimEndAborted(charType);
|
|
1030
|
+
return this;
|
|
1031
|
+
}
|
|
1032
|
+
trimStartAborted(charType) {
|
|
1033
|
+
const rx = new RegExp("^" + (charType || "\\s") + "+");
|
|
1034
|
+
this.intro = this.intro.replace(rx, "");
|
|
1035
|
+
if (this.intro.length)
|
|
1036
|
+
return true;
|
|
1037
|
+
let chunk = this.firstChunk;
|
|
1038
|
+
do {
|
|
1039
|
+
const end = chunk.end;
|
|
1040
|
+
const aborted = chunk.trimStart(rx);
|
|
1041
|
+
if (chunk.end !== end) {
|
|
1042
|
+
if (chunk === this.lastChunk)
|
|
1043
|
+
this.lastChunk = chunk.next;
|
|
1044
|
+
this.byEnd[chunk.end] = chunk;
|
|
1045
|
+
this.byStart[chunk.next.start] = chunk.next;
|
|
1046
|
+
this.byEnd[chunk.next.end] = chunk.next;
|
|
1047
|
+
}
|
|
1048
|
+
if (aborted)
|
|
1049
|
+
return true;
|
|
1050
|
+
chunk = chunk.next;
|
|
1051
|
+
} while (chunk);
|
|
1052
|
+
return false;
|
|
1053
|
+
}
|
|
1054
|
+
trimStart(charType) {
|
|
1055
|
+
this.trimStartAborted(charType);
|
|
1056
|
+
return this;
|
|
1057
|
+
}
|
|
1058
|
+
hasChanged() {
|
|
1059
|
+
return this.original !== this.toString();
|
|
1060
|
+
}
|
|
1061
|
+
_replaceRegexp(searchValue, replacement) {
|
|
1062
|
+
function getReplacement(match, str) {
|
|
1063
|
+
if (typeof replacement === "string") {
|
|
1064
|
+
return replacement.replace(/\$(\$|&|\d+)/g, (_, i) => {
|
|
1065
|
+
if (i === "$")
|
|
1066
|
+
return "$";
|
|
1067
|
+
if (i === "&")
|
|
1068
|
+
return match[0];
|
|
1069
|
+
const num = +i;
|
|
1070
|
+
if (num < match.length)
|
|
1071
|
+
return match[+i];
|
|
1072
|
+
return `$${i}`;
|
|
1073
|
+
});
|
|
1074
|
+
} else {
|
|
1075
|
+
return replacement(...match, match.index, str, match.groups);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function matchAll(re, str) {
|
|
1079
|
+
let match;
|
|
1080
|
+
const matches = [];
|
|
1081
|
+
while (match = re.exec(str)) {
|
|
1082
|
+
matches.push(match);
|
|
1083
|
+
}
|
|
1084
|
+
return matches;
|
|
1085
|
+
}
|
|
1086
|
+
if (searchValue.global) {
|
|
1087
|
+
const matches = matchAll(searchValue, this.original);
|
|
1088
|
+
matches.forEach((match) => {
|
|
1089
|
+
if (match.index != null) {
|
|
1090
|
+
const replacement2 = getReplacement(match, this.original);
|
|
1091
|
+
if (replacement2 !== match[0]) {
|
|
1092
|
+
this.overwrite(match.index, match.index + match[0].length, replacement2);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
} else {
|
|
1097
|
+
const match = this.original.match(searchValue);
|
|
1098
|
+
if (match && match.index != null) {
|
|
1099
|
+
const replacement2 = getReplacement(match, this.original);
|
|
1100
|
+
if (replacement2 !== match[0]) {
|
|
1101
|
+
this.overwrite(match.index, match.index + match[0].length, replacement2);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return this;
|
|
1106
|
+
}
|
|
1107
|
+
_replaceString(string, replacement) {
|
|
1108
|
+
const { original } = this;
|
|
1109
|
+
const index = original.indexOf(string);
|
|
1110
|
+
if (index !== -1) {
|
|
1111
|
+
if (typeof replacement === "function") {
|
|
1112
|
+
replacement = replacement(string, index, original);
|
|
1113
|
+
}
|
|
1114
|
+
if (string !== replacement) {
|
|
1115
|
+
this.overwrite(index, index + string.length, replacement);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return this;
|
|
1119
|
+
}
|
|
1120
|
+
replace(searchValue, replacement) {
|
|
1121
|
+
if (typeof searchValue === "string") {
|
|
1122
|
+
return this._replaceString(searchValue, replacement);
|
|
1123
|
+
}
|
|
1124
|
+
return this._replaceRegexp(searchValue, replacement);
|
|
1125
|
+
}
|
|
1126
|
+
_replaceAllString(string, replacement) {
|
|
1127
|
+
const { original } = this;
|
|
1128
|
+
const stringLength = string.length;
|
|
1129
|
+
for (let index = original.indexOf(string);index !== -1; index = original.indexOf(string, index + stringLength)) {
|
|
1130
|
+
const previous = original.slice(index, index + stringLength);
|
|
1131
|
+
let _replacement = replacement;
|
|
1132
|
+
if (typeof replacement === "function") {
|
|
1133
|
+
_replacement = replacement(previous, index, original);
|
|
1134
|
+
}
|
|
1135
|
+
if (previous !== _replacement)
|
|
1136
|
+
this.overwrite(index, index + stringLength, _replacement);
|
|
1137
|
+
}
|
|
1138
|
+
return this;
|
|
1139
|
+
}
|
|
1140
|
+
replaceAll(searchValue, replacement) {
|
|
1141
|
+
if (typeof searchValue === "string") {
|
|
1142
|
+
return this._replaceAllString(searchValue, replacement);
|
|
1143
|
+
}
|
|
1144
|
+
if (!searchValue.global) {
|
|
1145
|
+
throw new TypeError("MagicString.prototype.replaceAll called with a non-global RegExp argument");
|
|
1146
|
+
}
|
|
1147
|
+
return this._replaceRegexp(searchValue, replacement);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// src/plugin/transform-client.ts
|
|
1152
|
+
import { parseSync } from "oxc-parser";
|
|
1153
|
+
var bunTranspiler = new Bun.Transpiler({
|
|
1154
|
+
loader: "tsx",
|
|
1155
|
+
tsconfig: {
|
|
1156
|
+
compilerOptions: {
|
|
1157
|
+
jsx: "react",
|
|
1158
|
+
jsxFactory: "React.createElement",
|
|
1159
|
+
jsxFragmentFactory: "React.Fragment"
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
var SERVER_ONLY_PROPERTIES = new Set(["loader", "query", "params", "staticParams"]);
|
|
1164
|
+
function walk(node, visitor) {
|
|
1165
|
+
if (!node || typeof node !== "object") {
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (Array.isArray(node)) {
|
|
1169
|
+
for (const child of node) {
|
|
1170
|
+
walk(child, visitor);
|
|
1171
|
+
}
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
const n2 = node;
|
|
1175
|
+
if (typeof n2.type === "string") {
|
|
1176
|
+
visitor(n2);
|
|
1177
|
+
}
|
|
1178
|
+
for (const key of Object.keys(n2)) {
|
|
1179
|
+
if (key === "type" || key === "start" || key === "end") {
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
walk(n2[key], visitor);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
function isCreateRouteCall(node) {
|
|
1186
|
+
return node.callee.type === "Identifier" && node.callee.name === "createRoute";
|
|
1187
|
+
}
|
|
1188
|
+
function isRoutePageCall(node) {
|
|
1189
|
+
const { callee } = node;
|
|
1190
|
+
if (callee.type === "Identifier" && callee.name === "page") {
|
|
1191
|
+
return true;
|
|
1192
|
+
}
|
|
1193
|
+
if (callee.type === "MemberExpression" && callee.property?.name === "page") {
|
|
1194
|
+
return true;
|
|
1195
|
+
}
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
1198
|
+
function isTargetCall(node) {
|
|
1199
|
+
return isCreateRouteCall(node) || isRoutePageCall(node);
|
|
1200
|
+
}
|
|
1201
|
+
function removeServerProperties(s, source, obj) {
|
|
1202
|
+
const toRemove = obj.properties.filter((p) => {
|
|
1203
|
+
if (p.type !== "Property") {
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
const { key } = p;
|
|
1207
|
+
if (p.computed) {
|
|
1208
|
+
return false;
|
|
1209
|
+
}
|
|
1210
|
+
if (key.type === "Identifier" && typeof key.name === "string") {
|
|
1211
|
+
return SERVER_ONLY_PROPERTIES.has(key.name);
|
|
1212
|
+
}
|
|
1213
|
+
if (key.type === "Literal" && typeof key.value === "string") {
|
|
1214
|
+
return SERVER_ONLY_PROPERTIES.has(key.value);
|
|
1215
|
+
}
|
|
1216
|
+
return false;
|
|
1217
|
+
});
|
|
1218
|
+
if (toRemove.length === 0) {
|
|
1219
|
+
return false;
|
|
1220
|
+
}
|
|
1221
|
+
for (const prop of toRemove) {
|
|
1222
|
+
let removeEnd = prop.end;
|
|
1223
|
+
while (removeEnd < source.length && (source[removeEnd] === "," || source[removeEnd] === " " || source[removeEnd] === `
|
|
1224
|
+
` || source[removeEnd] === "\r" || source[removeEnd] === "\t")) {
|
|
1225
|
+
if (source[removeEnd] === ",") {
|
|
1226
|
+
removeEnd++;
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
removeEnd++;
|
|
1230
|
+
}
|
|
1231
|
+
let removeStart = prop.start;
|
|
1232
|
+
while (removeStart > 0 && (source[removeStart - 1] === " " || source[removeStart - 1] === "\t")) {
|
|
1233
|
+
removeStart--;
|
|
1234
|
+
}
|
|
1235
|
+
if (removeStart > 0 && source[removeStart - 1] === `
|
|
1236
|
+
`) {
|
|
1237
|
+
removeStart--;
|
|
1238
|
+
if (removeStart > 0 && source[removeStart - 1] === "\r") {
|
|
1239
|
+
removeStart--;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
s.remove(removeStart, removeEnd);
|
|
1243
|
+
}
|
|
1244
|
+
return true;
|
|
1245
|
+
}
|
|
1246
|
+
function collectReferencedNames(program) {
|
|
1247
|
+
const refs = new Set;
|
|
1248
|
+
const excluded = new Set;
|
|
1249
|
+
for (const stmt of program.body ?? []) {
|
|
1250
|
+
if (stmt.type === "ImportDeclaration") {
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
walk(stmt, (node) => {
|
|
1254
|
+
if (node.type === "Property" && !node.computed) {
|
|
1255
|
+
excluded.add(node.key);
|
|
1256
|
+
}
|
|
1257
|
+
if (node.type === "MemberExpression" && !node.computed) {
|
|
1258
|
+
excluded.add(node.property);
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
for (const stmt of program.body ?? []) {
|
|
1263
|
+
if (stmt.type === "ImportDeclaration") {
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
walk(stmt, (node) => {
|
|
1267
|
+
if (excluded.has(node)) {
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (node.type === "Identifier" && typeof node.name === "string") {
|
|
1271
|
+
refs.add(node.name);
|
|
1272
|
+
}
|
|
1273
|
+
});
|
|
1274
|
+
}
|
|
1275
|
+
return refs;
|
|
1276
|
+
}
|
|
1277
|
+
function removeEntireImport(s, code, decl) {
|
|
1278
|
+
let removeEnd = decl.end;
|
|
1279
|
+
while (removeEnd < code.length && (code[removeEnd] === `
|
|
1280
|
+
` || code[removeEnd] === "\r")) {
|
|
1281
|
+
removeEnd++;
|
|
1282
|
+
}
|
|
1283
|
+
s.remove(decl.start, removeEnd);
|
|
1284
|
+
}
|
|
1285
|
+
function removeUnusedSpecifiers(s, code, decl, refs) {
|
|
1286
|
+
const removedSpecs = decl.specifiers.filter((spec) => !refs.has(spec.local.name));
|
|
1287
|
+
for (const spec of removedSpecs) {
|
|
1288
|
+
let removeStart = spec.start;
|
|
1289
|
+
let removeEnd = spec.end;
|
|
1290
|
+
while (removeEnd < code.length && (code[removeEnd] === "," || code[removeEnd] === " ")) {
|
|
1291
|
+
removeEnd++;
|
|
1292
|
+
}
|
|
1293
|
+
if (!code.slice(spec.end, removeEnd).includes(",")) {
|
|
1294
|
+
while (removeStart > 0 && (code[removeStart - 1] === " " || code[removeStart - 1] === ",")) {
|
|
1295
|
+
removeStart--;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
s.remove(removeStart, removeEnd);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
function deadCodeElimination(s) {
|
|
1302
|
+
const code = s.toString();
|
|
1303
|
+
const { program, errors } = parseSync("dce.js", code);
|
|
1304
|
+
if (errors.length > 0) {
|
|
1305
|
+
console.error("[furin] DCE: failed to parse transformed output:", errors[0]?.message);
|
|
1306
|
+
return s;
|
|
1307
|
+
}
|
|
1308
|
+
const fresh = new MagicString(code);
|
|
1309
|
+
const programNode = program;
|
|
1310
|
+
const refs = collectReferencedNames(programNode);
|
|
1311
|
+
const body = programNode.body ?? [];
|
|
1312
|
+
for (let i = body.length - 1;i >= 0; i--) {
|
|
1313
|
+
const stmt = body[i];
|
|
1314
|
+
if (!stmt) {
|
|
1315
|
+
continue;
|
|
1316
|
+
}
|
|
1317
|
+
if (stmt.type !== "ImportDeclaration") {
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
const decl = stmt;
|
|
1321
|
+
if (!decl.specifiers || decl.specifiers.length === 0) {
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
const usedCount = decl.specifiers.filter((spec) => refs.has(spec.local.name)).length;
|
|
1325
|
+
if (usedCount === 0) {
|
|
1326
|
+
removeEntireImport(fresh, code, decl);
|
|
1327
|
+
} else if (usedCount < decl.specifiers.length) {
|
|
1328
|
+
removeUnusedSpecifiers(fresh, code, decl, refs);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return fresh;
|
|
1332
|
+
}
|
|
1333
|
+
function removeServerExports(s, source, program) {
|
|
1334
|
+
let removedServerCode = false;
|
|
1335
|
+
walk(program, (node) => {
|
|
1336
|
+
if (node.type !== "CallExpression") {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
const call = node;
|
|
1340
|
+
if (!isTargetCall(call)) {
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const arg = call.arguments[0];
|
|
1344
|
+
if (!arg || arg.type !== "ObjectExpression") {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
if (removeServerProperties(s, source, arg)) {
|
|
1348
|
+
removedServerCode = true;
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
return removedServerCode;
|
|
1352
|
+
}
|
|
1353
|
+
function transformForClient(code, filename) {
|
|
1354
|
+
const plainJs = bunTranspiler.transformSync(code);
|
|
1355
|
+
const { program, errors } = parseSync(filename, plainJs);
|
|
1356
|
+
if (errors.length > 0) {
|
|
1357
|
+
throw new Error(`Failed to parse ${filename}: ${errors[0]?.message}`);
|
|
1358
|
+
}
|
|
1359
|
+
let s = new MagicString(plainJs);
|
|
1360
|
+
const removedServerCode = removeServerExports(s, plainJs, program);
|
|
1361
|
+
if (removedServerCode) {
|
|
1362
|
+
s = deadCodeElimination(s);
|
|
1363
|
+
}
|
|
1364
|
+
return {
|
|
1365
|
+
code: s.toString(),
|
|
1366
|
+
map: s.generateMap({ source: filename, includeContent: true }),
|
|
1367
|
+
removedServerCode
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/render/shell.ts
|
|
1372
|
+
function generateIndexHtml() {
|
|
1373
|
+
return `<!DOCTYPE html>
|
|
1374
|
+
<html lang="en">
|
|
1375
|
+
<head>
|
|
1376
|
+
<meta charset="UTF-8">
|
|
1377
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1378
|
+
<!--ssr-head-->
|
|
1379
|
+
</head>
|
|
1380
|
+
<body>
|
|
1381
|
+
<div id="root"><!--ssr-outlet--></div>
|
|
1382
|
+
<script type="module" src="./_hydrate.tsx"></script>
|
|
1383
|
+
</body>
|
|
1384
|
+
</html>
|
|
1385
|
+
`;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// src/build/hydrate.ts
|
|
1389
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1390
|
+
import { join as join2 } from "path";
|
|
1391
|
+
|
|
1392
|
+
// src/build/route-types.ts
|
|
1393
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
1394
|
+
import { join } from "path";
|
|
1395
|
+
function patternToTypeString(pattern) {
|
|
1396
|
+
const t = pattern.replace(/:[^/]+/g, "${string}").replace(/\*/g, "${string}");
|
|
1397
|
+
return t.includes("${") ? `\`${t}\`` : `"${t}"`;
|
|
1398
|
+
}
|
|
1399
|
+
function schemaToTypeString(schema) {
|
|
1400
|
+
if (!schema || typeof schema !== "object") {
|
|
1401
|
+
return "unknown";
|
|
1402
|
+
}
|
|
1403
|
+
const s = schema;
|
|
1404
|
+
if (s.anyOf && Array.isArray(s.anyOf)) {
|
|
1405
|
+
const parts = s.anyOf.map(schemaToTypeString).filter((t) => t !== "null");
|
|
1406
|
+
return parts.join(" | ") || "unknown";
|
|
1407
|
+
}
|
|
1408
|
+
switch (s.type) {
|
|
1409
|
+
case "string":
|
|
1410
|
+
return "string";
|
|
1411
|
+
case "number":
|
|
1412
|
+
case "integer":
|
|
1413
|
+
return "number";
|
|
1414
|
+
case "boolean":
|
|
1415
|
+
return "boolean";
|
|
1416
|
+
case "null":
|
|
1417
|
+
return "null";
|
|
1418
|
+
case "object": {
|
|
1419
|
+
if (!s.properties || typeof s.properties !== "object") {
|
|
1420
|
+
return "Record<string, unknown>";
|
|
1421
|
+
}
|
|
1422
|
+
const required = new Set(Array.isArray(s.required) ? s.required : []);
|
|
1423
|
+
const props = Object.entries(s.properties).map(([k, v]) => `${k}${required.has(k) ? "" : "?"}: ${schemaToTypeString(v)}`).join("; ");
|
|
1424
|
+
return `{ ${props} }`;
|
|
1425
|
+
}
|
|
1426
|
+
default:
|
|
1427
|
+
return "unknown";
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
function writeRouteTypes(routes, outDir) {
|
|
1431
|
+
const entries = routes.map((r) => {
|
|
1432
|
+
const typeKey = patternToTypeString(r.pattern);
|
|
1433
|
+
const isDynamic = typeKey.startsWith("`");
|
|
1434
|
+
const querySchema = r.routeChain?.find((rt) => rt.query)?.query;
|
|
1435
|
+
const searchType = querySchema ? schemaToTypeString(querySchema) : "never";
|
|
1436
|
+
return isDynamic ? ` [key: ${typeKey}]: { search?: ${searchType} }` : ` ${typeKey}: { search?: ${searchType} }`;
|
|
1437
|
+
});
|
|
1438
|
+
const content = `// Auto-generated by Furin. Do not edit manually.
|
|
1439
|
+
// Add ".furin/routes.d.ts" to your tsconfig.json "include" array to enable typed navigation.
|
|
1440
|
+
import "@teyik0/furin/link";
|
|
1441
|
+
|
|
1442
|
+
declare module "@teyik0/furin/link" {
|
|
1443
|
+
interface RouteManifest {
|
|
1444
|
+
${entries.join(`;
|
|
1445
|
+
`)};
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
`;
|
|
1449
|
+
const typesPath = join(outDir, "routes.d.ts");
|
|
1450
|
+
const existing = existsSync(typesPath) ? readFileSync(typesPath, "utf8") : "";
|
|
1451
|
+
if (content !== existing) {
|
|
1452
|
+
writeFileSync(typesPath, content);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
// src/build/hydrate.ts
|
|
1457
|
+
function generateHydrateEntry(routes, rootLayout) {
|
|
1458
|
+
const routeEntries = [];
|
|
1459
|
+
for (const route of routes) {
|
|
1460
|
+
const resolvedPage = route.path.replace(/\\/g, "/");
|
|
1461
|
+
const regexPattern = route.pattern.replace(/:[^/]+/g, "([^/]+)").replace(/\*/g, "(.*)");
|
|
1462
|
+
routeEntries.push(` { pattern: "${route.pattern}", regex: new RegExp("^${regexPattern}$"), load: () => import("${resolvedPage}") }`);
|
|
1463
|
+
}
|
|
1464
|
+
return `import { hydrateRoot, createRoot } from "react-dom/client";
|
|
1465
|
+
import { createElement } from "react";
|
|
1466
|
+
import { RouterProvider } from "@teyik0/furin/link";
|
|
1467
|
+
import { route as root } from "${rootLayout.replace(/\\/g, "/")}";
|
|
1468
|
+
|
|
1469
|
+
const routes = [
|
|
1470
|
+
${routeEntries.join(`,
|
|
1471
|
+
`)}
|
|
1472
|
+
];
|
|
1473
|
+
|
|
1474
|
+
const pathname = window.location.pathname;
|
|
1475
|
+
const _match = routes.find((r) => r.regex.test(pathname));
|
|
1476
|
+
|
|
1477
|
+
// Eagerly load only the current page module for initial hydration.
|
|
1478
|
+
// All other pages are loaded on demand when the user navigates to them.
|
|
1479
|
+
if (_match) {
|
|
1480
|
+
const _mod = await _match.load();
|
|
1481
|
+
const match = { ..._match, component: _mod.default.component, pageRoute: _mod.default._route };
|
|
1482
|
+
|
|
1483
|
+
const dataEl = document.getElementById("__FURIN_DATA__");
|
|
1484
|
+
const loaderData = dataEl ? JSON.parse(dataEl.textContent || "{}") : {};
|
|
1485
|
+
const rootEl = document.getElementById("root") as HTMLElement;
|
|
1486
|
+
|
|
1487
|
+
const app = createElement(RouterProvider, {
|
|
1488
|
+
routes,
|
|
1489
|
+
root,
|
|
1490
|
+
initialMatch: match,
|
|
1491
|
+
initialData: loaderData,
|
|
1492
|
+
} as any);
|
|
1493
|
+
|
|
1494
|
+
if (import.meta.hot) {
|
|
1495
|
+
// Retain React root across hot reloads so Fast Refresh applies in-place.
|
|
1496
|
+
const hotRoot = (import.meta.hot.data.root ??= rootEl.innerHTML.trim()
|
|
1497
|
+
? hydrateRoot(rootEl, app)
|
|
1498
|
+
: createRoot(rootEl));
|
|
1499
|
+
hotRoot.render(app);
|
|
1500
|
+
} else if (rootEl.innerHTML.trim()) {
|
|
1501
|
+
hydrateRoot(rootEl, app);
|
|
1502
|
+
} else {
|
|
1503
|
+
createRoot(rootEl).render(app);
|
|
1504
|
+
}
|
|
1505
|
+
} else {
|
|
1506
|
+
console.error("[furin] No matching route for", pathname);
|
|
1507
|
+
}
|
|
1508
|
+
`;
|
|
1509
|
+
}
|
|
1510
|
+
function writeDevFiles(routes, { outDir, rootLayout }) {
|
|
1511
|
+
if (!existsSync2(outDir)) {
|
|
1512
|
+
mkdirSync(outDir, { recursive: true });
|
|
1513
|
+
}
|
|
1514
|
+
const hydrateCode = generateHydrateEntry(routes, rootLayout);
|
|
1515
|
+
const hydratePath = join2(outDir, "_hydrate.tsx");
|
|
1516
|
+
const existingHydrate = existsSync2(hydratePath) ? readFileSync2(hydratePath, "utf8") : "";
|
|
1517
|
+
if (hydrateCode !== existingHydrate) {
|
|
1518
|
+
writeFileSync2(hydratePath, hydrateCode);
|
|
1519
|
+
}
|
|
1520
|
+
const indexHtml = generateIndexHtml();
|
|
1521
|
+
const indexPath = join2(outDir, "index.html");
|
|
1522
|
+
const existingIndex = existsSync2(indexPath) ? readFileSync2(indexPath, "utf8") : "";
|
|
1523
|
+
if (indexHtml !== existingIndex) {
|
|
1524
|
+
writeFileSync2(indexPath, indexHtml);
|
|
1525
|
+
}
|
|
1526
|
+
writeRouteTypes(routes, outDir);
|
|
1527
|
+
console.log("[furin] Dev files written (.furin/_hydrate.tsx + .furin/index.html + .furin/routes.d.ts)");
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
// src/build/shared.ts
|
|
1531
|
+
import { cpSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1532
|
+
import { join as join3, relative, resolve } from "path";
|
|
1533
|
+
var CLIENT_MODULE_PATH = resolve(import.meta.dir, "../client.ts").replace(/\\/g, "/");
|
|
1534
|
+
var LINK_MODULE_PATH = resolve(import.meta.dir, "../link.tsx").replace(/\\/g, "/");
|
|
1535
|
+
function ensureDir(path) {
|
|
1536
|
+
if (!existsSync3(path)) {
|
|
1537
|
+
mkdirSync2(path, { recursive: true });
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
function toPosixPath(path) {
|
|
1541
|
+
return path.replace(/\\/g, "/");
|
|
1542
|
+
}
|
|
1543
|
+
function collectFilesRecursive(dir) {
|
|
1544
|
+
const files = [];
|
|
1545
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1546
|
+
for (const entry of entries) {
|
|
1547
|
+
const absolutePath = join3(dir, entry.name);
|
|
1548
|
+
if (entry.isDirectory()) {
|
|
1549
|
+
files.push(...collectFilesRecursive(absolutePath));
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
if (entry.isFile()) {
|
|
1553
|
+
files.push(absolutePath);
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
return files.sort();
|
|
1557
|
+
}
|
|
1558
|
+
function copyDirRecursive(sourceDir, targetDir) {
|
|
1559
|
+
rmSync(targetDir, { force: true, recursive: true });
|
|
1560
|
+
cpSync(sourceDir, targetDir, { recursive: true });
|
|
1561
|
+
}
|
|
1562
|
+
function toBuildRouteManifestEntry(route, rootDir) {
|
|
1563
|
+
return {
|
|
1564
|
+
pattern: route.pattern,
|
|
1565
|
+
mode: route.mode,
|
|
1566
|
+
pagePath: toPosixPath(relative(rootDir, route.path)),
|
|
1567
|
+
hasLayout: route.routeChain.some((entry) => !!entry.layout),
|
|
1568
|
+
hasStaticParams: !!route.page?.staticParams,
|
|
1569
|
+
revalidate: route.page?._route.revalidate ?? null
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
function buildTargetManifest(rootDir, buildRoot, target, serverEntry) {
|
|
1573
|
+
const targetDir = join3(buildRoot, target);
|
|
1574
|
+
const manifestPath = join3(targetDir, "manifest.json");
|
|
1575
|
+
return {
|
|
1576
|
+
generatedAt: new Date().toISOString(),
|
|
1577
|
+
targetDir: toPosixPath(relative(rootDir, targetDir)),
|
|
1578
|
+
clientDir: toPosixPath(relative(rootDir, join3(targetDir, "client"))),
|
|
1579
|
+
templatePath: toPosixPath(relative(rootDir, join3(targetDir, "client", "index.html"))),
|
|
1580
|
+
manifestPath: toPosixPath(relative(rootDir, manifestPath)),
|
|
1581
|
+
serverPath: null,
|
|
1582
|
+
serverEntry: serverEntry ? toPosixPath(relative(rootDir, serverEntry)) : null
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
function writeTargetManifest(targetDir, targetManifest) {
|
|
1586
|
+
const manifestPath = join3(targetDir, "manifest.json");
|
|
1587
|
+
writeFileSync3(manifestPath, `${JSON.stringify(targetManifest, null, 2)}
|
|
1588
|
+
`);
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/build/client.ts
|
|
1592
|
+
var TS_FILE_FILTER = /\.(tsx|ts)$/;
|
|
1593
|
+
var REACT_IMPORT_RE = /import\s+React\b/;
|
|
1594
|
+
async function buildClient(routes, { outDir, rootLayout, plugins }) {
|
|
1595
|
+
const clientDir = join4(outDir, "client");
|
|
1596
|
+
if (!existsSync4(outDir)) {
|
|
1597
|
+
mkdirSync3(outDir, { recursive: true });
|
|
1598
|
+
}
|
|
1599
|
+
if (!existsSync4(clientDir)) {
|
|
1600
|
+
mkdirSync3(clientDir, { recursive: true });
|
|
1601
|
+
}
|
|
1602
|
+
const hydrateCode = generateHydrateEntry(routes, rootLayout);
|
|
1603
|
+
const hydratePath = join4(outDir, "_hydrate.tsx");
|
|
1604
|
+
writeFileSync4(hydratePath, hydrateCode);
|
|
1605
|
+
const indexHtml = generateIndexHtml();
|
|
1606
|
+
const indexPath = join4(outDir, "index.html");
|
|
1607
|
+
writeFileSync4(indexPath, indexHtml);
|
|
1608
|
+
console.log("[furin] Building production client bundle\u2026");
|
|
1609
|
+
const transformPlugin = {
|
|
1610
|
+
name: "furin-transform-client",
|
|
1611
|
+
setup(build) {
|
|
1612
|
+
build.onLoad({ filter: TS_FILE_FILTER }, async (args) => {
|
|
1613
|
+
const { path } = args;
|
|
1614
|
+
if (path.includes("node_modules")) {
|
|
1615
|
+
return;
|
|
1616
|
+
}
|
|
1617
|
+
const code = await Bun.file(path).text();
|
|
1618
|
+
try {
|
|
1619
|
+
const result2 = transformForClient(code, path);
|
|
1620
|
+
let transformed = result2.code;
|
|
1621
|
+
if (transformed.includes("React.createElement") && !REACT_IMPORT_RE.test(transformed)) {
|
|
1622
|
+
transformed = `import React from "react";
|
|
1623
|
+
${transformed}`;
|
|
1624
|
+
}
|
|
1625
|
+
transformed = transformed.replaceAll(`"@teyik0/furin/client"`, JSON.stringify(CLIENT_MODULE_PATH)).replaceAll(`'furin/client'`, JSON.stringify(CLIENT_MODULE_PATH)).replaceAll(`"@teyik0/furin/link"`, JSON.stringify(LINK_MODULE_PATH)).replaceAll(`'furin/link'`, JSON.stringify(LINK_MODULE_PATH));
|
|
1626
|
+
return {
|
|
1627
|
+
contents: transformed,
|
|
1628
|
+
loader: path.endsWith(".tsx") ? "tsx" : "ts"
|
|
1629
|
+
};
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
console.error(`[furin] Transform error for ${path}:`, error);
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
};
|
|
1637
|
+
const clientBuildConfig = {
|
|
1638
|
+
entrypoints: [indexPath],
|
|
1639
|
+
outdir: clientDir,
|
|
1640
|
+
target: "browser",
|
|
1641
|
+
format: "esm",
|
|
1642
|
+
splitting: true,
|
|
1643
|
+
minify: true,
|
|
1644
|
+
sourcemap: "linked",
|
|
1645
|
+
publicPath: "/_client/",
|
|
1646
|
+
plugins: plugins ? [...plugins, transformPlugin] : [transformPlugin],
|
|
1647
|
+
alias: {
|
|
1648
|
+
"@teyik0/furin/client": CLIENT_MODULE_PATH,
|
|
1649
|
+
"@teyik0/furin/link": LINK_MODULE_PATH
|
|
1650
|
+
},
|
|
1651
|
+
define: {
|
|
1652
|
+
"process.env.NODE_ENV": JSON.stringify("production")
|
|
1653
|
+
}
|
|
1654
|
+
};
|
|
1655
|
+
const result = await Bun.build(clientBuildConfig);
|
|
1656
|
+
for (const output of result.outputs) {
|
|
1657
|
+
console.log(`[furin] ${output.path} (${(output.size / 1024).toFixed(1)} KB)`);
|
|
1658
|
+
}
|
|
1659
|
+
console.log("[furin] Production client build complete");
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
// src/build/compile-entry.ts
|
|
1663
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
1664
|
+
import { join as join5, relative as relative2 } from "path";
|
|
1665
|
+
|
|
1666
|
+
// src/build/entry-template.ts
|
|
1667
|
+
import { resolve as resolve2 } from "path";
|
|
1668
|
+
var INTERNAL_MODULE_PATH = resolve2(import.meta.dir, "../internal.ts").replace(/\\/g, "/");
|
|
1669
|
+
var RUNTIME_ENV_MODULE_PATH = resolve2(import.meta.dir, "../runtime-env.ts").replace(/\\/g, "/");
|
|
1670
|
+
function buildEntrySource(options) {
|
|
1671
|
+
const { headerComment, rootPath, routes, serverEntry, extraImports = [], extraContext = [] } = options;
|
|
1672
|
+
const allModulePaths = [rootPath, ...routes.map((r) => r.path)];
|
|
1673
|
+
const moduleImports = [];
|
|
1674
|
+
const moduleEntries = [];
|
|
1675
|
+
for (let i = 0;i < allModulePaths.length; i++) {
|
|
1676
|
+
const absPath = allModulePaths[i].replace(/\\/g, "/");
|
|
1677
|
+
const varName = `_mod${i}`;
|
|
1678
|
+
moduleImports.push(`import * as ${varName} from ${JSON.stringify(absPath)};`);
|
|
1679
|
+
moduleEntries.push(` ${JSON.stringify(absPath)}: ${varName},`);
|
|
1680
|
+
}
|
|
1681
|
+
const routeEntries = routes.map((r) => ` { pattern: ${JSON.stringify(r.pattern)}, path: ${JSON.stringify(r.path.replace(/\\/g, "/"))}, mode: ${JSON.stringify(r.mode)} },`);
|
|
1682
|
+
const lines = [
|
|
1683
|
+
headerComment,
|
|
1684
|
+
`import { __setCompileContext } from ${JSON.stringify(INTERNAL_MODULE_PATH)};`,
|
|
1685
|
+
`import { __setDevMode } from ${JSON.stringify(RUNTIME_ENV_MODULE_PATH)};`,
|
|
1686
|
+
...moduleImports,
|
|
1687
|
+
...extraImports.length > 0 ? ["", ...extraImports] : [],
|
|
1688
|
+
"",
|
|
1689
|
+
"// Force production mode \u2014 Bun may inline process.env.NODE_ENV at bundle time.",
|
|
1690
|
+
"__setDevMode(false);",
|
|
1691
|
+
'process.env.NODE_ENV = "production";',
|
|
1692
|
+
"",
|
|
1693
|
+
"__setCompileContext({",
|
|
1694
|
+
` rootPath: ${JSON.stringify(rootPath.replace(/\\/g, "/"))},`,
|
|
1695
|
+
" modules: {",
|
|
1696
|
+
...moduleEntries,
|
|
1697
|
+
" },",
|
|
1698
|
+
" routes: [",
|
|
1699
|
+
...routeEntries,
|
|
1700
|
+
" ],",
|
|
1701
|
+
...extraContext,
|
|
1702
|
+
"});",
|
|
1703
|
+
"",
|
|
1704
|
+
`await import(${JSON.stringify(serverEntry.replace(/\\/g, "/"))});`,
|
|
1705
|
+
""
|
|
1706
|
+
];
|
|
1707
|
+
return lines.join(`
|
|
1708
|
+
`);
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// src/build/compile-entry.ts
|
|
1712
|
+
function generateCompileEntry(options) {
|
|
1713
|
+
const { outDir, rootPath, routes, serverEntry, embed, publicDir } = options;
|
|
1714
|
+
ensureDir(outDir);
|
|
1715
|
+
const assetImports = [];
|
|
1716
|
+
let embeddedBlock = [];
|
|
1717
|
+
if (embed) {
|
|
1718
|
+
if (!existsSync5(embed.clientDir)) {
|
|
1719
|
+
throw new Error(`[furin] Client directory not found: ${embed.clientDir}. Run the client build first.`);
|
|
1720
|
+
}
|
|
1721
|
+
const clientFiles = collectFilesRecursive(embed.clientDir);
|
|
1722
|
+
const assetEntries = [];
|
|
1723
|
+
let templateVarName = null;
|
|
1724
|
+
let assetIndex = 0;
|
|
1725
|
+
for (const file of clientFiles) {
|
|
1726
|
+
const varName = `_asset${assetIndex++}`;
|
|
1727
|
+
const relPath = toPosixPath(relative2(outDir, file));
|
|
1728
|
+
const importPath = relPath.startsWith(".") ? relPath : `./${relPath}`;
|
|
1729
|
+
assetImports.push(`import ${varName} from ${JSON.stringify(importPath)} with { type: "file" };`);
|
|
1730
|
+
const clientRelativePath = toPosixPath(relative2(embed.clientDir, file));
|
|
1731
|
+
if (clientRelativePath === "index.html") {
|
|
1732
|
+
templateVarName = varName;
|
|
1733
|
+
} else {
|
|
1734
|
+
assetEntries.push(` ${JSON.stringify(`/_client/${clientRelativePath}`)}: ${varName},`);
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
if (templateVarName === null) {
|
|
1738
|
+
throw new Error(`[furin] Embed mode requires a client index.html at ${join5(embed.clientDir, "index.html")}. Run the client build first.`);
|
|
1739
|
+
}
|
|
1740
|
+
if (publicDir && existsSync5(publicDir)) {
|
|
1741
|
+
const publicFiles = collectFilesRecursive(publicDir);
|
|
1742
|
+
for (const file of publicFiles) {
|
|
1743
|
+
const varName = `_asset${assetIndex++}`;
|
|
1744
|
+
const relPath = toPosixPath(relative2(outDir, file));
|
|
1745
|
+
const importPath = relPath.startsWith(".") ? relPath : `./${relPath}`;
|
|
1746
|
+
assetImports.push(`import ${varName} from ${JSON.stringify(importPath)} with { type: "file" };`);
|
|
1747
|
+
const publicRelativePath = toPosixPath(relative2(publicDir, file));
|
|
1748
|
+
assetEntries.push(` ${JSON.stringify(`/public/${publicRelativePath}`)}: ${varName},`);
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
embeddedBlock = [
|
|
1752
|
+
" embedded: {",
|
|
1753
|
+
` template: ${templateVarName},`,
|
|
1754
|
+
" assets: {",
|
|
1755
|
+
...assetEntries,
|
|
1756
|
+
" },",
|
|
1757
|
+
" },"
|
|
1758
|
+
];
|
|
1759
|
+
}
|
|
1760
|
+
const source = buildEntrySource({
|
|
1761
|
+
headerComment: "// Auto-generated by furin compile \u2014 do not edit",
|
|
1762
|
+
rootPath,
|
|
1763
|
+
routes,
|
|
1764
|
+
serverEntry,
|
|
1765
|
+
extraImports: assetImports,
|
|
1766
|
+
extraContext: embeddedBlock
|
|
1767
|
+
});
|
|
1768
|
+
const entryPath = join5(outDir, "_compile-entry.ts");
|
|
1769
|
+
writeFileSync5(entryPath, source);
|
|
1770
|
+
return entryPath;
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// src/build/server-routes-entry.ts
|
|
1774
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
1775
|
+
import { join as join6 } from "path";
|
|
1776
|
+
function generateServerRoutesEntry(options) {
|
|
1777
|
+
const { outDir, rootPath, routes, serverEntry } = options;
|
|
1778
|
+
ensureDir(outDir);
|
|
1779
|
+
const source = buildEntrySource({
|
|
1780
|
+
headerComment: "// Auto-generated by furin build \u2014 do not edit",
|
|
1781
|
+
rootPath,
|
|
1782
|
+
routes,
|
|
1783
|
+
serverEntry
|
|
1784
|
+
});
|
|
1785
|
+
const entryPath = join6(outDir, "server.ts");
|
|
1786
|
+
writeFileSync6(entryPath, source);
|
|
1787
|
+
return entryPath;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// src/adapter/bun.ts
|
|
1791
|
+
async function buildBunTarget(routes, rootDir, buildRoot, rootPath, serverEntry, options) {
|
|
1792
|
+
if (options.compile && !serverEntry) {
|
|
1793
|
+
throw new Error(`[furin] \`compile: "${options.compile}"\` requires a server entry point. ` + "Create src/server.ts or set `serverEntry` in your furin.config.ts.");
|
|
1794
|
+
}
|
|
1795
|
+
const target = "bun";
|
|
1796
|
+
const targetManifest = buildTargetManifest(rootDir, buildRoot, target, serverEntry);
|
|
1797
|
+
const targetDir = resolve3(rootDir, targetManifest.targetDir);
|
|
1798
|
+
rmSync2(targetDir, { force: true, recursive: true });
|
|
1799
|
+
ensureDir(targetDir);
|
|
1800
|
+
await buildClient(routes, {
|
|
1801
|
+
outDir: targetDir,
|
|
1802
|
+
rootLayout: rootPath,
|
|
1803
|
+
plugins: options.plugins
|
|
1804
|
+
});
|
|
1805
|
+
const routeManifest = routes.map((r) => ({ pattern: r.pattern, path: r.path, mode: r.mode }));
|
|
1806
|
+
const publicDir = existsSync6(join7(rootDir, "public")) ? join7(rootDir, "public") : undefined;
|
|
1807
|
+
const targetPublicDir = publicDir ? join7(targetDir, "public") : undefined;
|
|
1808
|
+
if (publicDir && targetPublicDir && options.compile !== "embed") {
|
|
1809
|
+
copyDirRecursive(publicDir, targetPublicDir);
|
|
1810
|
+
}
|
|
1811
|
+
if (options.compile && serverEntry) {
|
|
1812
|
+
const clientDir = join7(targetDir, "client");
|
|
1813
|
+
const outfile = join7(targetDir, "server");
|
|
1814
|
+
const entryPath = generateCompileEntry({
|
|
1815
|
+
rootPath,
|
|
1816
|
+
routes: routeManifest,
|
|
1817
|
+
serverEntry,
|
|
1818
|
+
outDir: targetDir,
|
|
1819
|
+
embed: options.compile === "embed" ? { clientDir } : undefined,
|
|
1820
|
+
publicDir
|
|
1821
|
+
});
|
|
1822
|
+
await Bun.build({
|
|
1823
|
+
entrypoints: [entryPath],
|
|
1824
|
+
compile: { outfile },
|
|
1825
|
+
minify: true,
|
|
1826
|
+
sourcemap: "linked",
|
|
1827
|
+
plugins: options.plugins
|
|
1828
|
+
});
|
|
1829
|
+
console.log(`[furin] Server binary: ${outfile}`);
|
|
1830
|
+
targetManifest.serverPath = toPosixPath(join7(targetManifest.targetDir, "server"));
|
|
1831
|
+
if (options.compile === "embed") {
|
|
1832
|
+
rmSync2(clientDir, { force: true, recursive: true });
|
|
1833
|
+
}
|
|
1834
|
+
} else if (serverEntry) {
|
|
1835
|
+
const entryPath = generateServerRoutesEntry({
|
|
1836
|
+
rootPath,
|
|
1837
|
+
routes: routeManifest,
|
|
1838
|
+
serverEntry,
|
|
1839
|
+
outDir: targetDir
|
|
1840
|
+
});
|
|
1841
|
+
await Bun.build({
|
|
1842
|
+
entrypoints: [entryPath],
|
|
1843
|
+
outdir: targetDir,
|
|
1844
|
+
target: "bun",
|
|
1845
|
+
minify: true,
|
|
1846
|
+
sourcemap: "linked",
|
|
1847
|
+
plugins: options.plugins
|
|
1848
|
+
});
|
|
1849
|
+
console.log(`[furin] Server bundle: ${toPosixPath(join7(targetManifest.targetDir, "server.js"))}`);
|
|
1850
|
+
targetManifest.serverPath = toPosixPath(join7(targetManifest.targetDir, "server.js"));
|
|
1851
|
+
}
|
|
1852
|
+
for (const file of [
|
|
1853
|
+
"_compile-entry.ts",
|
|
1854
|
+
"_compile-entry.js.map",
|
|
1855
|
+
"server.ts",
|
|
1856
|
+
"_hydrate.tsx",
|
|
1857
|
+
"index.html"
|
|
1858
|
+
]) {
|
|
1859
|
+
rmSync2(join7(targetDir, file), { force: true });
|
|
1860
|
+
}
|
|
1861
|
+
writeTargetManifest(targetDir, targetManifest);
|
|
1862
|
+
return targetManifest;
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
// src/config.ts
|
|
1866
|
+
import { t } from "elysia";
|
|
1867
|
+
var BUILD_TARGETS = ["bun", "node", "vercel", "cloudflare"];
|
|
1868
|
+
var buildTargetSchema = t.Union(BUILD_TARGETS.map((v) => t.Literal(v)));
|
|
1869
|
+
var compileTargetSchema = t.Union([t.Literal("server"), t.Literal("embed")]);
|
|
1870
|
+
var configSchema = t.Object({
|
|
1871
|
+
rootDir: t.Optional(t.String()),
|
|
1872
|
+
pagesDir: t.Optional(t.String()),
|
|
1873
|
+
serverEntry: t.Optional(t.String()),
|
|
1874
|
+
targets: t.Optional(t.Array(buildTargetSchema)),
|
|
1875
|
+
bun: t.Optional(t.Object({
|
|
1876
|
+
compile: t.Optional(compileTargetSchema)
|
|
1877
|
+
}))
|
|
1878
|
+
});
|
|
1879
|
+
|
|
1880
|
+
// src/router.ts
|
|
1881
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1882
|
+
import { readdir } from "fs/promises";
|
|
1883
|
+
import { join as join8, parse } from "path";
|
|
1884
|
+
import { Elysia } from "elysia";
|
|
1885
|
+
|
|
1886
|
+
// src/internal.ts
|
|
1887
|
+
var _compileCtx = null;
|
|
1888
|
+
function getCompileContext() {
|
|
1889
|
+
return _compileCtx;
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
// src/render/index.ts
|
|
1893
|
+
import { renderToReadableStream } from "react-dom/server";
|
|
1894
|
+
|
|
1895
|
+
// src/render/cache.ts
|
|
1896
|
+
var isrCache = new Map;
|
|
1897
|
+
var ssgCache = new Map;
|
|
1898
|
+
|
|
1899
|
+
// src/render/element.tsx
|
|
1900
|
+
import { jsxDEV } from "react/jsx-dev-runtime";
|
|
1901
|
+
|
|
1902
|
+
// src/render/template.ts
|
|
1903
|
+
var devTemplatePromises = new Map;
|
|
1904
|
+
// src/utils.ts
|
|
1905
|
+
function isFurinPage(value) {
|
|
1906
|
+
return typeof value === "object" && value !== null && "__type" in value && value.__type === "FURIN_PAGE";
|
|
1907
|
+
}
|
|
1908
|
+
function isFurinRoute(value) {
|
|
1909
|
+
return typeof value === "object" && value !== null && "__type" in value && value.__type === "FURIN_ROUTE";
|
|
1910
|
+
}
|
|
1911
|
+
function collectRouteChainFromRoute(route) {
|
|
1912
|
+
const chain = [];
|
|
1913
|
+
let current = route;
|
|
1914
|
+
while (current) {
|
|
1915
|
+
chain.unshift(current);
|
|
1916
|
+
current = current.parent;
|
|
1917
|
+
}
|
|
1918
|
+
return chain;
|
|
1919
|
+
}
|
|
1920
|
+
function hasCycle(route) {
|
|
1921
|
+
const visited = new Set;
|
|
1922
|
+
let current = route;
|
|
1923
|
+
while (current) {
|
|
1924
|
+
if (visited.has(current)) {
|
|
1925
|
+
return true;
|
|
1926
|
+
}
|
|
1927
|
+
visited.add(current);
|
|
1928
|
+
current = current.parent;
|
|
1929
|
+
}
|
|
1930
|
+
return false;
|
|
1931
|
+
}
|
|
1932
|
+
function validateRouteChain(chain, root, pagePath) {
|
|
1933
|
+
const hasRoot = chain.some((r) => r === root);
|
|
1934
|
+
if (!hasRoot) {
|
|
1935
|
+
const location = pagePath ? `in ${pagePath}` : "";
|
|
1936
|
+
throw new Error(`[furin] Page ${location} must inherit from root route. ` + 'Add: import { route } from "./root"; and use route.page() or set parent: route');
|
|
1937
|
+
}
|
|
1938
|
+
for (const route of chain) {
|
|
1939
|
+
if (hasCycle(route)) {
|
|
1940
|
+
throw new Error("[furin] Cycle detected in route chain. A route cannot be its own ancestor.");
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// src/router.ts
|
|
1946
|
+
async function scanRootLayout(pagesDir) {
|
|
1947
|
+
const rootPath = `${pagesDir}/root.tsx`;
|
|
1948
|
+
const ctx = getCompileContext();
|
|
1949
|
+
if (!(existsSync7(rootPath) || ctx?.modules[rootPath])) {
|
|
1950
|
+
throw new Error("[furin] root.tsx: not found.");
|
|
1951
|
+
}
|
|
1952
|
+
const mod = ctx?.modules[rootPath] ?? await import(rootPath);
|
|
1953
|
+
const rootExport = mod.route ?? mod.default;
|
|
1954
|
+
if (!(rootExport && isFurinRoute(rootExport))) {
|
|
1955
|
+
throw new Error("[furin] root.tsx: createRoute() export not found.");
|
|
1956
|
+
}
|
|
1957
|
+
if (!rootExport.layout) {
|
|
1958
|
+
throw new Error("[furin] root.tsx: createRoute() has no layout.");
|
|
1959
|
+
}
|
|
1960
|
+
return { path: rootPath, route: rootExport };
|
|
1961
|
+
}
|
|
1962
|
+
async function collectPageFilePaths(dir) {
|
|
1963
|
+
const files = [];
|
|
1964
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
1965
|
+
const absolutePath = join8(dir, entry.name);
|
|
1966
|
+
if (entry.isDirectory()) {
|
|
1967
|
+
files.push(...await collectPageFilePaths(absolutePath));
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
if (entry.isFile()) {
|
|
1971
|
+
files.push(absolutePath);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
return files;
|
|
1975
|
+
}
|
|
1976
|
+
async function scanPageFiles(pagesDir, root) {
|
|
1977
|
+
const routes = [];
|
|
1978
|
+
for (const absolutePath of await collectPageFilePaths(pagesDir)) {
|
|
1979
|
+
if (![".tsx", ".ts", ".jsx", ".js"].some((ext) => absolutePath.endsWith(ext))) {
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
const relativePath = absolutePath.replace(`${pagesDir}/`, "");
|
|
1983
|
+
const fileName = parse(relativePath).name;
|
|
1984
|
+
if (fileName.startsWith("_") || fileName === "root") {
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
const ctx = getCompileContext();
|
|
1988
|
+
const pageMod = ctx?.modules[absolutePath] ?? await import(absolutePath);
|
|
1989
|
+
const page = pageMod.default;
|
|
1990
|
+
if (!isFurinPage(page)) {
|
|
1991
|
+
throw new Error(`[furin] ${relativePath}: no valid createRoute().page() export found`);
|
|
1992
|
+
}
|
|
1993
|
+
const routeChain = collectRouteChainFromRoute(page._route);
|
|
1994
|
+
validateRouteChain(routeChain, root.route, relativePath);
|
|
1995
|
+
routes.push({
|
|
1996
|
+
pattern: filePathToPattern(relativePath),
|
|
1997
|
+
page,
|
|
1998
|
+
path: absolutePath,
|
|
1999
|
+
routeChain,
|
|
2000
|
+
mode: resolveMode(page, routeChain)
|
|
2001
|
+
});
|
|
2002
|
+
}
|
|
2003
|
+
return routes;
|
|
2004
|
+
}
|
|
2005
|
+
async function scanPages(pagesDir) {
|
|
2006
|
+
const ctx = getCompileContext();
|
|
2007
|
+
if (ctx) {
|
|
2008
|
+
return loadProdRoutes(ctx);
|
|
2009
|
+
}
|
|
2010
|
+
const root = await scanRootLayout(pagesDir);
|
|
2011
|
+
const routes = await scanPageFiles(pagesDir, root);
|
|
2012
|
+
return { root, routes };
|
|
2013
|
+
}
|
|
2014
|
+
function loadProdRoutes(ctx) {
|
|
2015
|
+
const rootMod = ctx.modules[ctx.rootPath];
|
|
2016
|
+
const rootExport = rootMod.route ?? rootMod.default;
|
|
2017
|
+
if (!(rootExport && isFurinRoute(rootExport) && rootExport.layout)) {
|
|
2018
|
+
throw new Error("[furin] root.tsx: createRoute() with layout not found in CompileContext.");
|
|
2019
|
+
}
|
|
2020
|
+
const root = { path: ctx.rootPath, route: rootExport };
|
|
2021
|
+
const routes = [];
|
|
2022
|
+
for (const { pattern, path, mode } of ctx.routes) {
|
|
2023
|
+
const pageMod = ctx.modules[path];
|
|
2024
|
+
const page = pageMod.default;
|
|
2025
|
+
if (!isFurinPage(page)) {
|
|
2026
|
+
throw new Error(`[furin] ${path}: invalid page module in CompileContext.`);
|
|
2027
|
+
}
|
|
2028
|
+
const routeChain = collectRouteChainFromRoute(page._route);
|
|
2029
|
+
validateRouteChain(routeChain, root.route, path);
|
|
2030
|
+
routes.push({ pattern, page, path, routeChain, mode });
|
|
2031
|
+
}
|
|
2032
|
+
return { root, routes };
|
|
2033
|
+
}
|
|
2034
|
+
function resolveMode(page, routeChain) {
|
|
2035
|
+
const routeConfig = page._route;
|
|
2036
|
+
if (routeConfig.mode) {
|
|
2037
|
+
return routeConfig.mode;
|
|
2038
|
+
}
|
|
2039
|
+
const hasLoader = routeChain.some((r) => r.loader) || !!page.loader;
|
|
2040
|
+
if (!hasLoader) {
|
|
2041
|
+
return "ssg";
|
|
2042
|
+
}
|
|
2043
|
+
if (routeConfig.revalidate && routeConfig.revalidate > 0) {
|
|
2044
|
+
return "isr";
|
|
2045
|
+
}
|
|
2046
|
+
return "ssr";
|
|
2047
|
+
}
|
|
2048
|
+
function filePathToPattern(path) {
|
|
2049
|
+
const parts = path.replaceAll("\\", "/").split("/");
|
|
2050
|
+
const segments = [];
|
|
2051
|
+
for (const part of parts) {
|
|
2052
|
+
const name = parse(part).name;
|
|
2053
|
+
if (name === "index") {
|
|
2054
|
+
continue;
|
|
2055
|
+
}
|
|
2056
|
+
if (name.startsWith("[") && name.endsWith("]")) {
|
|
2057
|
+
const inner = name.slice(1, -1);
|
|
2058
|
+
if (inner.startsWith("...")) {
|
|
2059
|
+
segments.push("*");
|
|
2060
|
+
continue;
|
|
2061
|
+
}
|
|
2062
|
+
segments.push(`:${inner}`);
|
|
2063
|
+
continue;
|
|
2064
|
+
}
|
|
2065
|
+
segments.push(name);
|
|
2066
|
+
}
|
|
2067
|
+
return `/${segments.join("/")}`;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
// src/build/scan-server.ts
|
|
2071
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2072
|
+
import { parseSync as parseSync2 } from "oxc-parser";
|
|
2073
|
+
function scanFurinInstances(serverEntryPath) {
|
|
2074
|
+
const code = readFileSync3(serverEntryPath, "utf8");
|
|
2075
|
+
const { program, errors } = parseSync2(serverEntryPath, code);
|
|
2076
|
+
if (errors.length > 0) {
|
|
2077
|
+
return [];
|
|
2078
|
+
}
|
|
2079
|
+
const results = [];
|
|
2080
|
+
walkNode(program, results);
|
|
2081
|
+
return results;
|
|
2082
|
+
}
|
|
2083
|
+
function walkNode(node, out) {
|
|
2084
|
+
if (!node || typeof node !== "object")
|
|
2085
|
+
return;
|
|
2086
|
+
if (node.type === "CallExpression") {
|
|
2087
|
+
const callee = node.callee;
|
|
2088
|
+
const args = node.arguments;
|
|
2089
|
+
const isElyraCall = callee?.type === "Identifier" && callee.name === "furin";
|
|
2090
|
+
if (isElyraCall && Array.isArray(args) && args.length > 0) {
|
|
2091
|
+
const firstArg = args[0];
|
|
2092
|
+
if (firstArg?.type === "ObjectExpression") {
|
|
2093
|
+
const pagesDir = extractStringProperty(firstArg, "pagesDir");
|
|
2094
|
+
if (pagesDir !== null) {
|
|
2095
|
+
out.push(pagesDir);
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
for (const key of Object.keys(node)) {
|
|
2101
|
+
if (key === "type" || key === "start" || key === "end")
|
|
2102
|
+
continue;
|
|
2103
|
+
const child = node[key];
|
|
2104
|
+
if (Array.isArray(child)) {
|
|
2105
|
+
for (const item of child) {
|
|
2106
|
+
if (item && typeof item === "object") {
|
|
2107
|
+
walkNode(item, out);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
} else if (child && typeof child === "object") {
|
|
2111
|
+
walkNode(child, out);
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
function extractStringProperty(obj, propName) {
|
|
2116
|
+
const properties = obj.properties;
|
|
2117
|
+
if (!Array.isArray(properties))
|
|
2118
|
+
return null;
|
|
2119
|
+
for (const prop of properties) {
|
|
2120
|
+
if (prop.type !== "Property")
|
|
2121
|
+
continue;
|
|
2122
|
+
const key = prop.key;
|
|
2123
|
+
const value = prop.value;
|
|
2124
|
+
const keyMatches = key.type === "Identifier" && key.name === propName || key.type === "Literal" && key.value === propName;
|
|
2125
|
+
if (!keyMatches)
|
|
2126
|
+
continue;
|
|
2127
|
+
if (value?.type === "Literal" && typeof value.value === "string") {
|
|
2128
|
+
return value.value;
|
|
2129
|
+
}
|
|
2130
|
+
return null;
|
|
2131
|
+
}
|
|
2132
|
+
return null;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// src/build/index.ts
|
|
2136
|
+
var IMPLEMENTED_TARGETS = ["bun"];
|
|
2137
|
+
var BUILD_OUTPUT_DIR = ".furin/build";
|
|
2138
|
+
function resolvePagesDirFromServer(serverEntry, rootDir) {
|
|
2139
|
+
if (!serverEntry)
|
|
2140
|
+
return null;
|
|
2141
|
+
const detected = scanFurinInstances(serverEntry);
|
|
2142
|
+
if (detected.length === 0)
|
|
2143
|
+
return null;
|
|
2144
|
+
return resolve4(rootDir, detected[0]);
|
|
2145
|
+
}
|
|
2146
|
+
async function buildApp(options) {
|
|
2147
|
+
const rootDir = resolve4(options.rootDir ?? process.cwd());
|
|
2148
|
+
const buildRoot = join9(rootDir, BUILD_OUTPUT_DIR);
|
|
2149
|
+
const serverEntry = (() => {
|
|
2150
|
+
if (options.serverEntry) {
|
|
2151
|
+
const resolved = resolve4(rootDir, options.serverEntry);
|
|
2152
|
+
if (existsSync8(resolved))
|
|
2153
|
+
return resolved;
|
|
2154
|
+
}
|
|
2155
|
+
const serverEntry2 = resolve4(rootDir, "src/server.ts");
|
|
2156
|
+
if (!existsSync8(serverEntry2)) {
|
|
2157
|
+
throw new Error("[furin] Entrypoint server.ts not found");
|
|
2158
|
+
}
|
|
2159
|
+
return serverEntry2;
|
|
2160
|
+
})();
|
|
2161
|
+
const rawPagesDir = options.pagesDir ?? resolvePagesDirFromServer(serverEntry, rootDir) ?? "src/pages";
|
|
2162
|
+
const pagesDir = resolve4(rootDir, rawPagesDir);
|
|
2163
|
+
const requestedTargets = options.target === "all" ? [...IMPLEMENTED_TARGETS] : [options.target].map((target) => {
|
|
2164
|
+
if (!BUILD_TARGETS.includes(target)) {
|
|
2165
|
+
throw new Error(`[furin] Unsupported build target "${target}"`);
|
|
2166
|
+
}
|
|
2167
|
+
return target;
|
|
2168
|
+
});
|
|
2169
|
+
const { root, routes } = await scanPages(pagesDir);
|
|
2170
|
+
if (!root) {
|
|
2171
|
+
throw new Error("[furin] No root layout found. Create a root.tsx in your pages directory with a layout component.");
|
|
2172
|
+
}
|
|
2173
|
+
ensureDir(buildRoot);
|
|
2174
|
+
const manifest = {
|
|
2175
|
+
version: 1,
|
|
2176
|
+
generatedAt: new Date().toISOString(),
|
|
2177
|
+
rootDir: toPosixPath(rootDir),
|
|
2178
|
+
pagesDir: toPosixPath(relative3(rootDir, pagesDir)),
|
|
2179
|
+
rootPath: toPosixPath(relative3(rootDir, root.path)),
|
|
2180
|
+
serverEntry: serverEntry ? toPosixPath(relative3(rootDir, serverEntry)) : null,
|
|
2181
|
+
routes: routes.map((route) => toBuildRouteManifestEntry(route, rootDir)),
|
|
2182
|
+
targets: {}
|
|
2183
|
+
};
|
|
2184
|
+
for (const target of requestedTargets) {
|
|
2185
|
+
switch (target) {
|
|
2186
|
+
case "bun":
|
|
2187
|
+
manifest.targets.bun = await buildBunTarget(routes, rootDir, buildRoot, root.path, serverEntry, options);
|
|
2188
|
+
break;
|
|
2189
|
+
case "node":
|
|
2190
|
+
case "vercel":
|
|
2191
|
+
case "cloudflare":
|
|
2192
|
+
throw new Error(`[furin] \`--target ${target}\` is planned but not implemented yet in this branch.`);
|
|
2193
|
+
default:
|
|
2194
|
+
throw new Error(`[furin] Unsupported build target "${target}"`);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
writeFileSync7(join9(buildRoot, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
|
|
2198
|
+
`);
|
|
2199
|
+
return {
|
|
2200
|
+
manifest,
|
|
2201
|
+
targets: manifest.targets
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
export {
|
|
2205
|
+
writeRouteTypes,
|
|
2206
|
+
writeDevFiles,
|
|
2207
|
+
schemaToTypeString,
|
|
2208
|
+
patternToTypeString,
|
|
2209
|
+
buildClient,
|
|
2210
|
+
buildApp,
|
|
2211
|
+
BUILD_OUTPUT_DIR
|
|
2212
|
+
};
|