@flexireact/core 3.0.2 → 3.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli/index.js +14 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/core/build/index.js +689 -0
- package/dist/core/build/index.js.map +1 -0
- package/dist/core/config.js +86 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/start-dev.js +3092 -0
- package/dist/core/start-dev.js.map +1 -0
- package/dist/core/start-prod.js +3092 -0
- package/dist/core/start-prod.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
// core/build/index.ts
|
|
2
|
+
import * as esbuild from "esbuild";
|
|
3
|
+
import fs3 from "fs";
|
|
4
|
+
import path3 from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
// core/utils.ts
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import crypto from "crypto";
|
|
11
|
+
function generateHash(content) {
|
|
12
|
+
return crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
|
|
13
|
+
}
|
|
14
|
+
function findFiles(dir, pattern, files = []) {
|
|
15
|
+
if (!fs.existsSync(dir)) return files;
|
|
16
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const fullPath = path.join(dir, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
findFiles(fullPath, pattern, files);
|
|
21
|
+
} else if (pattern.test(entry.name)) {
|
|
22
|
+
files.push(fullPath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return files;
|
|
26
|
+
}
|
|
27
|
+
function ensureDir(dir) {
|
|
28
|
+
if (!fs.existsSync(dir)) {
|
|
29
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function cleanDir(dir) {
|
|
33
|
+
if (fs.existsSync(dir)) {
|
|
34
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
35
|
+
}
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
function isServerComponent(filePath) {
|
|
39
|
+
try {
|
|
40
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
41
|
+
const firstLine = content.split("\n")[0].trim();
|
|
42
|
+
return firstLine === "'use server'" || firstLine === '"use server"';
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function isClientComponent(filePath) {
|
|
48
|
+
try {
|
|
49
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
50
|
+
const firstLine = content.split("\n")[0].trim();
|
|
51
|
+
return firstLine === "'use client'" || firstLine === '"use client"';
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function isIsland(filePath) {
|
|
57
|
+
try {
|
|
58
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
59
|
+
const firstLine = content.split("\n")[0].trim();
|
|
60
|
+
return firstLine === "'use island'" || firstLine === '"use island"';
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// core/router/index.ts
|
|
67
|
+
import fs2 from "fs";
|
|
68
|
+
import path2 from "path";
|
|
69
|
+
var RouteType = {
|
|
70
|
+
PAGE: "page",
|
|
71
|
+
API: "api",
|
|
72
|
+
LAYOUT: "layout",
|
|
73
|
+
LOADING: "loading",
|
|
74
|
+
ERROR: "error",
|
|
75
|
+
NOT_FOUND: "not-found"
|
|
76
|
+
};
|
|
77
|
+
function buildRouteTree(pagesDir, layoutsDir, appDir = null, routesDir = null) {
|
|
78
|
+
const projectRoot = path2.dirname(pagesDir);
|
|
79
|
+
const routes = {
|
|
80
|
+
pages: [],
|
|
81
|
+
api: [],
|
|
82
|
+
layouts: /* @__PURE__ */ new Map(),
|
|
83
|
+
tree: {},
|
|
84
|
+
appRoutes: [],
|
|
85
|
+
// Next.js style app router routes
|
|
86
|
+
flexiRoutes: []
|
|
87
|
+
// FlexiReact v2 routes/ directory
|
|
88
|
+
};
|
|
89
|
+
const routesDirPath = routesDir || path2.join(projectRoot, "routes");
|
|
90
|
+
if (fs2.existsSync(routesDirPath)) {
|
|
91
|
+
scanRoutesDirectory(routesDirPath, routesDirPath, routes);
|
|
92
|
+
}
|
|
93
|
+
const appDirPath = appDir || path2.join(projectRoot, "app");
|
|
94
|
+
if (fs2.existsSync(appDirPath)) {
|
|
95
|
+
scanAppDirectory(appDirPath, appDirPath, routes);
|
|
96
|
+
}
|
|
97
|
+
if (fs2.existsSync(pagesDir)) {
|
|
98
|
+
scanDirectory(pagesDir, pagesDir, routes);
|
|
99
|
+
}
|
|
100
|
+
if (fs2.existsSync(layoutsDir)) {
|
|
101
|
+
scanLayouts(layoutsDir, routes.layouts);
|
|
102
|
+
}
|
|
103
|
+
const rootLayoutPath = path2.join(appDirPath, "layout.tsx");
|
|
104
|
+
const rootLayoutPathJs = path2.join(appDirPath, "layout.jsx");
|
|
105
|
+
if (fs2.existsSync(rootLayoutPath)) {
|
|
106
|
+
routes.rootLayout = rootLayoutPath;
|
|
107
|
+
} else if (fs2.existsSync(rootLayoutPathJs)) {
|
|
108
|
+
routes.rootLayout = rootLayoutPathJs;
|
|
109
|
+
}
|
|
110
|
+
routes.tree = buildTree([...routes.flexiRoutes, ...routes.appRoutes, ...routes.pages]);
|
|
111
|
+
return routes;
|
|
112
|
+
}
|
|
113
|
+
function scanRoutesDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null, parentMiddleware = null) {
|
|
114
|
+
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
115
|
+
let layoutFile = null;
|
|
116
|
+
let loadingFile = null;
|
|
117
|
+
let errorFile = null;
|
|
118
|
+
let middlewareFile = null;
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (entry.isFile()) {
|
|
121
|
+
const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, "");
|
|
122
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
123
|
+
const ext = path2.extname(entry.name);
|
|
124
|
+
if (name === "layout") layoutFile = fullPath;
|
|
125
|
+
if (name === "loading") loadingFile = fullPath;
|
|
126
|
+
if (name === "error") errorFile = fullPath;
|
|
127
|
+
if (name === "_middleware" || name === "middleware") middlewareFile = fullPath;
|
|
128
|
+
if (["layout", "loading", "error", "not-found", "_middleware", "middleware"].includes(name)) continue;
|
|
129
|
+
if (![".tsx", ".jsx", ".ts", ".js"].includes(ext)) continue;
|
|
130
|
+
const relativePath = path2.relative(baseDir, currentDir);
|
|
131
|
+
const isApiRoute = relativePath.startsWith("api") || relativePath.startsWith("api/");
|
|
132
|
+
if (isApiRoute && [".ts", ".js"].includes(ext)) {
|
|
133
|
+
const apiPath = "/" + [...parentSegments, name === "index" ? "" : name].filter(Boolean).join("/");
|
|
134
|
+
routes.api.push({
|
|
135
|
+
type: RouteType.API,
|
|
136
|
+
path: apiPath.replace(/\/+/g, "/") || "/",
|
|
137
|
+
filePath: fullPath,
|
|
138
|
+
pattern: createRoutePattern(apiPath),
|
|
139
|
+
segments: [...parentSegments, name === "index" ? "" : name].filter(Boolean)
|
|
140
|
+
});
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if ([".tsx", ".jsx"].includes(ext)) {
|
|
144
|
+
let routePath;
|
|
145
|
+
if (name === "home" && parentSegments.length === 0) {
|
|
146
|
+
routePath = "/";
|
|
147
|
+
} else if (name === "index") {
|
|
148
|
+
routePath = "/" + parentSegments.join("/") || "/";
|
|
149
|
+
} else if (name.startsWith("[") && name.endsWith("]")) {
|
|
150
|
+
const paramName = name.slice(1, -1);
|
|
151
|
+
if (paramName.startsWith("...")) {
|
|
152
|
+
routePath = "/" + [...parentSegments, "*" + paramName.slice(3)].join("/");
|
|
153
|
+
} else {
|
|
154
|
+
routePath = "/" + [...parentSegments, ":" + paramName].join("/");
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
routePath = "/" + [...parentSegments, name].join("/");
|
|
158
|
+
}
|
|
159
|
+
routes.flexiRoutes.push({
|
|
160
|
+
type: RouteType.PAGE,
|
|
161
|
+
path: routePath.replace(/\/+/g, "/"),
|
|
162
|
+
filePath: fullPath,
|
|
163
|
+
pattern: createRoutePattern(routePath),
|
|
164
|
+
segments: routePath.split("/").filter(Boolean),
|
|
165
|
+
layout: layoutFile || parentLayout,
|
|
166
|
+
loading: loadingFile,
|
|
167
|
+
error: errorFile,
|
|
168
|
+
middleware: middlewareFile || parentMiddleware,
|
|
169
|
+
isFlexiRouter: true,
|
|
170
|
+
isServerComponent: isServerComponent(fullPath),
|
|
171
|
+
isClientComponent: isClientComponent(fullPath),
|
|
172
|
+
isIsland: isIsland(fullPath)
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
if (entry.isDirectory()) {
|
|
179
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
180
|
+
const dirName = entry.name;
|
|
181
|
+
if (dirName.startsWith("_") || dirName.startsWith(".")) continue;
|
|
182
|
+
const isGroup = dirName.startsWith("(") && dirName.endsWith(")");
|
|
183
|
+
let segmentName = dirName;
|
|
184
|
+
if (dirName.startsWith("[") && dirName.endsWith("]")) {
|
|
185
|
+
const paramName = dirName.slice(1, -1);
|
|
186
|
+
if (paramName.startsWith("...")) {
|
|
187
|
+
segmentName = "*" + paramName.slice(3);
|
|
188
|
+
} else {
|
|
189
|
+
segmentName = ":" + paramName;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
|
|
193
|
+
const newLayout = layoutFile || parentLayout;
|
|
194
|
+
const newMiddleware = middlewareFile || parentMiddleware;
|
|
195
|
+
scanRoutesDirectory(baseDir, fullPath, routes, newSegments, newLayout, newMiddleware);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null, parentMiddleware = null) {
|
|
200
|
+
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
201
|
+
const specialFiles = {
|
|
202
|
+
page: null,
|
|
203
|
+
layout: null,
|
|
204
|
+
loading: null,
|
|
205
|
+
error: null,
|
|
206
|
+
notFound: null,
|
|
207
|
+
template: null,
|
|
208
|
+
middleware: null
|
|
209
|
+
};
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (entry.isFile()) {
|
|
212
|
+
const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, "");
|
|
213
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
214
|
+
if (name === "page") specialFiles.page = fullPath;
|
|
215
|
+
if (name === "layout") specialFiles.layout = fullPath;
|
|
216
|
+
if (name === "loading") specialFiles.loading = fullPath;
|
|
217
|
+
if (name === "error") specialFiles.error = fullPath;
|
|
218
|
+
if (name === "not-found") specialFiles.notFound = fullPath;
|
|
219
|
+
if (name === "template") specialFiles.template = fullPath;
|
|
220
|
+
if (name === "middleware" || name === "_middleware") specialFiles.middleware = fullPath;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (specialFiles.page) {
|
|
224
|
+
const routePath = "/" + parentSegments.join("/") || "/";
|
|
225
|
+
routes.appRoutes.push({
|
|
226
|
+
type: RouteType.PAGE,
|
|
227
|
+
path: routePath.replace(/\/+/g, "/"),
|
|
228
|
+
filePath: specialFiles.page,
|
|
229
|
+
pattern: createRoutePattern(routePath),
|
|
230
|
+
segments: parentSegments,
|
|
231
|
+
layout: specialFiles.layout || parentLayout,
|
|
232
|
+
loading: specialFiles.loading,
|
|
233
|
+
error: specialFiles.error,
|
|
234
|
+
notFound: specialFiles.notFound,
|
|
235
|
+
template: specialFiles.template,
|
|
236
|
+
middleware: specialFiles.middleware || parentMiddleware,
|
|
237
|
+
isAppRouter: true,
|
|
238
|
+
isServerComponent: isServerComponent(specialFiles.page),
|
|
239
|
+
isClientComponent: isClientComponent(specialFiles.page),
|
|
240
|
+
isIsland: isIsland(specialFiles.page)
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
for (const entry of entries) {
|
|
244
|
+
if (entry.isDirectory()) {
|
|
245
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
246
|
+
const isGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
|
|
247
|
+
let segmentName = entry.name;
|
|
248
|
+
if (entry.name.startsWith("[") && entry.name.endsWith("]")) {
|
|
249
|
+
segmentName = ":" + entry.name.slice(1, -1);
|
|
250
|
+
if (entry.name.startsWith("[...")) {
|
|
251
|
+
segmentName = "*" + entry.name.slice(4, -1);
|
|
252
|
+
}
|
|
253
|
+
if (entry.name.startsWith("[[...")) {
|
|
254
|
+
segmentName = "*" + entry.name.slice(5, -2);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
|
|
258
|
+
const newLayout = specialFiles.layout || parentLayout;
|
|
259
|
+
const newMiddleware = specialFiles.middleware || parentMiddleware;
|
|
260
|
+
scanAppDirectory(baseDir, fullPath, routes, newSegments, newLayout, newMiddleware);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function scanDirectory(baseDir, currentDir, routes, parentSegments = []) {
|
|
265
|
+
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
266
|
+
const specialFiles = {
|
|
267
|
+
layout: null,
|
|
268
|
+
loading: null,
|
|
269
|
+
error: null,
|
|
270
|
+
notFound: null
|
|
271
|
+
};
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
if (entry.isFile()) {
|
|
274
|
+
const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, "");
|
|
275
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
276
|
+
if (name === "layout") specialFiles.layout = fullPath;
|
|
277
|
+
if (name === "loading") specialFiles.loading = fullPath;
|
|
278
|
+
if (name === "error") specialFiles.error = fullPath;
|
|
279
|
+
if (name === "not-found" || name === "404") specialFiles.notFound = fullPath;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
for (const entry of entries) {
|
|
283
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
284
|
+
const relativePath = path2.relative(baseDir, fullPath);
|
|
285
|
+
if (entry.isDirectory()) {
|
|
286
|
+
const isGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
|
|
287
|
+
const newSegments = isGroup ? parentSegments : [...parentSegments, entry.name];
|
|
288
|
+
scanDirectory(baseDir, fullPath, routes, newSegments);
|
|
289
|
+
} else if (entry.isFile()) {
|
|
290
|
+
const ext = path2.extname(entry.name);
|
|
291
|
+
const baseName = path2.basename(entry.name, ext);
|
|
292
|
+
if (["layout", "loading", "error", "not-found", "404"].includes(baseName)) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if ([".jsx", ".js", ".tsx", ".ts"].includes(ext)) {
|
|
296
|
+
const isApi = relativePath.startsWith("api" + path2.sep) || relativePath.startsWith("api/");
|
|
297
|
+
if (isApi && [".js", ".ts"].includes(ext)) {
|
|
298
|
+
routes.api.push(createRoute(fullPath, baseDir, specialFiles, RouteType.API));
|
|
299
|
+
} else if (!isApi && [".jsx", ".tsx"].includes(ext)) {
|
|
300
|
+
routes.pages.push(createRoute(fullPath, baseDir, specialFiles, RouteType.PAGE));
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function createRoute(filePath, baseDir, specialFiles, type) {
|
|
307
|
+
const relativePath = path2.relative(baseDir, filePath);
|
|
308
|
+
const routePath = filePathToRoute(relativePath);
|
|
309
|
+
return {
|
|
310
|
+
type,
|
|
311
|
+
path: routePath,
|
|
312
|
+
filePath,
|
|
313
|
+
pattern: createRoutePattern(routePath),
|
|
314
|
+
segments: routePath.split("/").filter(Boolean),
|
|
315
|
+
layout: specialFiles.layout,
|
|
316
|
+
loading: specialFiles.loading,
|
|
317
|
+
error: specialFiles.error,
|
|
318
|
+
notFound: specialFiles.notFound,
|
|
319
|
+
isServerComponent: isServerComponent(filePath),
|
|
320
|
+
isClientComponent: isClientComponent(filePath),
|
|
321
|
+
isIsland: isIsland(filePath)
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function scanLayouts(layoutsDir, layoutsMap) {
|
|
325
|
+
const entries = fs2.readdirSync(layoutsDir, { withFileTypes: true });
|
|
326
|
+
for (const entry of entries) {
|
|
327
|
+
if (entry.isFile() && /\.(jsx|tsx)$/.test(entry.name)) {
|
|
328
|
+
const name = entry.name.replace(/\.(jsx|tsx)$/, "");
|
|
329
|
+
layoutsMap.set(name, path2.join(layoutsDir, entry.name));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
function filePathToRoute(filePath) {
|
|
334
|
+
let route = filePath.replace(/\\/g, "/");
|
|
335
|
+
route = route.replace(/\.(jsx|js|tsx|ts)$/, "");
|
|
336
|
+
route = route.replace(/\[\.\.\.([^\]]+)\]/g, "*$1");
|
|
337
|
+
route = route.replace(/\[([^\]]+)\]/g, ":$1");
|
|
338
|
+
if (route.endsWith("/index")) {
|
|
339
|
+
route = route.slice(0, -6) || "/";
|
|
340
|
+
} else if (route === "index") {
|
|
341
|
+
route = "/";
|
|
342
|
+
}
|
|
343
|
+
route = route.replace(/\/?\([^)]+\)\/?/g, "/");
|
|
344
|
+
if (!route.startsWith("/")) {
|
|
345
|
+
route = "/" + route;
|
|
346
|
+
}
|
|
347
|
+
route = route.replace(/\/+/g, "/");
|
|
348
|
+
return route;
|
|
349
|
+
}
|
|
350
|
+
function createRoutePattern(routePath) {
|
|
351
|
+
let pattern = routePath.replace(/\*[^/]*/g, "(.*)").replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
|
|
352
|
+
return new RegExp(`^${pattern}$`);
|
|
353
|
+
}
|
|
354
|
+
function buildTree(routes) {
|
|
355
|
+
const tree = { children: {}, routes: [] };
|
|
356
|
+
for (const route of routes) {
|
|
357
|
+
let current = tree;
|
|
358
|
+
for (const segment of route.segments) {
|
|
359
|
+
if (!current.children[segment]) {
|
|
360
|
+
current.children[segment] = { children: {}, routes: [] };
|
|
361
|
+
}
|
|
362
|
+
current = current.children[segment];
|
|
363
|
+
}
|
|
364
|
+
current.routes.push(route);
|
|
365
|
+
}
|
|
366
|
+
return tree;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// core/build/index.ts
|
|
370
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
371
|
+
var __dirname2 = path3.dirname(__filename2);
|
|
372
|
+
var BuildMode = {
|
|
373
|
+
DEVELOPMENT: "development",
|
|
374
|
+
PRODUCTION: "production"
|
|
375
|
+
};
|
|
376
|
+
async function build2(options) {
|
|
377
|
+
const {
|
|
378
|
+
projectRoot,
|
|
379
|
+
config,
|
|
380
|
+
mode = BuildMode.PRODUCTION,
|
|
381
|
+
analyze = false
|
|
382
|
+
} = options;
|
|
383
|
+
const startTime = Date.now();
|
|
384
|
+
const outDir = config.outDir;
|
|
385
|
+
const isDev = mode === BuildMode.DEVELOPMENT;
|
|
386
|
+
console.log("\n\u26A1 FlexiReact Build\n");
|
|
387
|
+
console.log(` Mode: ${mode}`);
|
|
388
|
+
console.log(` Output: ${outDir}
|
|
389
|
+
`);
|
|
390
|
+
cleanDir(outDir);
|
|
391
|
+
ensureDir(path3.join(outDir, "client"));
|
|
392
|
+
ensureDir(path3.join(outDir, "server"));
|
|
393
|
+
ensureDir(path3.join(outDir, "static"));
|
|
394
|
+
const routes = buildRouteTree(config.pagesDir, config.layoutsDir);
|
|
395
|
+
const clientEntries = findClientEntries(config.pagesDir, config.layoutsDir);
|
|
396
|
+
console.log("\u{1F4E6} Building client bundle...");
|
|
397
|
+
const clientResult = await buildClient({
|
|
398
|
+
entries: clientEntries,
|
|
399
|
+
outDir: path3.join(outDir, "client"),
|
|
400
|
+
config,
|
|
401
|
+
isDev
|
|
402
|
+
});
|
|
403
|
+
console.log("\u{1F4E6} Building server bundle...");
|
|
404
|
+
const serverResult = await buildServer({
|
|
405
|
+
pagesDir: config.pagesDir,
|
|
406
|
+
layoutsDir: config.layoutsDir,
|
|
407
|
+
outDir: path3.join(outDir, "server"),
|
|
408
|
+
config,
|
|
409
|
+
isDev
|
|
410
|
+
});
|
|
411
|
+
console.log("\u{1F4C1} Copying public assets...");
|
|
412
|
+
await copyPublicAssets(config.publicDir, path3.join(outDir, "static"));
|
|
413
|
+
const manifest = generateManifest({
|
|
414
|
+
routes,
|
|
415
|
+
clientResult,
|
|
416
|
+
serverResult,
|
|
417
|
+
config
|
|
418
|
+
});
|
|
419
|
+
fs3.writeFileSync(
|
|
420
|
+
path3.join(outDir, "manifest.json"),
|
|
421
|
+
JSON.stringify(manifest, null, 2)
|
|
422
|
+
);
|
|
423
|
+
const duration = Date.now() - startTime;
|
|
424
|
+
console.log("\n\u2728 Build complete!\n");
|
|
425
|
+
console.log(` Duration: ${duration}ms`);
|
|
426
|
+
console.log(` Client chunks: ${clientResult.outputs.length}`);
|
|
427
|
+
console.log(` Server modules: ${serverResult.outputs.length}`);
|
|
428
|
+
console.log("");
|
|
429
|
+
let analysis = null;
|
|
430
|
+
if (analyze) {
|
|
431
|
+
analysis = generateBundleAnalysis(clientResult, serverResult, outDir);
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
success: true,
|
|
435
|
+
duration,
|
|
436
|
+
manifest,
|
|
437
|
+
clientResult,
|
|
438
|
+
serverResult,
|
|
439
|
+
analysis
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
function generateBundleAnalysis(clientResult, serverResult, outDir) {
|
|
443
|
+
const files = {};
|
|
444
|
+
let totalSize = 0;
|
|
445
|
+
let totalGzipSize = 0;
|
|
446
|
+
for (const output of clientResult.outputs || []) {
|
|
447
|
+
if (output.path && fs3.existsSync(output.path)) {
|
|
448
|
+
const stat = fs3.statSync(output.path);
|
|
449
|
+
const relativePath = path3.relative(outDir, output.path);
|
|
450
|
+
const gzipSize = Math.round(stat.size * 0.3);
|
|
451
|
+
files[relativePath] = {
|
|
452
|
+
size: stat.size,
|
|
453
|
+
gzipSize
|
|
454
|
+
};
|
|
455
|
+
totalSize += stat.size;
|
|
456
|
+
totalGzipSize += gzipSize;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
for (const output of serverResult.outputs || []) {
|
|
460
|
+
if (output.path && fs3.existsSync(output.path)) {
|
|
461
|
+
const stat = fs3.statSync(output.path);
|
|
462
|
+
const relativePath = path3.relative(outDir, output.path);
|
|
463
|
+
files[relativePath] = {
|
|
464
|
+
size: stat.size
|
|
465
|
+
};
|
|
466
|
+
totalSize += stat.size;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return {
|
|
470
|
+
files,
|
|
471
|
+
totalSize,
|
|
472
|
+
totalGzipSize,
|
|
473
|
+
clientSize: clientResult.outputs?.reduce((sum, o) => {
|
|
474
|
+
if (o.path && fs3.existsSync(o.path)) {
|
|
475
|
+
return sum + fs3.statSync(o.path).size;
|
|
476
|
+
}
|
|
477
|
+
return sum;
|
|
478
|
+
}, 0) || 0,
|
|
479
|
+
serverSize: serverResult.outputs?.reduce((sum, o) => {
|
|
480
|
+
if (o.path && fs3.existsSync(o.path)) {
|
|
481
|
+
return sum + fs3.statSync(o.path).size;
|
|
482
|
+
}
|
|
483
|
+
return sum;
|
|
484
|
+
}, 0) || 0
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function findClientEntries(pagesDir, layoutsDir) {
|
|
488
|
+
const entries = [];
|
|
489
|
+
const dirs = [pagesDir, layoutsDir].filter((d) => fs3.existsSync(d));
|
|
490
|
+
for (const dir of dirs) {
|
|
491
|
+
const files = findFiles(dir, /\.(jsx|tsx)$/);
|
|
492
|
+
for (const file of files) {
|
|
493
|
+
if (isClientComponent(file) || isIsland(file)) {
|
|
494
|
+
entries.push(file);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return entries;
|
|
499
|
+
}
|
|
500
|
+
async function buildClient(options) {
|
|
501
|
+
const { entries, outDir, config, isDev } = options;
|
|
502
|
+
if (entries.length === 0) {
|
|
503
|
+
return { outputs: [] };
|
|
504
|
+
}
|
|
505
|
+
const entryPoints = {};
|
|
506
|
+
for (const entry of entries) {
|
|
507
|
+
const name = path3.basename(entry, path3.extname(entry));
|
|
508
|
+
const hash = generateHash(entry);
|
|
509
|
+
entryPoints[`${name}-${hash}`] = entry;
|
|
510
|
+
}
|
|
511
|
+
const runtimePath = path3.join(__dirname2, "..", "client", "runtime.js");
|
|
512
|
+
if (fs3.existsSync(runtimePath)) {
|
|
513
|
+
entryPoints["runtime"] = runtimePath;
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
const result = await esbuild.build({
|
|
517
|
+
entryPoints,
|
|
518
|
+
bundle: true,
|
|
519
|
+
splitting: true,
|
|
520
|
+
format: "esm",
|
|
521
|
+
outdir: outDir,
|
|
522
|
+
minify: !isDev && config.build.minify,
|
|
523
|
+
sourcemap: config.build.sourcemap,
|
|
524
|
+
target: config.build.target,
|
|
525
|
+
jsx: "automatic",
|
|
526
|
+
jsxImportSource: "react",
|
|
527
|
+
metafile: true,
|
|
528
|
+
external: [],
|
|
529
|
+
define: {
|
|
530
|
+
"process.env.NODE_ENV": JSON.stringify(isDev ? "development" : "production")
|
|
531
|
+
},
|
|
532
|
+
loader: {
|
|
533
|
+
".js": "jsx",
|
|
534
|
+
".jsx": "jsx",
|
|
535
|
+
".ts": "tsx",
|
|
536
|
+
".tsx": "tsx"
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
const outputs = Object.keys(result.metafile.outputs).map((file) => ({
|
|
540
|
+
file: path3.basename(file),
|
|
541
|
+
size: result.metafile.outputs[file].bytes
|
|
542
|
+
}));
|
|
543
|
+
return { outputs, metafile: result.metafile };
|
|
544
|
+
} catch (error) {
|
|
545
|
+
console.error("Client build failed:", error);
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async function buildServer(options) {
|
|
550
|
+
const { pagesDir, layoutsDir, outDir, config, isDev } = options;
|
|
551
|
+
const entries = [];
|
|
552
|
+
for (const dir of [pagesDir, layoutsDir]) {
|
|
553
|
+
if (fs3.existsSync(dir)) {
|
|
554
|
+
entries.push(...findFiles(dir, /\.(jsx|tsx|js|ts)$/));
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (entries.length === 0) {
|
|
558
|
+
return { outputs: [] };
|
|
559
|
+
}
|
|
560
|
+
const entryPoints = {};
|
|
561
|
+
for (const entry of entries) {
|
|
562
|
+
const relativePath = path3.relative(pagesDir, entry);
|
|
563
|
+
const name = relativePath.replace(/[\/\\]/g, "_").replace(/\.(jsx|tsx|js|ts)$/, "");
|
|
564
|
+
entryPoints[name] = entry;
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
const result = await esbuild.build({
|
|
568
|
+
entryPoints,
|
|
569
|
+
bundle: true,
|
|
570
|
+
format: "esm",
|
|
571
|
+
platform: "node",
|
|
572
|
+
outdir: outDir,
|
|
573
|
+
minify: false,
|
|
574
|
+
// Keep server code readable
|
|
575
|
+
sourcemap: true,
|
|
576
|
+
target: "node18",
|
|
577
|
+
jsx: "automatic",
|
|
578
|
+
jsxImportSource: "react",
|
|
579
|
+
metafile: true,
|
|
580
|
+
packages: "external",
|
|
581
|
+
// Don't bundle node_modules
|
|
582
|
+
loader: {
|
|
583
|
+
".js": "jsx",
|
|
584
|
+
".jsx": "jsx",
|
|
585
|
+
".ts": "tsx",
|
|
586
|
+
".tsx": "tsx"
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
const outputs = Object.keys(result.metafile.outputs).map((file) => ({
|
|
590
|
+
file: path3.basename(file),
|
|
591
|
+
size: result.metafile.outputs[file].bytes
|
|
592
|
+
}));
|
|
593
|
+
return { outputs, metafile: result.metafile };
|
|
594
|
+
} catch (error) {
|
|
595
|
+
console.error("Server build failed:", error);
|
|
596
|
+
throw error;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
async function copyPublicAssets(publicDir, outDir) {
|
|
600
|
+
if (!fs3.existsSync(publicDir)) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
const copyRecursive = (src, dest) => {
|
|
604
|
+
const entries = fs3.readdirSync(src, { withFileTypes: true });
|
|
605
|
+
ensureDir(dest);
|
|
606
|
+
for (const entry of entries) {
|
|
607
|
+
const srcPath = path3.join(src, entry.name);
|
|
608
|
+
const destPath = path3.join(dest, entry.name);
|
|
609
|
+
if (entry.isDirectory()) {
|
|
610
|
+
copyRecursive(srcPath, destPath);
|
|
611
|
+
} else {
|
|
612
|
+
fs3.copyFileSync(srcPath, destPath);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
copyRecursive(publicDir, outDir);
|
|
617
|
+
}
|
|
618
|
+
function generateManifest(options) {
|
|
619
|
+
const { routes, clientResult, serverResult, config } = options;
|
|
620
|
+
return {
|
|
621
|
+
version: "2.0.0",
|
|
622
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
623
|
+
routes: {
|
|
624
|
+
pages: routes.pages.map((r) => ({
|
|
625
|
+
path: r.path,
|
|
626
|
+
file: r.filePath,
|
|
627
|
+
hasLayout: !!r.layout,
|
|
628
|
+
hasLoading: !!r.loading,
|
|
629
|
+
hasError: !!r.error
|
|
630
|
+
})),
|
|
631
|
+
api: routes.api.map((r) => ({
|
|
632
|
+
path: r.path,
|
|
633
|
+
file: r.filePath
|
|
634
|
+
}))
|
|
635
|
+
},
|
|
636
|
+
client: {
|
|
637
|
+
chunks: clientResult.outputs || []
|
|
638
|
+
},
|
|
639
|
+
server: {
|
|
640
|
+
modules: serverResult.outputs || []
|
|
641
|
+
},
|
|
642
|
+
config: {
|
|
643
|
+
islands: config.islands.enabled,
|
|
644
|
+
rsc: config.rsc.enabled
|
|
645
|
+
}
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
async function buildDev(options) {
|
|
649
|
+
const { projectRoot, config, onChange } = options;
|
|
650
|
+
const outDir = config.outDir;
|
|
651
|
+
ensureDir(outDir);
|
|
652
|
+
const ctx = await esbuild.context({
|
|
653
|
+
entryPoints: findFiles(config.pagesDir, /\.(jsx|tsx)$/),
|
|
654
|
+
bundle: true,
|
|
655
|
+
format: "esm",
|
|
656
|
+
outdir: path3.join(outDir, "dev"),
|
|
657
|
+
sourcemap: true,
|
|
658
|
+
jsx: "automatic",
|
|
659
|
+
jsxImportSource: "react",
|
|
660
|
+
loader: {
|
|
661
|
+
".js": "jsx",
|
|
662
|
+
".jsx": "jsx"
|
|
663
|
+
},
|
|
664
|
+
plugins: [{
|
|
665
|
+
name: "flexi-watch",
|
|
666
|
+
setup(build3) {
|
|
667
|
+
build3.onEnd((result) => {
|
|
668
|
+
if (result.errors.length === 0) {
|
|
669
|
+
onChange?.();
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}]
|
|
674
|
+
});
|
|
675
|
+
await ctx.watch();
|
|
676
|
+
return ctx;
|
|
677
|
+
}
|
|
678
|
+
var build_default = {
|
|
679
|
+
build: build2,
|
|
680
|
+
buildDev,
|
|
681
|
+
BuildMode
|
|
682
|
+
};
|
|
683
|
+
export {
|
|
684
|
+
BuildMode,
|
|
685
|
+
build2 as build,
|
|
686
|
+
buildDev,
|
|
687
|
+
build_default as default
|
|
688
|
+
};
|
|
689
|
+
//# sourceMappingURL=index.js.map
|