@lolyjs/core 0.3.0-alpha.6 → 0.4.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.
Files changed (40) hide show
  1. package/README.md +88 -0
  2. package/dist/{bootstrap-BfGTMUkj.d.mts → bootstrap-B6W6XoI5.d.mts} +6 -0
  3. package/dist/{bootstrap-BfGTMUkj.d.ts → bootstrap-B6W6XoI5.d.ts} +6 -0
  4. package/dist/cli.cjs +2969 -977
  5. package/dist/cli.cjs.map +1 -1
  6. package/dist/cli.mjs +2936 -949
  7. package/dist/cli.mjs.map +1 -1
  8. package/dist/index.cjs +3159 -937
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.mts +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.mjs +3136 -920
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/react/cache.cjs.map +1 -1
  15. package/dist/react/cache.mjs.map +1 -1
  16. package/dist/react/components.cjs +16 -0
  17. package/dist/react/components.cjs.map +1 -1
  18. package/dist/react/components.d.mts +40 -2
  19. package/dist/react/components.d.ts +40 -2
  20. package/dist/react/components.mjs +15 -0
  21. package/dist/react/components.mjs.map +1 -1
  22. package/dist/react/hooks.cjs +24 -0
  23. package/dist/react/hooks.cjs.map +1 -1
  24. package/dist/react/hooks.d.mts +49 -1
  25. package/dist/react/hooks.d.ts +49 -1
  26. package/dist/react/hooks.mjs +22 -0
  27. package/dist/react/hooks.mjs.map +1 -1
  28. package/dist/react/themes.cjs +0 -169
  29. package/dist/react/themes.cjs.map +1 -1
  30. package/dist/react/themes.d.mts +18 -11
  31. package/dist/react/themes.d.ts +18 -11
  32. package/dist/react/themes.mjs +0 -159
  33. package/dist/react/themes.mjs.map +1 -1
  34. package/dist/runtime.cjs +268 -24
  35. package/dist/runtime.cjs.map +1 -1
  36. package/dist/runtime.d.mts +2 -2
  37. package/dist/runtime.d.ts +2 -2
  38. package/dist/runtime.mjs +255 -15
  39. package/dist/runtime.mjs.map +1 -1
  40. package/package.json +2 -2
