@qwik.dev/router 2.0.0-beta.2 → 2.0.0-beta.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.
@@ -1,7 +1,7 @@
1
1
  import { jsx, Fragment, jsxs } from "@qwik.dev/core/jsx-runtime";
2
- import { component$, useErrorBoundary, useOnWindow, $, Slot, isBrowser, createContextId, implicit$FirstArg, useContext, useVisibleTask$, noSerialize, useServerData, useSignal, untrack, sync$, isDev, withLocale, event$, useStyles$, isServer, useStore, useContextProvider, useTask$, getLocale, jsx as jsx$1, SkipRender } from "@qwik.dev/core";
2
+ import { component$, useErrorBoundary, useOnWindow, $, Slot, createAsyncComputed$, isBrowser, createContextId, implicit$FirstArg, useContext, useVisibleTask$, noSerialize, useServerData, useSignal, untrack, sync$, isDev, withLocale, event$, useStyles$, isServer, useStore, useContextProvider, useTask$, getLocale, jsx as jsx$1, SkipRender } from "@qwik.dev/core";
3
3
  import { p } from "@qwik.dev/core/preloader";
4
- import { _deserialize, _weakSerialize, _getContextElement, _getQContainerElement, _waitUntilRendered, _wrapStore, _getContextEvent, _serialize } from "@qwik.dev/core/internal";
4
+ import { _deserialize, _UNINITIALIZED, _getContextContainer, SerializerSymbol, _getContextElement, _getQContainerElement, _waitUntilRendered, _useInvokeContext, _getContextEvent, _serialize } from "@qwik.dev/core/internal";
5
5
  import * as qwikRouterConfig from "@qwik-router-config";
6
6
  import { z } from "zod";
7
7
  import { z as z2 } from "zod";
