@lolyjs/core 0.1.0-alpha.9 → 0.2.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ declare const WINDOW_DATA_KEY = "__FW_DATA__";
2
+
3
+ type InitialData = {
4
+ pathname: string;
5
+ params: Record<string, string>;
6
+ props: Record<string, any>;
7
+ metadata?: {
8
+ title?: string;
9
+ description?: string;
10
+ } | null;
11
+ notFound?: boolean;
12
+ error?: boolean;
13
+ theme?: string;
14
+ };
15
+ declare global {
16
+ interface Window {
17
+ [WINDOW_DATA_KEY]?: InitialData;
18
+ }
19
+ }
20
+ type ClientLoadedComponents = {
21
+ Page: React.ComponentType<any>;
22
+ layouts: React.ComponentType<any>[];
23
+ };
24
+ type ClientRouteLoaded = {
25
+ pattern: string;
26
+ paramNames: string[];
27
+ load: () => Promise<ClientLoadedComponents>;
28
+ };
29
+ type ClientRouteMatch = {
30
+ route: ClientRouteLoaded;
31
+ params: Record<string, string>;
32
+ };
33
+ type RouteViewState = {
34
+ url: string;
35
+ route: ClientRouteLoaded | null;
36
+ params: Record<string, string>;
37
+ components: ClientLoadedComponents | null;
38
+ props: Record<string, any>;
39
+ };
40
+
41
+ /**
42
+ * Bootstraps the client-side application.
43
+ *
44
+ * @param routes - Array of client routes
45
+ * @param notFoundRoute - Not-found route definition
46
+ * @param errorRoute - Error route definition
47
+ */
48
+ declare function bootstrapClient(routes: ClientRouteLoaded[], notFoundRoute: ClientRouteLoaded | null, errorRoute?: ClientRouteLoaded | null): void;
49
+
50
+ export { type ClientRouteLoaded as C, type InitialData as I, type RouteViewState as R, type ClientLoadedComponents as a, type ClientRouteMatch as b, bootstrapClient as c };
@@ -0,0 +1,50 @@
1
+ declare const WINDOW_DATA_KEY = "__FW_DATA__";
2
+
3
+ type InitialData = {
4
+ pathname: string;
5
+ params: Record<string, string>;
6
+ props: Record<string, any>;
7
+ metadata?: {
8
+ title?: string;
9
+ description?: string;
10
+ } | null;
11
+ notFound?: boolean;
12
+ error?: boolean;
13
+ theme?: string;
14
+ };
15
+ declare global {
16
+ interface Window {
17
+ [WINDOW_DATA_KEY]?: InitialData;
18
+ }
19
+ }
20
+ type ClientLoadedComponents = {
21
+ Page: React.ComponentType<any>;
22
+ layouts: React.ComponentType<any>[];
23
+ };
24
+ type ClientRouteLoaded = {
25
+ pattern: string;
26
+ paramNames: string[];
27
+ load: () => Promise<ClientLoadedComponents>;
28
+ };
29
+ type ClientRouteMatch = {
30
+ route: ClientRouteLoaded;
31
+ params: Record<string, string>;
32
+ };
33
+ type RouteViewState = {
34
+ url: string;
35
+ route: ClientRouteLoaded | null;
36
+ params: Record<string, string>;
37
+ components: ClientLoadedComponents | null;
38
+ props: Record<string, any>;
39
+ };
40
+
41
+ /**
42
+ * Bootstraps the client-side application.
43
+ *
44
+ * @param routes - Array of client routes
45
+ * @param notFoundRoute - Not-found route definition
46
+ * @param errorRoute - Error route definition
47
+ */
48
+ declare function bootstrapClient(routes: ClientRouteLoaded[], notFoundRoute: ClientRouteLoaded | null, errorRoute?: ClientRouteLoaded | null): void;
49
+
50
+ export { type ClientRouteLoaded as C, type InitialData as I, type RouteViewState as R, type ClientLoadedComponents as a, type ClientRouteMatch as b, bootstrapClient as c };
package/dist/cli.cjs CHANGED
@@ -1330,24 +1330,69 @@ function createClientConfig(projectRoot, mode) {
1330
1330
  var import_path14 = __toESM(require("path"));
1331
1331
  var import_fs11 = __toESM(require("fs"));
1332
1332
  init_globals();
1333
- function startClientBundler(projectRoot) {
1334
- const { config, outDir } = createClientConfig(projectRoot, "production");
1333
+ function startClientBundler(projectRoot, mode = "development") {
1334
+ const { config, outDir } = createClientConfig(projectRoot, mode);
1335
1335
  copyStaticAssets(projectRoot, outDir);
1336
1336
  const compiler = (0, import_core2.rspack)(config);
1337
+ let isBuilding = false;
1338
+ let buildResolve = null;
1339
+ let buildPromise = null;
1340
+ let lastBuildTime = Date.now();
1341
+ compiler.hooks.compile.tap("HotReload", () => {
1342
+ isBuilding = true;
1343
+ buildPromise = new Promise((resolve3) => {
1344
+ buildResolve = resolve3;
1345
+ });
1346
+ });
1337
1347
  compiler.watch({}, (err, stats) => {
1338
1348
  if (err) {
1339
1349
  console.error("[framework][client] Rspack error:", err);
1350
+ isBuilding = false;
1351
+ lastBuildTime = Date.now();
1352
+ if (buildResolve) {
1353
+ buildResolve();
1354
+ buildResolve = null;
1355
+ buildPromise = null;
1356
+ }
1357
+ return;
1358
+ }
1359
+ if (!stats) {
1360
+ isBuilding = false;
1361
+ lastBuildTime = Date.now();
1340
1362
  return;
1341
1363
  }
1342
- if (!stats) return;
1343
1364
  if (stats.hasErrors()) {
1344
1365
  console.error(
1345
1366
  "[framework][client] Build with errors:\n",
1346
1367
  stats.toString("errors-only")
1347
1368
  );
1369
+ } else {
1370
+ console.log("[framework][client] \u2713 Client bundle rebuilt successfully");
1371
+ }
1372
+ isBuilding = false;
1373
+ lastBuildTime = Date.now();
1374
+ if (buildResolve) {
1375
+ buildResolve();
1376
+ buildResolve = null;
1377
+ buildPromise = null;
1348
1378
  }
1349
1379
  });
1350
- return { outDir };
1380
+ return {
1381
+ outDir,
1382
+ waitForBuild: async () => {
1383
+ if (isBuilding && buildPromise) {
1384
+ await buildPromise;
1385
+ await new Promise((resolve3) => setTimeout(resolve3, 100));
1386
+ return;
1387
+ }
1388
+ const timeSinceLastBuild = Date.now() - lastBuildTime;
1389
+ if (timeSinceLastBuild < 500) {
1390
+ await new Promise((resolve3) => setTimeout(resolve3, 200));
1391
+ return;
1392
+ }
1393
+ return Promise.resolve();
1394
+ }
1395
+ };
1351
1396
  }
1352
1397
  function buildClientBundle(projectRoot) {
1353
1398
  const { config, outDir } = createClientConfig(projectRoot, "production");
@@ -3702,7 +3747,9 @@ init_globals();
3702
3747
  function setupHotReload({
3703
3748
  app,
3704
3749
  appDir,
3705
- route = "/__fw/hot"
3750
+ route = "/__fw/hot",
3751
+ waitForBuild,
3752
+ onFileChange
3706
3753
  }) {
3707
3754
  const clients = /* @__PURE__ */ new Set();
3708
3755
  app.get(route, (req, res) => {
@@ -3723,9 +3770,25 @@ data: connected
3723
3770
  ignoreInitial: true,
3724
3771
  ignored: ["**/node_modules/**", `**/${BUILD_FOLDER_NAME}/**`, "**/.git/**"]
3725
3772
  });
3726
- function broadcastReload(reason, filePath) {
3773
+ async function broadcastReload(reason, filePath) {
3727
3774
  const rel = import_path23.default.relative(appDir, filePath);
3728
3775
  console.log(`[hot-reload] ${reason}: ${rel}`);
3776
+ if (onFileChange) {
3777
+ try {
3778
+ await onFileChange(filePath);
3779
+ } catch (error) {
3780
+ console.warn("[hot-reload] Error in onFileChange callback:", error);
3781
+ }
3782
+ }
3783
+ if (waitForBuild) {
3784
+ try {
3785
+ console.log("[hot-reload] Waiting for client bundle to finish...");
3786
+ await waitForBuild();
3787
+ console.log("[hot-reload] Client bundle ready, sending reload event");
3788
+ } catch (error) {
3789
+ console.warn("[hot-reload] Error waiting for build:", error);
3790
+ }
3791
+ }
3729
3792
  for (const res of clients) {
3730
3793
  res.write(`event: message
3731
3794
  data: reload:${rel}
@@ -3762,14 +3825,29 @@ function setupServer(app, options) {
3762
3825
  };
3763
3826
  };
3764
3827
  var getRoutes = getRoutes2;
3765
- setupHotReload({ app, appDir });
3828
+ const { outDir, waitForBuild } = startClientBundler(projectRoot, "development");
3829
+ const onFileChange = async (filePath) => {
3830
+ const rel = import_path25.default.relative(appDir, filePath);
3831
+ const isPageFile = filePath.includes("page.tsx") || filePath.includes("page.ts") || filePath.includes("layout.tsx") || filePath.includes("layout.ts") || filePath.includes("_not-found") || filePath.includes("_error");
3832
+ const isTsFile = filePath.endsWith(".ts") || filePath.endsWith(".tsx");
3833
+ if (isTsFile) {
3834
+ clearAppRequireCache(appDir);
3835
+ console.log(`[hot-reload] Cleared require cache for: ${rel}`);
3836
+ }
3837
+ if (isPageFile) {
3838
+ const loader = new FilesystemRouteLoader(appDir);
3839
+ const newRoutes = loader.loadRoutes();
3840
+ writeClientRoutesManifest(newRoutes, projectRoot);
3841
+ console.log("[hot-reload] Client routes manifest reloaded");
3842
+ }
3843
+ };
3844
+ setupHotReload({ app, appDir, waitForBuild, onFileChange });
3845
+ app.use("/static", import_express.default.static(outDir));
3766
3846
  const routes = routeLoader.loadRoutes();
3767
3847
  const wssRoutes = routeLoader.loadWssRoutes();
3768
3848
  const notFoundPage = routeLoader.loadNotFoundRoute();
3769
3849
  const errorPage = routeLoader.loadErrorRoute();
3770
3850
  writeClientRoutesManifest(routes, projectRoot);
3771
- const { outDir } = startClientBundler(projectRoot);
3772
- app.use("/static", import_express.default.static(outDir));
3773
3851
  return {
3774
3852
  routes,
3775
3853
  wssRoutes,
@@ -4332,7 +4410,7 @@ async function handlePageRequest(options) {
4332
4410
  const { errorPage, req, res, routeChunks, theme, projectRoot } = options;
4333
4411
  const reqLogger = getRequestLogger(req);
4334
4412
  if (errorPage) {
4335
- await renderErrorPageWithStream(errorPage, req, res, error, routeChunks || {}, theme, projectRoot);
4413
+ await renderErrorPageWithStream(errorPage, req, res, error, routeChunks || {}, theme, projectRoot, options.env);
4336
4414
  } else {
4337
4415
  reqLogger.error("Unhandled error in page request", error, {
4338
4416
  urlPath: options.urlPath,
@@ -4360,9 +4438,9 @@ async function handlePageRequestInternal(options) {
4360
4438
  theme,
4361
4439
  projectRoot
4362
4440
  } = options;
4363
- const clientJsPath = projectRoot ? getClientJsPath(projectRoot) : "/static/client.js";
4364
- const clientCssPath = projectRoot ? getClientCssPath(projectRoot) : "/static/client.css";
4365
- const assetManifest = projectRoot ? loadAssetManifest(projectRoot) : null;
4441
+ const clientJsPath = env === "dev" ? "/static/client.js" : projectRoot ? getClientJsPath(projectRoot) : "/static/client.js";
4442
+ const clientCssPath = env === "dev" ? "/static/client.css" : projectRoot ? getClientCssPath(projectRoot) : "/static/client.css";
4443
+ const assetManifest = env === "prod" && projectRoot ? loadAssetManifest(projectRoot) : null;
4366
4444
  const isDataReq = isDataRequest(req);
4367
4445
  if (env === "prod" && ssgOutDir) {
4368
4446
  if (isDataReq) {
@@ -4418,7 +4496,7 @@ async function handlePageRequestInternal(options) {
4418
4496
  const reqLogger = getRequestLogger(req);
4419
4497
  reqLogger.error("SSR shell error", err, { route: "not-found" });
4420
4498
  if (!res.headersSent && errorPage) {
4421
- renderErrorPageWithStream(errorPage, req, res, err, routeChunks);
4499
+ renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4422
4500
  } else if (!res.headersSent) {
4423
4501
  res.statusCode = 500;
4424
4502
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -4465,7 +4543,7 @@ async function handlePageRequestInternal(options) {
4465
4543
  return;
4466
4544
  } else {
4467
4545
  if (errorPage) {
4468
- await renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot);
4546
+ await renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env);
4469
4547
  return;
4470
4548
  } else {
4471
4549
  throw error;
@@ -4527,7 +4605,7 @@ async function handlePageRequestInternal(options) {
4527
4605
  const reqLogger = getRequestLogger(req);
4528
4606
  reqLogger.error("SSR shell error", err, { route: matched?.route?.pattern || "unknown" });
4529
4607
  if (!res.headersSent && errorPage) {
4530
- renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot);
4608
+ renderErrorPageWithStream(errorPage, req, res, err, routeChunks, theme, projectRoot, env);
4531
4609
  } else if (!res.headersSent) {
4532
4610
  res.statusCode = 500;
4533
4611
  res.setHeader("Content-Type", "text/html; charset=utf-8");
@@ -4545,7 +4623,7 @@ async function handlePageRequestInternal(options) {
4545
4623
  abort();
4546
4624
  });
4547
4625
  }
4548
- async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot) {
4626
+ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks, theme, projectRoot, env = "dev") {
4549
4627
  try {
4550
4628
  const isDataReq = isDataRequest(req);
4551
4629
  const ctx = {
@@ -4574,9 +4652,9 @@ async function renderErrorPageWithStream(errorPage, req, res, error, routeChunks
4574
4652
  return;
4575
4653
  }
4576
4654
  const appTree = buildAppTree(errorPage, { error: String(error) }, initialData.props);
4577
- const clientJsPath = projectRoot ? getClientJsPath(projectRoot) : "/static/client.js";
4578
- const clientCssPath = projectRoot ? getClientCssPath(projectRoot) : "/static/client.css";
4579
- const assetManifest = projectRoot ? loadAssetManifest(projectRoot) : null;
4655
+ const clientJsPath = env === "dev" ? "/static/client.js" : projectRoot ? getClientJsPath(projectRoot) : "/static/client.js";
4656
+ const clientCssPath = env === "dev" ? "/static/client.css" : projectRoot ? getClientCssPath(projectRoot) : "/static/client.css";
4657
+ const assetManifest = env === "prod" && projectRoot ? loadAssetManifest(projectRoot) : null;
4580
4658
  const chunkName = routeChunks[ERROR_CHUNK_KEY];
4581
4659
  let chunkHref = null;
4582
4660
  if (chunkName != null) {