package/dist/runtime.mjs CHANGED
@@ -1,5 +1,102 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // modules/runtime/client/mount-client-islands.ts
12
+ var mount_client_islands_exports = {};
13
+ __export(mount_client_islands_exports, {
14
+ mountClientIslands: () => mountClientIslands
15
+ });
16
+ import React2 from "react";
17
+ import { createRoot } from "react-dom/client";
18
+ function getClientComponentLoaders() {
19
+ if (typeof window === "undefined") return null;
20
+ return window.__LOLY_CLIENT_COMPONENT_LOADERS__ || null;
21
+ }
22
+ function normalizeClientFilePath(filePath) {
23
+ return filePath.replace(/\\/g, "/");
24
+ }
25
+ async function loadClientComponentModule(filePath) {
26
+ const loaders = getClientComponentLoaders();
27
+ if (!loaders) {
28
+ throw new Error(
29
+ `[client] Missing __LOLY_CLIENT_COMPONENT_LOADERS__. Expected bootstrap to register loaders from .loly/client-components-loaders.ts`
30
+ );
31
+ }
32
+ const normalized = normalizeClientFilePath(filePath);
33
+ const loader = loaders[normalized];
34
+ if (!loader) {
35
+ const known = Object.keys(loaders);
36
+ throw new Error(
37
+ `[client] No loader found for "${normalized}". Known loaders: ${known.slice(0, 10).join(", ")}${known.length > 10 ? "..." : ""}`
38
+ );
39
+ }
40
+ return loader();
41
+ }
42
+ async function loadClientComponentExport(filePath, exportName = "default") {
43
+ const mod = await loadClientComponentModule(filePath);
44
+ return exportName === "default" ? mod.default : mod[exportName];
45
+ }
46
+ async function mountClientIslands(container) {
47
+ const placeholders = container.querySelectorAll("[data-client-component]");
48
+ if (placeholders.length === 0) return;
49
+ const promises = Array.from(placeholders).map(async (placeholder) => {
50
+ const filePath = placeholder.getAttribute("data-client-file");
51
+ const exportName = placeholder.getAttribute("data-export-name") || "default";
52
+ const propsJson = placeholder.getAttribute("data-client-props");
53
+ if (!filePath) {
54
+ console.warn("[client] Missing filePath for component");
55
+ return;
56
+ }
57
+ let props = {};
58
+ if (propsJson) {
59
+ try {
60
+ props = JSON.parse(propsJson);
61
+ } catch (e) {
62
+ console.warn("[client] Failed to parse props", e);
63
+ }
64
+ }
65
+ try {
66
+ const Component = await loadClientComponentExport(filePath, exportName);
67
+ if (!Component) throw new Error(`Component export "${exportName}" not found`);
68
+ const placeholderElement = placeholder;
69
+ while (placeholderElement.firstChild) {
70
+ placeholderElement.removeChild(placeholderElement.firstChild);
71
+ }
72
+ const root = createRoot(placeholderElement);
73
+ root.render(React2.createElement(Component, props));
74
+ } catch (error) {
75
+ console.error("[client] Failed to mount component:", error);
76
+ const placeholderElement = placeholder;
77
+ while (placeholderElement.firstChild) {
78
+ placeholderElement.removeChild(placeholderElement.firstChild);
79
+ }
80
+ const root = createRoot(placeholderElement);
81
+ root.render(
82
+ React2.createElement(
83
+ "div",
84
+ { style: { padding: "1rem", border: "1px solid red", color: "red" } },
85
+ "Failed to load component"
86
+ )
87
+ );
88
+ }
89
+ });
90
+ await Promise.all(promises);
91
+ }
92
+ var init_mount_client_islands = __esm({
93
+ "modules/runtime/client/mount-client-islands.ts"() {
94
+ "use strict";
95
+ }
96
+ });
97
+
1
98
  // modules/runtime/client/bootstrap.tsx
2
- import { hydrateRoot } from "react-dom/client";
99
+ import { hydrateRoot, createRoot as createRoot2 } from "react-dom/client";
3
100
 
4
101
  // modules/runtime/client/constants.ts
5
102
  var WINDOW_DATA_KEY = "__FW_DATA__";
@@ -259,9 +356,10 @@ function applyMetadata(md) {
259
356
  }
260
357
 
261
358
  // modules/runtime/client/AppShell.tsx
262
- import { useEffect, useState, useRef, useCallback } from "react";
359
+ import { useEffect, useState, useRef, useCallback, useLayoutEffect } from "react";
263
360
 
264
361
  // modules/runtime/client/RouterView.tsx
362
+ import React from "react";
265
363
  import { jsx } from "react/jsx-runtime";