@@ -21,100 +21,19 @@ const ErrorBoundary = component$((props) => {
21
21
  const MODULE_CACHE = /* @__PURE__ */ new WeakMap();
22
22
  const CLIENT_DATA_CACHE = /* @__PURE__ */ new Map();
23
23
  const QACTION_KEY = "qaction";
24
+ const QLOADER_KEY = "qloaders";
24
25
  const QFN_KEY = "qfunc";
25
26
  const QDATA_KEY = "qdata";
26
- const toPath = (url) => url.pathname + url.search + url.hash;
27
- const toUrl = (url, baseUrl) => new URL(url, baseUrl.href);
28
- const isSameOrigin = (a, b) => a.origin === b.origin;
29
- const withSlash = (path) => path.endsWith("/") ? path : path + "/";
30
- const isSamePathname = ({ pathname: a }, { pathname: b }) => {
31
- const lDiff = Math.abs(a.length - b.length);
32
- return lDiff === 0 ? a === b : lDiff === 1 && withSlash(a) === withSlash(b);
33
- };
34
- const isSameSearchQuery = (a, b) => a.search === b.search;
35
- const isSamePath = (a, b) => isSameSearchQuery(a, b) && isSamePathname(a, b);
36
- const getClientDataPath = (pathname, pageSearch, action) => {
37
- let search = pageSearch ?? "";
38
- if (action) {
39
- search += (search ? "&" : "?") + QACTION_KEY + "=" + encodeURIComponent(action.id);
40
- }
41
- return pathname + (pathname.endsWith("/") ? "" : "/") + "q-data.json" + search;
42
- };
43
- const getClientNavPath = (props, baseUrl) => {
44
- const href = props.href;
45
- if (typeof href === "string" && typeof props.target !== "string" && !props.reload) {
46
- try {
47
- const linkUrl = toUrl(href.trim(), baseUrl.url);
48
- const currentUrl = toUrl("", baseUrl.url);
49
- if (isSameOrigin(linkUrl, currentUrl)) {
50
- return toPath(linkUrl);
51
- }
52
- } catch (e) {
53
- console.error(e);
54
- }
55
- } else if (props.reload) {
56
- return toPath(toUrl("", baseUrl.url));
57
- }
58
- return null;
59
- };
60
- const shouldPreload = (clientNavPath, currentLoc) => {
61
- if (clientNavPath) {
62
- const prefetchUrl = toUrl(clientNavPath, currentLoc.url);
63
- const currentUrl = toUrl("", currentLoc.url);
64
- return !isSamePathname(prefetchUrl, currentUrl);
65
- }
66
- return false;
67
- };
68
- const isPromise = (value) => {
69
- return value && typeof value.then === "function";
70
- };
71
- const deepFreeze = (obj) => {
72
- if (obj == null) {
73
- return obj;
74
- }
75
- Object.getOwnPropertyNames(obj).forEach((prop) => {
76
- const value = obj[prop];
77
- if (value && typeof value === "object" && !Object.isFrozen(value)) {
78
- deepFreeze(value);
79
- }
80
- });
81
- return Object.freeze(obj);
82
- };
83
- const clientNavigate = (win, navType, fromURL, toURL, replaceState = false) => {
84
- if (navType !== "popstate") {
85
- const samePath = isSamePath(fromURL, toURL);
86
- const sameHash = fromURL.hash === toURL.hash;
87
- if (!samePath || !sameHash) {
88
- const newState = {
89
- _qRouterScroll: newScrollState()
90
- };
91
- if (replaceState) {
92
- win.history.replaceState(newState, "", toPath(toURL));
93
- } else {
94
- win.history.pushState(newState, "", toPath(toURL));
95
- }
96
- }
97
- }
98
- };
99
- const newScrollState = () => {
100
- return {
101
- x: 0,
102
- y: 0,
103
- w: 0,
104
- h: 0
105
- };
106
- };
107
- const prefetchSymbols = (path) => {
108
- if (isBrowser) {
109
- path = path.endsWith("/") ? path : path + "/";
110
- path = path.length > 1 && path.startsWith("/") ? path.slice(1) : path;
111
- p(path, 0.8);
112
- }
113
- };
114
- const loadClientData = async (url, element, opts) => {
27
+ const Q_ROUTE = "q:route";
28
+ const DEFAULT_LOADERS_SERIALIZATION_STRATEGY = globalThis.__DEFAULT_LOADERS_SERIALIZATION_STRATEGY__ || "never";
29
+ const MAX_Q_DATA_RETRY_COUNT = 3;
30
+ const loadClientData = async (url, element, opts, retryCount = 0) => {
115
31
  const pagePathname = url.pathname;
116
32
  const pageSearch = url.search;
117
- const clientDataPath = getClientDataPath(pagePathname, pageSearch, opts?.action);
33
+ const clientDataPath = getClientDataPath(pagePathname, pageSearch, {
34
+ actionId: opts?.action?.id,
35
+ loaderIds: opts?.loaderIds
36
+ });
118
37
  let qData;
119
38
  if (!opts?.action) {
120
39
  qData = CLIENT_DATA_CACHE.get(clientDataPath);
@@ -129,6 +48,10 @@ const loadClientData = async (url, element, opts) => {
129
48
  opts.action.data = void 0;
130
49
  }
131
50
  qData = fetch(clientDataPath, fetchOptions).then((rsp) => {
51
+ if (rsp.status === 404 && opts?.loaderIds && retryCount < MAX_Q_DATA_RETRY_COUNT) {
52
+ opts.loaderIds = void 0;
53
+ return loadClientData(url, element, opts, retryCount + 1);
54
+ }
132
55
  if (rsp.redirected) {
133
56
  const redirectedURL = new URL(rsp.url);
134
57
  const isQData = redirectedURL.pathname.endsWith("/q-data.json");
@@ -209,6 +132,115 @@ const getFetchOptions = (action, noCache) => {
209
132
  };
210
133
  }
211
134
  };
135
+ const toPath = (url) => url.pathname + url.search + url.hash;
136
+ const toUrl = (url, baseUrl) => new URL(url, baseUrl.href);
137
+ const isSameOrigin = (a, b) => a.origin === b.origin;
138
+ const withSlash = (path) => path.endsWith("/") ? path : path + "/";
139
+ const isSamePathname = ({ pathname: a }, { pathname: b }) => {
140
+ const lDiff = Math.abs(a.length - b.length);
141
+ return lDiff === 0 ? a === b : lDiff === 1 && withSlash(a) === withSlash(b);
142
+ };
143
+ const isSameSearchQuery = (a, b) => a.search === b.search;
144
+ const isSamePath = (a, b) => isSameSearchQuery(a, b) && isSamePathname(a, b);
145
+ const getClientDataPath = (pathname, pageSearch, options) => {
146
+ let search = pageSearch ?? "";
147
+ if (options?.actionId) {
148
+ search += (search ? "&" : "?") + QACTION_KEY + "=" + encodeURIComponent(options.actionId);
149
+ }
150
+ if (options?.loaderIds) {
151
+ for (const loaderId of options.loaderIds) {
152
+ search += (search ? "&" : "?") + QLOADER_KEY + "=" + encodeURIComponent(loaderId);
153
+ }
154
+ }
155
+ return pathname + (pathname.endsWith("/") ? "" : "/") + "q-data.json" + search;
156
+ };
157
+ const getClientNavPath = (props, baseUrl) => {
158
+ const href = props.href;
159
+ if (typeof href === "string" && typeof props.target !== "string" && !props.reload) {
160
+ try {
161
+ const linkUrl = toUrl(href.trim(), baseUrl.url);
162
+ const currentUrl = toUrl("", baseUrl.url);
163
+ if (isSameOrigin(linkUrl, currentUrl)) {
164
+ return toPath(linkUrl);
165
+ }
166
+ } catch (e) {
167
+ console.error(e);
168
+ }
169
+ } else if (props.reload) {
170
+ return toPath(toUrl("", baseUrl.url));
171
+ }
172
+ return null;
173
+ };
174
+ const shouldPreload = (clientNavPath, currentLoc) => {
175
+ if (clientNavPath) {
176
+ const prefetchUrl = toUrl(clientNavPath, currentLoc.url);
177
+ const currentUrl = toUrl("", currentLoc.url);
178
+ return !isSamePathname(prefetchUrl, currentUrl);
179
+ }
180
+ return false;
181
+ };
182
+ const isPromise = (value) => {
183
+ return value && typeof value.then === "function";
184
+ };
185
+ const deepFreeze = (obj) => {
186
+ if (obj == null) {
187
+ return obj;
188
+ }
189
+ Object.getOwnPropertyNames(obj).forEach((prop) => {
190
+ const value = obj[prop];
191
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
192
+ deepFreeze(value);
193
+ }
194
+ });
195
+ return Object.freeze(obj);
196
+ };
197
+ const createLoaderSignal = (loadersObject, loaderId, url, serializationStrategy, container) => {
198
+ return createAsyncComputed$(async () => {
199
+ if (isBrowser && loadersObject[loaderId] === _UNINITIALIZED) {
200
+ const data = await loadClientData(url, void 0, {
201
+ loaderIds: [
202
+ loaderId
203
+ ]
204
+ });
205
+ loadersObject[loaderId] = data?.loaders[loaderId] ?? _UNINITIALIZED;
206
+ }
207
+ return loadersObject[loaderId];
208
+ }, {
209
+ container,
210
+ serializationStrategy
211
+ });
212
+ };
213
+ const clientNavigate = (win, navType, fromURL, toURL, replaceState = false) => {
214
+ if (navType !== "popstate") {
215
+ const samePath = isSamePath(fromURL, toURL);
216
+ const sameHash = fromURL.hash === toURL.hash;
217
+ if (!samePath || !sameHash) {
218
+ const newState = {
219
+ _qRouterScroll: newScrollState()
220
+ };
221
+ if (replaceState) {
222
+ win.history.replaceState(newState, "", toPath(toURL));
223
+ } else {
224
+ win.history.pushState(newState, "", toPath(toURL));
225
+ }
226
+ }
227
+ }
228
+ };
229
+ const newScrollState = () => {
230
+ return {
231
+ x: 0,
232
+ y: 0,
233
+ w: 0,
234
+ h: 0
235
+ };
236
+ };
237
+ const prefetchSymbols = (path) => {
238
+ if (isBrowser) {
239
+ path = path.endsWith("/") ? path : path + "/";
240
+ path = path.length > 1 && path.startsWith("/") ? path.slice(1) : path;
241
+ p(path, 0.8);
242
+ }
243
+ };
212
244
  const RouteStateContext = /* @__PURE__ */ createContextId("qc-s");
213
245
  const ContentContext = /* @__PURE__ */ createContextId("qc-c");
214
246
  const ContentInternalContext = /* @__PURE__ */ createContextId("qc-ic");
@@ -260,7 +292,7 @@ const Link = component$((props) => {
260
292
  }
261
293
  }
262
294
  }) : void 0;
263
- const preventDefault = clientNavPath ? sync$((event, target) => {
295
+ const preventDefault = clientNavPath ? sync$((event) => {
264
296
  if (!(event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)) {
265
297
  event.preventDefault();
266
298
  }
@@ -490,18 +522,30 @@ function lastIndexOf(text, start, match, searchIdx, notFoundIdx) {
490
522
  }
491
523
  return idx > start ? idx : notFoundIdx;
492
524
  }
525
+ var RouteDataProp = /* @__PURE__ */ function(RouteDataProp2) {
526
+ RouteDataProp2[RouteDataProp2["RouteName"] = 0] = "RouteName";
527
+ RouteDataProp2[RouteDataProp2["Loaders"] = 1] = "Loaders";
528
+ RouteDataProp2[RouteDataProp2["OriginalPathname"] = 2] = "OriginalPathname";
529
+ RouteDataProp2[RouteDataProp2["RouteBundleNames"] = 3] = "RouteBundleNames";
530
+ return RouteDataProp2;
531
+ }({});
532
+ var MenuDataProp = /* @__PURE__ */ function(MenuDataProp2) {
533
+ MenuDataProp2[MenuDataProp2["Pathname"] = 0] = "Pathname";
534
+ MenuDataProp2[MenuDataProp2["MenuLoader"] = 1] = "MenuLoader";
535
+ return MenuDataProp2;
536
+ }({});
493
537
  const loadRoute = async (routes, menus, cacheModules, pathname) => {
494
538
  if (!Array.isArray(routes)) {
495
539
  return null;
496
540
  }
497
541
  for (const routeData of routes) {
498
- const routeName = routeData[0];
542
+ const routeName = routeData[RouteDataProp.RouteName];
499
543
  const params = matchRoute(routeName, pathname);
500
544
  if (!params) {
501
545
  continue;
502
546
  }
503
- const loaders = routeData[1];
504
- const routeBundleNames = routeData[3];
547
+ const loaders = routeData[RouteDataProp.Loaders];
548
+ const routeBundleNames = routeData[RouteDataProp.RouteBundleNames];
505
549
  const modules = new Array(loaders.length);
506
550
  const pendingLoads = [];
507
551
  loaders.forEach((moduleLoader, i) => {
@@ -546,9 +590,9 @@ const loadModule = (moduleLoader, pendingLoads, moduleSetter, cacheModules) => {
546
590
  const getMenuLoader = (menus, pathname) => {
547
591
  if (menus) {
548
592
  pathname = pathname.endsWith("/") ? pathname : pathname + "/";
549
- const menu = menus.find((m) => m[0] === pathname || pathname.startsWith(m[0] + (pathname.endsWith("/") ? "" : "/")));
593
+ const menu = menus.find((m) => m[MenuDataProp.Pathname] === pathname || pathname.startsWith(m[MenuDataProp.Pathname] + (pathname.endsWith("/") ? "" : "/")));
550
594
  if (menu) {
551
- return menu[1];
595
+ return menu[MenuDataProp.MenuLoader];
552
596
  }
553
597
  }
554
598
  };
@@ -815,9 +859,23 @@ const QwikRouterProvider = component$((props) => {
815
859
  deep: false
816
860
  });
817
861
  const navResolver = {};
818
- const loaderState = _weakSerialize(useStore(env.response.loaders, {
819
- deep: false
820
- }));
862
+ const container = _getContextContainer();
863
+ const getSerializationStrategy = (loaderId) => {
864
+ return env.response.loadersSerializationStrategy.get(loaderId) || DEFAULT_LOADERS_SERIALIZATION_STRATEGY;
865
+ };
866
+ const loadersObject = {};
867
+ const loaderState = {};
868
+ for (const [key, value] of Object.entries(env.response.loaders)) {
869
+ loadersObject[key] = value;
870
+ loaderState[key] = createLoaderSignal(loadersObject, key, url, getSerializationStrategy(key), container);
871
+ }
872
+ loadersObject[SerializerSymbol] = (obj) => {
873
+ const loadersSerializationObject = {};
874
+ for (const [k, v] of Object.entries(obj)) {
875
+ loadersSerializationObject[k] = getSerializationStrategy(k) === "always" ? v : _UNINITIALIZED;
876
+ }
877
+ return loadersSerializationObject;
878
+ };
821
879
  const routeInternal = useSignal({
822
880
  type: "initial",
823
881
  dest: url,
@@ -910,7 +968,7 @@ const QwikRouterProvider = component$((props) => {
910
968
  let scroller = document.getElementById(QWIK_ROUTER_SCROLLER);
911
969
  if (!scroller) {
912
970
  scroller = document.getElementById(QWIK_CITY_SCROLLER);
913
- if (scroller) {
971
+ if (scroller && isDev) {
914
972
  console.warn(`Please update your scroller ID to "${QWIK_ROUTER_SCROLLER}" as "${QWIK_CITY_SCROLLER}" is deprecated and will be removed in V3`);
915
973
  }
916
974
  }
@@ -1051,11 +1109,21 @@ const QwikRouterProvider = component$((props) => {
1051
1109
  document.__q_scroll_restore__ = () => restoreScroll(navType, trackUrl, prevUrl, scroller, scrollState);
1052
1110
  }
1053
1111
  const loaders = clientPageData?.loaders;
1054
- const win = window;
1055
1112
  if (loaders) {
1056
- Object.assign(loaderState, loaders);
1113
+ const container2 = _getContextContainer();
1114
+ for (const [key, value] of Object.entries(loaders)) {
1115
+ const signal = loaderState[key];
1116
+ const awaitedValue = await value;
1117
+ loadersObject[key] = awaitedValue;
1118
+ if (!signal) {
1119
+ loaderState[key] = createLoaderSignal(loadersObject, key, trackUrl, DEFAULT_LOADERS_SERIALIZATION_STRATEGY, container2);
1120
+ } else {
1121
+ signal.invalidate();
1122
+ }
1123
+ }
1057
1124
  }
1058
1125
  CLIENT_DATA_CACHE.clear();
1126
+ const win = window;
1059
1127
  if (!win._qRouterSPA) {
1060
1128
  win._qRouterSPA = true;
1061
1129
  history.scrollRestoration = "manual";
@@ -1185,8 +1253,8 @@ const QwikRouterProvider = component$((props) => {
1185
1253
  }
1186
1254
  };
1187
1255
  _waitNextPage().then(() => {
1188
- const container = _getQContainerElement(elm);
1189
- container.setAttribute("q:route", routeName);
1256
+ const container2 = _getQContainerElement(elm);
1257
+ container2.setAttribute(Q_ROUTE, routeName);
1190
1258
  const scrollState2 = currentScrollState(scroller);
1191
1259
  saveScrollHistory(scrollState2);
1192
1260
  win._qRouterScrollEnabled = true;
@@ -1219,7 +1287,7 @@ const QwikRouterMockProvider = component$((props) => {
1219
1287
  }, {
1220
1288
  deep: false
1221
1289
  });
1222
- const loaderState = useSignal({});
1290
+ const loaderState = {};
1223
1291
  const routeInternal = useSignal({
1224
1292
  type: "initial",
1225
1293
  dest: url
@@ -1464,24 +1532,26 @@ const globalActionQrl = (actionQrl, ...rest) => {
1464
1532
  const routeAction$ = /* @__PURE__ */ implicit$FirstArg(routeActionQrl);
1465
1533
  const globalAction$ = /* @__PURE__ */ implicit$FirstArg(globalActionQrl);
1466
1534
  const routeLoaderQrl = (loaderQrl, ...rest) => {
1467
- const { id, validators } = getValidators(rest, loaderQrl);
1535
+ const { id, validators, serializationStrategy } = getValidators(rest, loaderQrl);
1468
1536
  function loader() {
1469
- return useContext(RouteStateContext, (state) => {
1470
- if (!(id in state)) {
1471
- throw new Error(`routeLoader$ "${loaderQrl.getSymbol()}" was invoked in a route where it was not declared.
1537
+ const iCtx = _useInvokeContext();
1538
+ const state = iCtx.$container$.resolveContext(iCtx.$hostElement$, RouteStateContext);
1539
+ if (!(id in state)) {
1540
+ throw new Error(`routeLoader$ "${loaderQrl.getSymbol()}" was invoked in a route where it was not declared.
1472
1541
  This is because the routeLoader$ was not exported in a 'layout.tsx' or 'index.tsx' file of the existing route.
1473
1542
  For more information check: https://qwik.dev/docs/route-loader/
1474
1543
 
1475
1544
  If your are managing reusable logic or a library it is essential that this function is re-exported from within 'layout.tsx' or 'index.tsx file of the existing route otherwise it will not run or throw exception.
1476
1545
  For more information check: https://qwik.dev/docs/re-exporting-loaders/`);
1477
- }
1478
- return _wrapStore(state, id);
1479
- });
1546
+ }
1547
+ untrack(() => state[id].value);
1548
+ return state[id];
1480
1549
  }
1481
1550
  loader.__brand = "server_loader";
1482
1551
  loader.__qrl = loaderQrl;
1483
1552
  loader.__validators = validators;
1484
1553
  loader.__id = id;
1554
+ loader.__serializationStrategy = serializationStrategy;
1485
1555
  Object.freeze(loader);
1486
1556
  return loader;
1487
1557
  };
@@ -1710,6 +1780,7 @@ const serverQrl = (qrl, options) => {
1710
1780
  const server$ = /* @__PURE__ */ implicit$FirstArg(serverQrl);
1711
1781
  const getValidators = (rest, qrl) => {
1712
1782
  let id;
1783
+ let serializationStrategy = DEFAULT_LOADERS_SERIALIZATION_STRATEGY;
1713
1784
  const validators = [];
1714
1785
  if (rest.length === 1) {
1715
1786
  const options = rest[0];
@@ -1718,6 +1789,9 @@ const getValidators = (rest, qrl) => {
1718
1789
  validators.push(options);
1719
1790
  } else {
1720
1791
  id = options.id;
1792
+ if (options.serializationStrategy) {
1793
+ serializationStrategy = options.serializationStrategy;
1794
+ }
1721
1795
  if (options.validation) {
1722
1796
  validators.push(...options.validation);
1723
1797
  }
@@ -1738,7 +1812,8 @@ const getValidators = (rest, qrl) => {
1738
1812
  }
1739
1813
  return {
1740
1814
  validators: validators.reverse(),
1741
- id
1815
+ id,
1816
+ serializationStrategy
1742
1817
  };
1743
1818
  };
1744
1819
  const deserializeStream = async function* (stream, ctxElm, abortSignal) {