crelte 0.3.1 → 0.3.2

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.
@@ -54,6 +54,8 @@ export default class Router {
54
54
  this._loading = new Writable(false);
55
55
  this._loadingProgress = new Writable(0);
56
56
  this._onRequest = new Listeners();
57
+ // these functions are exposed to the init "module"
58
+ // but should not be used by anybody else
57
59
  this._internal = {
58
60
  onLoaded: () => { },
59
61
  onNothingLoaded: () => { },
@@ -130,10 +132,11 @@ export default class Router {
130
132
  * ```
131
133
  */
132
134
  open(target, opts = {}) {
133
- const req = this.inner.targetToRequest(target, {
134
- ...opts,
135
+ const req = this.targetOrUpdateToRequest(target, opts, {
135
136
  origin: 'manual',
136
137
  });
138
+ if (!req)
139
+ return;
137
140
  this.inner.open(req);
138
141
  }
139
142
  /**
@@ -147,7 +150,17 @@ export default class Router {
147
150
  * And will clear the scrollY value if you not provide a new one via the `opts`
148
151
  * This will disableLoadData by default if you not provide an override via the `opts`
149
152
  *
150
- * ## Example
153
+ * ## Example using the update function
154
+ * ```
155
+ * import { getRouter } from 'crelte';
156
+ *
157
+ * const router = getRouter();
158
+ *
159
+ * const page = 1;
160
+ * router.push(req => req.setSearchParam('page', page || null));
161
+ * ```
162
+ *
163
+ * ## Example using the route object
151
164
  * ```
152
165
  * import { getRouter } from 'crelte';
153
166
  *
@@ -160,14 +173,15 @@ export default class Router {
160
173
  * ```
161
174
  */
162
175
  push(route, opts = {}) {
163
- // cancel previous request
164
- this.pageLoader.discard();
165
- const req = this.inner.targetToRequest(route, {
166
- ...opts,
176
+ // theoretically string and URL also work but we might
177
+ // change that in the future
178
+ const req = this.targetOrUpdateToRequest(route, opts, {
167
179
  origin: 'push',
168
180
  scrollY: opts.scrollY ?? undefined,
169
181
  disableLoadData: opts.disableLoadData ?? true,
170
182
  });
183
+ if (!req)
184
+ return;
171
185
  this.inner.push(req);
172
186
  }
173
187
  /**
@@ -178,16 +192,26 @@ export default class Router {
178
192
  this.push(route);
179
193
  }
180
194
  /**
181
- * This replaces the state of the route without triggering an event
195
+ * This replaces the state of the route without triggering a new pageload
182
196
  *
183
197
  * You can use this when using some filters for example a search filter
184
198
  *
185
199
  * ## Note
186
200
  * This will always set the origin to 'replace'
187
- * And will clear the scrollY value if you not provide a new one via the `opts`
188
- * This will disableLoadData by default if you not provide an override via the `opts`
201
+ * And will clear the scrollY value if you don't provide a new one via the `opts`
202
+ * This will disableLoadData by default if you don't provide an override via the `opts`
189
203
  *
190
- * ## Example
204
+ * ## Example using the update function
205
+ * ```
206
+ * import { getRouter } from 'crelte';
207
+ *
208
+ * const router = getRouter();
209
+ *
210
+ * const search = 'foo';
211
+ * router.replace(req => req.setSearchParam('search', search));
212
+ * ```
213
+ *
214
+ * ## Example using the route object
191
215
  * ```
192
216
  * import { getRouter } from 'crelte';
193
217
  *
@@ -195,18 +219,20 @@ export default class Router {
195
219
  *
196
220
  * const search = 'foo';
197
221
  * const route = router.route.get();
198
- * route.setSearchParam('search', search ? search : null);
199
- * router.replaceState(route);
222
+ * route.setSearchParam('search', search);
223
+ * router.replace(route);
200
224
  * ```
201
225
  */
202
226
  replace(route, opts = {}) {
203
- // cancel previous request
204
- this.pageLoader.discard();
205
- const req = this.inner.targetToRequest(route, {
227
+ // theoretically string and URL also work but we might
228
+ // change that in the future
229
+ const req = this.targetOrUpdateToRequest(route, opts, {
206
230
  origin: 'replace',
207
231
  scrollY: opts.scrollY ?? undefined,
208
232
  disableLoadData: opts.disableLoadData ?? true,
209
233
  });
234
+ if (!req)
235
+ return;
210
236
  this.inner.replace(req);
211
237
  }
212
238
  /**
@@ -325,6 +351,7 @@ export default class Router {
325
351
  }
326
352
  return resp;
327
353
  }
354
+ // gets called by the InnerRouter when a new route is requested
328
355
  _onRoute(req, changeHistory) {
329
356
  this.destroyRequest();
330
357
  this._request = req;
@@ -338,6 +365,7 @@ export default class Router {
338
365
  this.pageLoader.load(req, { changeHistory });
339
366
  }
340
367
  else {
368
+ this.pageLoader.discard();
341
369
  this._onNothingLoaded(req, { changeHistory });
342
370
  }
343
371
  }
@@ -350,12 +378,13 @@ export default class Router {
350
378
  _onPreload(req) {
351
379
  this.pageLoader.preload(req);
352
380
  }
381
+ // gets called by the pageLoader when teh loadData completes
353
382
  async _onLoaded(resp, req, more) {
354
383
  // check if the render was cancelled
355
384
  if (await req._renderBarrier.ready())
356
385
  return;
357
386
  // when the data is loaded let's update the route of the inner
358
- // this is will only happen if no other route has been requested
387
+ // this will only happen if no other route has been requested
359
388
  // in the meantime
360
389
  more.changeHistory();
361
390
  const route = req.toRoute();
@@ -365,6 +394,7 @@ export default class Router {
365
394
  return resp.data;
366
395
  });
367
396
  }
397
+ // this gets called if loadData is not called
368
398
  async _onNothingLoaded(req, more) {
369
399
  // check if the render was cancelled
370
400
  if (await req._renderBarrier.ready())
@@ -380,10 +410,37 @@ export default class Router {
380
410
  this.setNewRoute(route);
381
411
  });
382
412
  }
413
+ // this is called by the pageLoader if we get a progress update
383
414
  _onProgress(loading, progress) {
384
415
  if (this._loading.get() !== loading)
385
416
  this._loading.set(loading);
386
417
  if (typeof progress === 'number')
387
418
  this._loadingProgress.set(progress);
388
419
  }
420
+ /**
421
+ * Transforms a target to a request
422
+ *
423
+ * returns null if the request was canceled by the update request
424
+ */
425
+ targetOrUpdateToRequest(target, opts = {}, forcedOpts = {}) {
426
+ // we have an update request
427
+ if (typeof target === 'function') {
428
+ const route = this.route.get();
429
+ if (!route) {
430
+ throw new Error('route to update missing in first loadData call');
431
+ }
432
+ // first get a req
433
+ const req = this.inner.targetToRequest(route, opts);
434
+ // check if the request was canceled by the update request
435
+ if (target(req) === false)
436
+ return null;
437
+ // now we add the forcedOpts
438
+ req._updateOpts(forcedOpts);
439
+ return req;
440
+ }
441
+ return this.inner.targetToRequest(target, {
442
+ ...opts,
443
+ ...forcedOpts,
444
+ });
445
+ }
389
446
  }
@@ -1,6 +1,6 @@
1
- import Router from './Router.js';
1
+ import Router, { type UpdateRequest } from './Router.js';
2
2
  import Route, { type RouteOptions } from './Route.js';
3
3
  import Request, { type RequestOptions, type DelayRender } from './Request.js';
4
4
  import Site from './Site.js';
5
- export { Router, Route, RouteOptions, Site, Request, DelayRender, RequestOptions, };
5
+ export { Router, UpdateRequest, Route, RouteOptions, Site, Request, DelayRender, RequestOptions, };
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/routing/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,KAAK,EAAE,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,OAAO,EAAE,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACN,MAAM,EACN,KAAK,EACL,YAAY,EACZ,IAAI,EACJ,OAAO,EACP,WAAW,EACX,cAAc,GACd,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/routing/index.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,EAAE,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,KAAK,EAAE,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,OAAO,EAAE,EAAE,KAAK,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACN,MAAM,EACN,aAAa,EACb,KAAK,EACL,YAAY,EACZ,IAAI,EACJ,OAAO,EACP,WAAW,EACX,cAAc,GACd,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crelte",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "author": "Crelte <support@crelte.com>",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -44,6 +44,10 @@
44
44
  "types": "./dist/routing/index.d.ts",
45
45
  "default": "./dist/routing/index.js"
46
46
  },
47
+ "./entry": {
48
+ "types": "./dist/entry/index.d.ts",
49
+ "default": "./dist/entry/index.js"
50
+ },
47
51
  "./ssr": {
48
52
  "types": "./dist/ssr/index.d.ts",
49
53
  "default": "./dist/ssr/index.js"
@@ -71,14 +75,15 @@
71
75
  },
72
76
  "dependencies": {
73
77
  "crelte-std": "^0.1.1",
74
- "svelte": "^4.2.12"
78
+ "svelte": "^4.2.12",
79
+ "trouter": "^4.0.0"
75
80
  },
76
81
  "devDependencies": {
77
82
  "@sveltejs/package": "^2.3.1",
78
83
  "@sveltejs/vite-plugin-svelte": "^3.0.0",
79
84
  "svelte-check": "^4.1.4",
80
85
  "typescript-svelte-plugin": "^0.3.45",
81
- "vitest": "^2.0.0",
82
- "vite": "^5.0"
86
+ "vite": "^5.0",
87
+ "vitest": "^2.0.0"
83
88
  }
84
89
  }
@@ -0,0 +1,71 @@
1
+ import { Pattern, Trouter } from 'trouter';
2
+ import { Crelte, CrelteRequest, Entry, QueryOptions } from '../index.js';
3
+ import { CrelteEntryRequest, EntryRequest } from './index.js';
4
+ import { GraphQlQuery } from '../graphql/GraphQl.js';
5
+
6
+ export type EntryRouteHandler = (
7
+ cr: CrelteEntryRequest,
8
+ ) => Promise<Entry | null | undefined> | Entry | null | undefined | void;
9
+
10
+ export type EntryRoutes = (router: EntryRouter) => Promise<void> | void;
11
+
12
+ export default class EntryRouter {
13
+ private _crelte: Crelte;
14
+ private inner: Trouter<EntryRouteHandler>;
15
+
16
+ constructor(crelte: Crelte) {
17
+ this._crelte = crelte;
18
+ this.inner = new Trouter();
19
+ }
20
+
21
+ add(pattern: Pattern, ...handlers: EntryRouteHandler[]): this {
22
+ this.inner.add('GET', pattern, ...handlers);
23
+ return this;
24
+ }
25
+
26
+ /**
27
+ * returns an env variable from the craft/.env file.
28
+ */
29
+ getEnv(name: 'ENDPOINT_URL'): string;
30
+ getEnv(name: 'CRAFT_WEB_URL'): string;
31
+ getEnv(name: string): string | null;
32
+ getEnv(name: string): string | null {
33
+ return this._crelte.getEnv(name);
34
+ }
35
+
36
+ /**
37
+ * Run a GraphQl Query
38
+ *
39
+ * @param query the default export from a graphql file or the gql`query {}`
40
+ * function
41
+ * @param variables variables that should be passed to the
42
+ * graphql query
43
+ */
44
+ async query(
45
+ query: GraphQlQuery,
46
+ variables: Record<string, unknown> = {},
47
+ opts: QueryOptions = {},
48
+ ): Promise<unknown> {
49
+ // this function is added as convenience
50
+ return this._crelte.graphQl.query(query, variables, opts);
51
+ }
52
+
53
+ /** @hidden */
54
+ async _handle(cr: CrelteRequest): Promise<Entry | null> {
55
+ const { params, handlers } = this.inner.find('GET', cr.req.uri);
56
+
57
+ if (!handlers.length) return null;
58
+
59
+ const er = new EntryRequest(cr.req.url, cr.req.site, {
60
+ params: new Map(Object.entries(params)),
61
+ });
62
+ const cer = new CrelteEntryRequest(cr, er);
63
+
64
+ for (const handler of handlers) {
65
+ const res = await handler(cer);
66
+ if (res) return res;
67
+ }
68
+
69
+ return null;
70
+ }
71
+ }
@@ -0,0 +1,48 @@
1
+ import { Crelte, CrelteRequest } from '../index.js';
2
+ import { Request, RequestOptions, Site } from '../routing/index.js';
3
+ import EntryRouter, { EntryRouteHandler, EntryRoutes } from './EntryRouter.js';
4
+
5
+ export { EntryRouter, type EntryRouteHandler, type EntryRoutes };
6
+
7
+ export type Entry = {
8
+ sectionHandle: string;
9
+ typeHandle: string;
10
+ [key: string]: any;
11
+ };
12
+
13
+ export type EntryRequestOptions = RequestOptions & {
14
+ params?: Map<string, string>;
15
+ };
16
+
17
+ export class EntryRequest extends Request {
18
+ private params: Map<string, string>;
19
+
20
+ constructor(url: string | URL, site: Site, opts: EntryRequestOptions = {}) {
21
+ super(url, site, opts);
22
+
23
+ this.params = opts.params ?? new Map();
24
+ }
25
+
26
+ /**
27
+ * returns the url params from the request
28
+ *
29
+ * @example
30
+ * ```js
31
+ * router.get('/blog/:slug', async (cs, req) => {
32
+ * return Response.json({ slug: cs.getParam('slug') });
33
+ * });
34
+ * ```
35
+ */
36
+ getParam(name: string): string | null {
37
+ return this.params.get(name) ?? null;
38
+ }
39
+ }
40
+
41
+ export class CrelteEntryRequest extends CrelteRequest {
42
+ req: EntryRequest;
43
+
44
+ constructor(inner: Crelte, req: EntryRequest) {
45
+ super(inner, req);
46
+ this.req = req;
47
+ }
48
+ }
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  LoadDataFn,
16
16
  LoadDataObj,
17
17
  } from './loadData/index.js';
18
+ import { Entry } from './entry/index.js';
18
19
 
19
20
  export {
20
21
  Crelte,
@@ -25,6 +26,7 @@ export {
25
26
  type LoadDataFn,
26
27
  type LoadDataObj,
27
28
  type LoadDataArray,
29
+ type Entry,
28
30
  };
29
31
 
30
32
  /**
@@ -2,7 +2,7 @@ import { CrelteBuilder } from '../Crelte.js';
2
2
  import CrelteRequest from '../CrelteRequest.js';
3
3
  import { GraphQlQuery } from '../graphql/GraphQl.js';
4
4
  import { SiteFromGraphQl } from '../routing/Site.js';
5
- import { loadFn, pluginsBeforeRender, setupPlugins } from './shared.js';
5
+ import { pluginsBeforeRender, prepareLoadFn, setupPlugins } from './shared.js';
6
6
  import { tick } from 'svelte';
7
7
 
8
8
  /**
@@ -49,7 +49,7 @@ const mainDataDefault = {
49
49
  * });
50
50
  * ```
51
51
  */
52
- export function main(data: MainData) {
52
+ export async function main(data: MainData) {
53
53
  data = { ...mainDataDefault, ...data };
54
54
 
55
55
  // rendering steps
@@ -97,11 +97,18 @@ export function main(data: MainData) {
97
97
  // setup plugins
98
98
  setupPlugins(crelte, data.app.plugins ?? []);
99
99
 
100
+ const loadFn = await prepareLoadFn(
101
+ crelte,
102
+ data.app,
103
+ data.entryQuery,
104
+ data.globalQuery,
105
+ );
106
+
100
107
  // setup load Data
101
108
 
102
109
  crelte.router._internal.onLoad = (req, opts) => {
103
110
  const cr = new CrelteRequest(crelte, req);
104
- return loadFn(cr, data.app, data.entryQuery, data.globalQuery, opts);
111
+ return loadFn(cr, opts);
105
112
  };
106
113
 
107
114
  // render Space
@@ -1,6 +1,6 @@
1
1
  import { CrelteBuilder } from '../Crelte.js';
2
2
  import { SiteFromGraphQl } from '../routing/Site.js';
3
- import { loadFn, pluginsBeforeRender, setupPlugins } from './shared.js';
3
+ import { pluginsBeforeRender, prepareLoadFn, setupPlugins } from './shared.js';
4
4
  import SsrComponents from '../ssr/SsrComponents.js';
5
5
  import SsrCache from '../ssr/SsrCache.js';
6
6
  import ServerCookies from '../cookies/ServerCookies.js';
@@ -85,11 +85,18 @@ export async function main(data: MainData): Promise<{
85
85
  // setup plugins
86
86
  setupPlugins(crelte, data.app.plugins ?? []);
87
87
 
88
+ const loadFn = await prepareLoadFn(
89
+ crelte,
90
+ data.app,
91
+ data.entryQuery,
92
+ data.globalQuery,
93
+ );
94
+
88
95
  // setup load Data
89
96
 
90
97
  crelte.router._internal.onLoad = req => {
91
98
  const cr = new CrelteRequest(crelte, req);
92
- return loadFn(cr, data.app, data.entryQuery, data.globalQuery);
99
+ return loadFn(cr);
93
100
  };
94
101
 
95
102
  const { success, redirect, req, props } =
@@ -1,5 +1,6 @@
1
1
  import Crelte from '../Crelte.js';
2
2
  import CrelteRequest from '../CrelteRequest.js';
3
+ import EntryRouter, { EntryRoutes } from '../entry/EntryRouter.js';
3
4
  import { GraphQlQuery } from '../graphql/GraphQl.js';
4
5
  import { LoadData, callLoadData } from '../loadData/index.js';
5
6
  import { PluginCreator } from '../plugins/Plugins.js';
@@ -12,6 +13,8 @@ interface App<E, T> {
12
13
  loadEntryData?: LoadData<any>;
13
14
 
14
15
  templates?: Record<string, LazyTemplateModule<E, T>>;
16
+
17
+ entryRoutes?: EntryRoutes;
15
18
  }
16
19
 
17
20
  interface TemplateModule<E, T> {
@@ -58,14 +61,43 @@ export function getEntry(page: any): any {
58
61
  };
59
62
  }
60
63
 
61
- export async function loadFn<D, E, T>(
64
+ // todo it would be nice to call this only once per server start
65
+ export async function prepareLoadFn<E, T>(
66
+ crelte: Crelte,
67
+ app: App<E, T>,
68
+ entryQuery: GraphQlQuery,
69
+ globalQuery?: GraphQlQuery,
70
+ ): Promise<(cr: CrelteRequest, loadOpts?: LoadOptions) => Promise<any>> {
71
+ const templateModules = prepareTemplates(app.templates ?? {});
72
+ let entryRouter: EntryRouter | null = null;
73
+ if (app.entryRoutes) {
74
+ entryRouter = new EntryRouter(crelte);
75
+ await app.entryRoutes(entryRouter);
76
+ }
77
+
78
+ return async (cr, loadOpts) => {
79
+ return await loadFn(
80
+ cr,
81
+ app,
82
+ templateModules,
83
+ entryRouter,
84
+ entryQuery,
85
+ globalQuery,
86
+ loadOpts,
87
+ );
88
+ };
89
+ }
90
+
91
+ async function loadFn<E, T>(
62
92
  cr: CrelteRequest,
63
93
  app: App<E, T>,
94
+ templateModules: Map<string, LazyTemplateModule<E, T>>,
95
+ entryRouter: EntryRouter | null,
64
96
  entryQuery: GraphQlQuery,
65
97
  globalQuery?: GraphQlQuery,
66
98
  loadOpts?: LoadOptions,
67
99
  ): Promise<any> {
68
- let dataProm: Promise<D> | null = null;
100
+ let dataProm: Promise<any> | null = null;
69
101
  // @ts-ignore
70
102
  if (app.loadData) {
71
103
  throw new Error(
@@ -93,43 +125,29 @@ export async function loadFn<D, E, T>(
93
125
  })();
94
126
  }
95
127
 
96
- let pageProm = null;
97
- if (cr.req.siteMatches()) {
98
- let uri = decodeURI(cr.req.uri);
99
- if (uri.startsWith('/')) uri = uri.substring(1);
100
- if (uri === '' || uri === '/') uri = '__home__';
101
-
102
- pageProm = cr.query(entryQuery, {
103
- uri,
104
- siteId: cr.site.id,
105
- });
106
- }
128
+ const entryProm = queryEntry(cr, app, entryRouter, entryQuery);
107
129
 
108
130
  const pluginsLoadGlobalData = cr.events.trigger('loadGlobalData', cr);
109
131
 
110
132
  // loading progress is at 20%
111
133
  loadOpts?.setProgress(0.2);
112
134
 
113
- const [data, global, page] = await Promise.all([
135
+ const [data, global, entry] = await Promise.all([
114
136
  dataProm,
115
137
  globalProm,
116
- pageProm,
138
+ entryProm,
117
139
  ...pluginsLoadGlobalData,
118
140
  ]);
119
141
 
120
- if (global) {
121
- cr.globals._setData(cr.site.id, global);
122
- } else if (!cr.globals._wasLoaded(cr.site.id)) {
123
- // we need to set the global data to an empty object
124
- // so any waiters get's triggered
125
- cr.globals._setData(cr.site.id, {});
142
+ // global is only set if !wasLoaded but we need to store something
143
+ // even if no globalQuery exists
144
+ if (global || !cr.globals._wasLoaded(cr.site.id)) {
145
+ cr.globals._setData(cr.site.id, global ?? {});
126
146
  }
127
147
 
128
- const entry = getEntry(page);
129
-
130
148
  let template;
131
149
  if (app.templates) {
132
- template = await loadTemplate(app.templates, entry);
150
+ template = await loadTemplate(templateModules, entry);
133
151
  } else {
134
152
  throw new Error('App must have templates or loadTemplate method');
135
153
  }
@@ -180,12 +198,39 @@ function parseFilename(path: string): [string, string] {
180
198
  return [name, ext];
181
199
  }
182
200
 
183
- async function loadTemplate<E, T>(
201
+ async function queryEntry<E, T>(
202
+ cr: CrelteRequest,
203
+ app: App<E, T>,
204
+ entryRouter: EntryRouter | null,
205
+ entryQuery: GraphQlQuery,
206
+ ): Promise<any | null> {
207
+ // check
208
+ if (entryRouter) {
209
+ const entry = await entryRouter._handle(cr);
210
+ if (entry) return entry;
211
+ }
212
+
213
+ if (cr.req.siteMatches()) {
214
+ let uri = decodeURI(cr.req.uri);
215
+ if (uri.startsWith('/')) uri = uri.substring(1);
216
+ if (uri === '' || uri === '/') uri = '__home__';
217
+
218
+ const page = await cr.query(entryQuery, {
219
+ uri,
220
+ siteId: cr.site.id,
221
+ });
222
+
223
+ return getEntry(page);
224
+ }
225
+
226
+ return null;
227
+ }
228
+
229
+ function prepareTemplates<E, T>(
184
230
  rawModules: Record<string, LazyTemplateModule<E, T>>,
185
- entry: E,
186
- ): Promise<TemplateModule<E, T>> {
231
+ ): Map<string, LazyTemplateModule<E, T>> {
187
232
  // parse modules
188
- const modules = new Map(
233
+ return new Map(
189
234
  Object.entries(rawModules)
190
235
  .map(([path, mod]) => {
191
236
  const [name, _ext] = parseFilename(path);
@@ -193,7 +238,12 @@ async function loadTemplate<E, T>(
193
238
  })
194
239
  .filter(([name, _mod]) => !!name),
195
240
  );
241
+ }
196
242
 
243
+ async function loadTemplate<E, T>(
244
+ modules: Map<string, LazyTemplateModule<E, T>>,
245
+ entry: E,
246
+ ): Promise<TemplateModule<E, T>> {
197
247
  const entr = entry as any;
198
248
  const handle = `${entr.sectionHandle}-${entr.typeHandle}`;
199
249
 
@@ -187,6 +187,20 @@ export default class Route {
187
187
  return this.url.hash;
188
188
  }
189
189
 
190
+ /**
191
+ * Set the hash of the route
192
+ *
193
+ * ## Example
194
+ * ```
195
+ * const route = new Route('https://example.com/foo/bar/', null);
196
+ * route.hash = '#hash';
197
+ * console.log(route.url.href); // 'https://example.com/foo/bar/#hash'
198
+ * ```
199
+ */
200
+ set hash(hash: string) {
201
+ this.url.hash = hash;
202
+ }
203
+
190
204
  /**
191
205
  * Checks if there are previous routes which would allow it to go back
192
206
  */