266
364
  function RouterView({ state }) {
267
365
  if (!state.route) {
@@ -273,12 +371,23 @@ function RouterView({ state }) {
273
371
  if (!state.components) {
274
372
  return null;
275
373
  }
276
- const { Page, layouts } = state.components;
374
+ const {
375
+ Page,
376
+ layouts,
377
+ isPageClientComponent,
378
+ isLayoutClientComponent,
379
+ clientComponentFilePaths
380
+ } = state.components;
277
381
  const { params, props } = state;
278
- let element = /* @__PURE__ */ jsx(Page, { params, ...props });
382
+ let element = isPageClientComponent && clientComponentFilePaths?.page ? null : React.createElement(Page, { params, ...props });
279
383
  const layoutChain = layouts.slice().reverse();
280
- for (const Layout of layoutChain) {
281
- element = /* @__PURE__ */ jsx(Layout, { params, ...props, children: element });
384
+ const isLayoutClientComponentReversed = (isLayoutClientComponent || []).slice().reverse();
385
+ const clientComponentLayoutPaths = (clientComponentFilePaths?.layouts || []).slice().reverse();
386
+ for (let i = 0; i < layoutChain.length; i++) {
387
+ const Layout = layoutChain[i];
388
+ const isClient = isLayoutClientComponentReversed[i];
389
+ const layoutPath = clientComponentLayoutPaths[i];
390
+ element = isClient && layoutPath ? element : React.createElement(Layout, { params, ...props, children: element });
282
391
  }
283
392
  return element;
284
393
  }
@@ -442,9 +551,46 @@ async function getRouteData(url, options) {
442
551
  }
443
552
 
444
553
  // modules/runtime/client/navigation.ts
554
+ function detectClientComponents(routePattern, components) {
555
+ let dependenciesManifest = null;
556
+ if (typeof window !== "undefined" && window.__LOLY_ROUTE_DEPENDENCIES__) {
557
+ dependenciesManifest = window.__LOLY_ROUTE_DEPENDENCIES__;
558
+ }
559
+ if (!dependenciesManifest || !dependenciesManifest.routes) {
560
+ return components;
561
+ }
562
+ const routeDeps = dependenciesManifest.routes[routePattern];
563
+ if (!routeDeps) {
564
+ return components;
565
+ }
566
+ const isPageClientComponent = routeDeps.isPageClientComponent || false;
567
+ const pageFilePath = routeDeps.pageFilePath || (routePattern === "/" ? "app/page.tsx" : `app${routePattern}/page.tsx`);
568
+ const isLayoutClientComponent = routeDeps.isLayoutClientComponent ? routeDeps.isLayoutClientComponent.slice(0, components.layouts.length) : new Array(components.layouts.length).fill(false);
569
+ const layoutFilePaths = routeDeps.layoutFilePaths || [];
570
+ const clientComponentLayoutPaths = new Array(components.layouts.length).fill(void 0);
571
+ if (routeDeps.isLayoutClientComponent && routeDeps.layoutFilePaths) {
572
+ let layoutFilePathIndex = 0;
573
+ for (let i = 0; i < isLayoutClientComponent.length; i++) {
574
+ if (isLayoutClientComponent[i] && layoutFilePathIndex < routeDeps.layoutFilePaths.length) {
575
+ clientComponentLayoutPaths[i] = routeDeps.layoutFilePaths[layoutFilePathIndex];
576
+ layoutFilePathIndex++;
577
+ }
578
+ }
579
+ }
580
+ return {
581
+ ...components,
582
+ isPageClientComponent,
583
+ isLayoutClientComponent,
584
+ clientComponentFilePaths: {
585
+ page: isPageClientComponent ? pageFilePath : void 0,
586
+ layouts: clientComponentLayoutPaths.some((p) => p !== void 0) ? clientComponentLayoutPaths : void 0
587
+ }
588
+ };
589
+ }
445
590
  async function handleErrorRoute(nextUrl, json, errorRoute, setState) {
446
591
  try {
447
- const components = await errorRoute.load();
592
+ const loadedComponents = await errorRoute.load();
593
+ const components = detectClientComponents(errorRoute.pattern, loadedComponents);
448
594
  let theme = "light";
449
595
  if (typeof document !== "undefined") {
450
596
  const cookieMatch = document.cookie.match(/theme=([^;]+)/);
@@ -565,7 +711,8 @@ async function handleNotFoundRoute(nextUrl, json, notFoundRoute, setState) {
565
711
  };
566
712
  setRouterData(routerData);
567
713
  if (notFoundRoute) {
568
- const components = await notFoundRoute.load();
714
+ const loadedComponents = await notFoundRoute.load();
715
+ const components = detectClientComponents(notFoundRoute.pattern, loadedComponents);
569
716
  setState({
570
717
  url: nextUrl,
571
718
  route: notFoundRoute,
@@ -650,9 +797,10 @@ async function handleNormalRoute(nextUrl, json, routes, setState) {
650
797
  };
651
798
  setRouterData(routerData);
652
799
  const prefetched = prefetchedRoutes.get(matched.route);
653
- const components = prefetched ? await prefetched : await matched.route.load();
800
+ const loadedComponents = prefetched ? await prefetched : await matched.route.load();
801
+ const components = detectClientComponents(matched.route.pattern, loadedComponents);
654
802
  if (!prefetched) {
655
- prefetchedRoutes.set(matched.route, Promise.resolve(components));
803
+ prefetchedRoutes.set(matched.route, Promise.resolve(loadedComponents));
656
804
  }
657
805
  window.scrollTo({
658
806
  top: 0,
@@ -848,7 +996,7 @@ function AppShell({
848
996
  },
849
997
  []
850
998
  );
851
- useEffect(() => {
999
+ useLayoutEffect(() => {
852
1000
  if (typeof window !== "undefined") {
853
1001
  window[ROUTER_NAVIGATE_KEY] = handleNavigate;
854
1002
  return () => {
@@ -989,6 +1137,42 @@ function setupHotReload() {
989
1137
 
990
1138
  // modules/runtime/client/bootstrap.tsx
991
1139
  import { jsx as jsx3 } from "react/jsx-runtime";
1140
+ function detectClientComponents2(routePattern, components) {
1141
+ let dependenciesManifest = null;
1142
+ if (typeof window !== "undefined" && window.__LOLY_ROUTE_DEPENDENCIES__) {
1143
+ dependenciesManifest = window.__LOLY_ROUTE_DEPENDENCIES__;
1144
+ }
1145
+ if (!dependenciesManifest || !dependenciesManifest.routes) {
1146
+ return components;
1147
+ }
1148
+ const routeDeps = dependenciesManifest.routes[routePattern];
1149
+ if (!routeDeps) {
1150
+ return components;
1151
+ }
1152
+ const isPageClientComponent = routeDeps.isPageClientComponent || false;
1153
+ const pageFilePath = routeDeps.pageFilePath || (routePattern === "/" ? "app/page.tsx" : `app${routePattern}/page.tsx`);
1154
+ const isLayoutClientComponent = routeDeps.isLayoutClientComponent ? routeDeps.isLayoutClientComponent.slice(0, components.layouts.length) : new Array(components.layouts.length).fill(false);
1155
+ const layoutFilePaths = routeDeps.layoutFilePaths || [];
1156
+ const clientComponentLayoutPaths = new Array(components.layouts.length).fill(void 0);
1157
+ if (routeDeps.isLayoutClientComponent && routeDeps.layoutFilePaths) {
1158
+ let layoutFilePathIndex = 0;
1159
+ for (let i = 0; i < isLayoutClientComponent.length; i++) {
1160
+ if (isLayoutClientComponent[i] && layoutFilePathIndex < routeDeps.layoutFilePaths.length) {
1161
+ clientComponentLayoutPaths[i] = routeDeps.layoutFilePaths[layoutFilePathIndex];
1162
+ layoutFilePathIndex++;
1163
+ }
1164
+ }
1165
+ }
1166
+ return {
1167
+ ...components,
1168
+ isPageClientComponent,
1169
+ isLayoutClientComponent,
1170
+ clientComponentFilePaths: {
1171
+ page: isPageClientComponent ? pageFilePath : void 0,
1172
+ layouts: clientComponentLayoutPaths.some((p) => p !== void 0) ? clientComponentLayoutPaths : void 0
1173
+ }
1174
+ };
1175
+ }
992
1176
  async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute, errorRoute) {
993
1177
  const isInitialNotFound = initialData?.notFound === true;
994
1178
  const isInitialError = initialData?.error === true;
@@ -998,21 +1182,25 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
998
1182
  if (isInitialError && errorRoute) {
999
1183
  initialRoute = errorRoute;
1000
1184
  initialParams = initialData?.params ?? {};
1001
- initialComponents = await errorRoute.load();
1185
+ const loaded = await errorRoute.load();
1186
+ initialComponents = detectClientComponents2(errorRoute.pattern, loaded);
1002
1187
  } else if (isInitialNotFound && notFoundRoute) {
1003
1188
  initialRoute = notFoundRoute;
1004
1189
  initialParams = {};
1005
- initialComponents = await notFoundRoute.load();
1190
+ const loaded = await notFoundRoute.load();
1191
+ initialComponents = detectClientComponents2(notFoundRoute.pattern, loaded);
1006
1192
  } else {
1007
1193
  const match = matchRouteClient(initialUrl, routes);
1008
1194
  if (match) {
1009
1195
  initialRoute = match.route;
1010
1196
  initialParams = match.params;
1011
- initialComponents = await match.route.load();
1197
+ const loaded = await match.route.load();
1198
+ initialComponents = detectClientComponents2(match.route.pattern, loaded);
1012
1199
  } else if (notFoundRoute) {
1013
1200
  initialRoute = notFoundRoute;
1014
1201
  initialParams = {};
1015
- initialComponents = await notFoundRoute.load();
1202
+ const loaded = await notFoundRoute.load();
1203
+ initialComponents = detectClientComponents2(notFoundRoute.pattern, loaded);
1016
1204
  } else {
1017
1205
  console.warn(
1018
1206
  `[client] No route match found for ${initialUrl}. Available routes:`,
@@ -1028,6 +1216,12 @@ async function loadInitialRoute(initialUrl, initialData, routes, notFoundRoute,
1028
1216
  props: initialData?.props ?? {}
1029
1217
  };
1030
1218
  }
1219
+ function getRouteDependencies(routePattern) {
1220
+ if (typeof window === "undefined") return null;
1221
+ const deps = window.__LOLY_ROUTE_DEPENDENCIES__;
1222
+ if (!deps || !deps.routes) return null;
1223
+ return deps.routes[routePattern] || null;
1224
+ }
1031
1225
  function initializeRouterData(initialUrl, initialData) {
1032
1226
  let routerData = getRouterData();
1033
1227
  if (!routerData) {
@@ -1099,6 +1293,39 @@ function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
1099
1293
  }
1100
1294
  const routerPathname = initialData?.pathname || window.location.pathname;
1101
1295
  initializeRouterData(routerPathname + window.location.search, initialData);
1296
+ const routePattern = initialData?.pathname || window.location.pathname;
1297
+ const routeDeps = getRouteDependencies(routePattern);
1298
+ const hasClientIslands = !!routeDeps && (routeDeps.isPageClientComponent === true || routeDeps.isLayoutClientComponent && routeDeps.isLayoutClientComponent.some((v) => v) || routeDeps.directClientComponents && routeDeps.directClientComponents.length > 0);
1299
+ const initialState = await loadInitialRoute(
1300
+ initialUrl,
1301
+ initialData,
1302
+ routes,
1303
+ notFoundRoute,
1304
+ errorRoute
1305
+ );
1306
+ if (initialData?.metadata) {
1307
+ try {
1308
+ applyMetadata(initialData.metadata);
1309
+ } catch (metadataError) {
1310
+ console.warn("[client] Error applying metadata:", metadataError);
1311
+ }
1312
+ }
1313
+ if (hasClientIslands) {
1314
+ container.innerHTML = "";
1315
+ const root = createRoot2(container);
1316
+ root.render(
1317
+ /* @__PURE__ */ jsx3(
1318
+ AppShell,
1319
+ {
1320
+ initialState,
1321
+ routes,
1322
+ notFoundRoute,
1323
+ errorRoute
1324
+ }
1325
+ )
1326
+ );
1327
+ return;
1328
+ }
1102
1329
  await hydrateInitialRoute(
1103
1330
  container,
1104
1331
  initialUrl,
@@ -1107,6 +1334,19 @@ function bootstrapClient(routes, notFoundRoute, errorRoute = null) {
1107
1334
  notFoundRoute,
1108
1335
  errorRoute
1109
1336
  );
1337
+ try {
1338
+ await new Promise((resolve) => {
1339
+ if (typeof queueMicrotask !== "undefined") {
1340
+ queueMicrotask(resolve);
1341
+ } else {
1342
+ setTimeout(resolve, 0);
1343
+ }
1344
+ });
1345
+ const { mountClientIslands: mountClientIslands2 } = await Promise.resolve().then(() => (init_mount_client_islands(), mount_client_islands_exports));
1346
+ await mountClientIslands2(container);
1347
+ } catch (error) {
1348
+ console.warn("[client] Failed to mount client component islands:", error);
1349
+ }
1110
1350
  } catch (error) {
1111
1351
  console.error("\n\u274C [client] Fatal error during bootstrap:");
1112
1352
  console.error(error);