crelte 0.1.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.
- package/LICENSE.md +41 -0
- package/dist/Crelte.d.ts +55 -0
- package/dist/Crelte.d.ts.map +1 -0
- package/dist/Crelte.js +106 -0
- package/dist/CrelteBase.d.ts +16 -0
- package/dist/CrelteBase.d.ts.map +1 -0
- package/dist/CrelteBase.js +1 -0
- package/dist/CrelteRouted.d.ts +50 -0
- package/dist/CrelteRouted.d.ts.map +1 -0
- package/dist/CrelteRouted.js +88 -0
- package/dist/blocks/Blocks.d.ts +35 -0
- package/dist/blocks/Blocks.d.ts.map +1 -0
- package/dist/blocks/Blocks.js +100 -0
- package/dist/blocks/Blocks.svelte +21 -0
- package/dist/blocks/Blocks.svelte.d.ts +24 -0
- package/dist/blocks/Blocks.svelte.d.ts.map +1 -0
- package/dist/blocks/index.d.ts +5 -0
- package/dist/blocks/index.d.ts.map +1 -0
- package/dist/blocks/index.js +3 -0
- package/dist/cookies/ClientCookies.d.ts +9 -0
- package/dist/cookies/ClientCookies.d.ts.map +1 -0
- package/dist/cookies/ClientCookies.js +22 -0
- package/dist/cookies/ServerCookies.d.ts +13 -0
- package/dist/cookies/ServerCookies.d.ts.map +1 -0
- package/dist/cookies/ServerCookies.js +31 -0
- package/dist/cookies/index.d.ts +20 -0
- package/dist/cookies/index.d.ts.map +1 -0
- package/dist/cookies/index.js +1 -0
- package/dist/cookies/utils.d.ts +12 -0
- package/dist/cookies/utils.d.ts.map +1 -0
- package/dist/cookies/utils.js +32 -0
- package/dist/graphql/GraphQl.d.ts +60 -0
- package/dist/graphql/GraphQl.d.ts.map +1 -0
- package/dist/graphql/GraphQl.js +197 -0
- package/dist/graphql/gql.test.d.ts +2 -0
- package/dist/graphql/gql.test.d.ts.map +1 -0
- package/dist/graphql/gql.test.js +80 -0
- package/dist/graphql/index.d.ts +3 -0
- package/dist/graphql/index.d.ts.map +1 -0
- package/dist/graphql/index.js +2 -0
- package/dist/index.d.ts +67 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +83 -0
- package/dist/init/client.d.ts +13 -0
- package/dist/init/client.d.ts.map +1 -0
- package/dist/init/client.js +129 -0
- package/dist/init/server.d.ts +38 -0
- package/dist/init/server.d.ts.map +1 -0
- package/dist/init/server.js +95 -0
- package/dist/init/shared.d.ts +29 -0
- package/dist/init/shared.d.ts.map +1 -0
- package/dist/init/shared.js +154 -0
- package/dist/loadData/Globals.d.ts +33 -0
- package/dist/loadData/Globals.d.ts.map +1 -0
- package/dist/loadData/Globals.js +119 -0
- package/dist/loadData/index.d.ts +25 -0
- package/dist/loadData/index.d.ts.map +1 -0
- package/dist/loadData/index.js +39 -0
- package/dist/plugins/Events.d.ts +11 -0
- package/dist/plugins/Events.d.ts.map +1 -0
- package/dist/plugins/Events.js +29 -0
- package/dist/plugins/Plugins.d.ts +12 -0
- package/dist/plugins/Plugins.d.ts.map +1 -0
- package/dist/plugins/Plugins.js +12 -0
- package/dist/plugins/index.d.ts +5 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +2 -0
- package/dist/routing/History.d.ts +22 -0
- package/dist/routing/History.d.ts.map +1 -0
- package/dist/routing/History.js +36 -0
- package/dist/routing/InnerRouter.d.ts +111 -0
- package/dist/routing/InnerRouter.d.ts.map +1 -0
- package/dist/routing/InnerRouter.js +397 -0
- package/dist/routing/PageLoader.d.ts +37 -0
- package/dist/routing/PageLoader.d.ts.map +1 -0
- package/dist/routing/PageLoader.js +72 -0
- package/dist/routing/Route.d.ts +82 -0
- package/dist/routing/Route.d.ts.map +1 -0
- package/dist/routing/Route.js +134 -0
- package/dist/routing/Router.d.ts +162 -0
- package/dist/routing/Router.d.ts.map +1 -0
- package/dist/routing/Router.js +333 -0
- package/dist/routing/Site.d.ts +47 -0
- package/dist/routing/Site.d.ts.map +1 -0
- package/dist/routing/Site.js +48 -0
- package/dist/routing/index.d.ts +5 -0
- package/dist/routing/index.d.ts.map +1 -0
- package/dist/routing/index.js +4 -0
- package/dist/ssr/SsrCache.d.ts +12 -0
- package/dist/ssr/SsrCache.d.ts.map +1 -0
- package/dist/ssr/SsrCache.js +50 -0
- package/dist/ssr/SsrComponents.d.ts +7 -0
- package/dist/ssr/SsrComponents.d.ts.map +1 -0
- package/dist/ssr/SsrComponents.js +30 -0
- package/dist/ssr/index.d.ts +4 -0
- package/dist/ssr/index.d.ts.map +1 -0
- package/dist/ssr/index.js +3 -0
- package/package.json +79 -0
- package/src/Crelte.ts +135 -0
- package/src/CrelteBase.ts +24 -0
- package/src/CrelteRouted.ts +128 -0
- package/src/blocks/Blocks.svelte +68 -0
- package/src/blocks/Blocks.ts +155 -0
- package/src/blocks/index.ts +14 -0
- package/src/cookies/ClientCookies.ts +30 -0
- package/src/cookies/ServerCookies.ts +42 -0
- package/src/cookies/index.ts +24 -0
- package/src/cookies/utils.ts +53 -0
- package/src/graphql/GraphQl.ts +281 -0
- package/src/graphql/gql.test.ts +123 -0
- package/src/graphql/index.ts +8 -0
- package/src/index.ts +109 -0
- package/src/init/client.ts +190 -0
- package/src/init/server.ts +177 -0
- package/src/init/shared.ts +221 -0
- package/src/loadData/Globals.ts +150 -0
- package/src/loadData/index.ts +67 -0
- package/src/plugins/Events.ts +50 -0
- package/src/plugins/Plugins.ts +23 -0
- package/src/plugins/index.ts +5 -0
- package/src/routing/History.ts +52 -0
- package/src/routing/InnerRouter.ts +469 -0
- package/src/routing/PageLoader.ts +112 -0
- package/src/routing/Route.ts +184 -0
- package/src/routing/Router.ts +476 -0
- package/src/routing/Site.ts +65 -0
- package/src/routing/index.ts +5 -0
- package/src/ssr/SsrCache.ts +61 -0
- package/src/ssr/SsrComponents.ts +34 -0
- package/src/ssr/index.ts +4 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { CrelteBuilder } from '../Crelte.js';
|
|
2
|
+
import { SiteFromGraphQl } from '../routing/Site.js';
|
|
3
|
+
import { loadFn, pluginsBeforeRender, setupPlugins } from './shared.js';
|
|
4
|
+
import SsrComponents from '../ssr/SsrComponents.js';
|
|
5
|
+
import SsrCache from '../ssr/SsrCache.js';
|
|
6
|
+
import ServerCookies from '../cookies/ServerCookies.js';
|
|
7
|
+
|
|
8
|
+
export type ServerData = {
|
|
9
|
+
url: string;
|
|
10
|
+
htmlTemplate: string;
|
|
11
|
+
ssrManifest: Record<string, string[]>;
|
|
12
|
+
acceptLang?: string;
|
|
13
|
+
endpoint: string;
|
|
14
|
+
craftWeb: string;
|
|
15
|
+
viteEnv: Map<string, string>;
|
|
16
|
+
cookies?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type MainData = {
|
|
20
|
+
/// svelte app component
|
|
21
|
+
app: any;
|
|
22
|
+
entryQuery: any;
|
|
23
|
+
globalQuery?: any;
|
|
24
|
+
|
|
25
|
+
serverData: ServerData;
|
|
26
|
+
|
|
27
|
+
// debug
|
|
28
|
+
graphQlDebug?: boolean;
|
|
29
|
+
debugTiming?: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export async function main(data: MainData): Promise<{
|
|
33
|
+
status: number;
|
|
34
|
+
location?: string;
|
|
35
|
+
html?: string;
|
|
36
|
+
setCookies?: string[];
|
|
37
|
+
}> {
|
|
38
|
+
const builder = new CrelteBuilder();
|
|
39
|
+
|
|
40
|
+
// setup viteEnv
|
|
41
|
+
data.serverData.viteEnv.forEach((v, k) => {
|
|
42
|
+
builder.ssrCache.set(k, v);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const endpoint = data.serverData.endpoint;
|
|
46
|
+
builder.ssrCache.set('ENDPOINT_URL', endpoint);
|
|
47
|
+
builder.ssrCache.set('CRAFT_WEB_URL', data.serverData.craftWeb);
|
|
48
|
+
builder.setupGraphQl(endpoint, {
|
|
49
|
+
debug: data.graphQlDebug,
|
|
50
|
+
debugTiming: data.debugTiming,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const cookies = data.serverData.cookies ?? '';
|
|
54
|
+
builder.setupCookies(cookies);
|
|
55
|
+
|
|
56
|
+
const csites = await loadSites(builder);
|
|
57
|
+
builder.ssrCache.set('crelteSites', csites);
|
|
58
|
+
builder.setupRouter(csites);
|
|
59
|
+
|
|
60
|
+
const crelte = builder.build();
|
|
61
|
+
|
|
62
|
+
// setup plugins
|
|
63
|
+
setupPlugins(crelte, data.app.plugins ?? []);
|
|
64
|
+
|
|
65
|
+
// setup load Data
|
|
66
|
+
|
|
67
|
+
crelte.router._internal.onLoad = (route, site) => {
|
|
68
|
+
const cr = crelte.toRouted(route, site);
|
|
69
|
+
return loadFn(cr, data.app, data.entryQuery, data.globalQuery);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const { success, redirect, route, site, props } =
|
|
73
|
+
await crelte.router._internal.initServer(
|
|
74
|
+
data.serverData.url,
|
|
75
|
+
data.serverData.acceptLang,
|
|
76
|
+
);
|
|
77
|
+
if (!success) throw props;
|
|
78
|
+
|
|
79
|
+
if (redirect) {
|
|
80
|
+
return {
|
|
81
|
+
status: 302,
|
|
82
|
+
location: route.url.toString(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const context = crelte._getContext();
|
|
87
|
+
const ssrComponents = new SsrComponents();
|
|
88
|
+
ssrComponents.addToContext(context);
|
|
89
|
+
|
|
90
|
+
pluginsBeforeRender(crelte.toRouted(route, site));
|
|
91
|
+
crelte.globals._updateSiteId(site.id);
|
|
92
|
+
// eslint-disable-next-line prefer-const
|
|
93
|
+
let { html, head } = data.app.default.render(props, { context });
|
|
94
|
+
|
|
95
|
+
head += ssrComponents.toHead(data.serverData.ssrManifest);
|
|
96
|
+
head += crelte.ssrCache._exportToHead();
|
|
97
|
+
|
|
98
|
+
let htmlTemplate = data.serverData.htmlTemplate;
|
|
99
|
+
htmlTemplate = htmlTemplate.replace('<!--page-lang-->', site.language);
|
|
100
|
+
|
|
101
|
+
const finalHtml = htmlTemplate
|
|
102
|
+
.replace('</head>', head + '\n\t</head>')
|
|
103
|
+
.replace('<!--ssr-body-->', html);
|
|
104
|
+
|
|
105
|
+
const entry = props.entry;
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
status:
|
|
109
|
+
entry.sectionHandle === 'error' ? parseInt(entry.typeHandle) : 200,
|
|
110
|
+
html: finalHtml,
|
|
111
|
+
setCookies: (crelte.cookies as ServerCookies)._getSetCookiesHeaders(),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export type Error = {
|
|
116
|
+
status: number;
|
|
117
|
+
message: any;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export type MainErrorData = {
|
|
121
|
+
/// svelte app component
|
|
122
|
+
error: Error;
|
|
123
|
+
errorPage: any;
|
|
124
|
+
|
|
125
|
+
serverData: ServerData;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export async function mainError(
|
|
129
|
+
data: MainErrorData,
|
|
130
|
+
): Promise<{ status: number; html?: string }> {
|
|
131
|
+
const ssrCache = new SsrCache();
|
|
132
|
+
|
|
133
|
+
const context = new Map();
|
|
134
|
+
const ssrComponents = new SsrComponents();
|
|
135
|
+
ssrComponents.addToContext(context);
|
|
136
|
+
|
|
137
|
+
ssrCache.set('ERROR', data.error);
|
|
138
|
+
|
|
139
|
+
// eslint-disable-next-line prefer-const
|
|
140
|
+
let { html, head } = data.errorPage.default.render(data.error, { context });
|
|
141
|
+
|
|
142
|
+
head += ssrComponents.toHead(data.serverData.ssrManifest);
|
|
143
|
+
head += ssrCache._exportToHead();
|
|
144
|
+
|
|
145
|
+
let htmlTemplate = data.serverData.htmlTemplate;
|
|
146
|
+
htmlTemplate = htmlTemplate.replace('<!--page-lang-->', 'de');
|
|
147
|
+
|
|
148
|
+
const finalHtml = htmlTemplate
|
|
149
|
+
.replace('<!--ssr-head-->', head)
|
|
150
|
+
.replace('<!--ssr-body-->', html);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
status: data.error.status,
|
|
154
|
+
html: finalHtml,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// requires, GraphQl, SsrCache
|
|
159
|
+
async function loadSites(builder: CrelteBuilder): Promise<SiteFromGraphQl[]> {
|
|
160
|
+
if (!builder.graphQl) throw new Error();
|
|
161
|
+
|
|
162
|
+
if ('CRAFT_SITES_CACHED' in globalThis) {
|
|
163
|
+
// @ts-ignore
|
|
164
|
+
return globalThis['CRAFT_SITES_CACHED'];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const resp = (await builder.graphQl.request(
|
|
168
|
+
'query { crelteSites { id baseUrl language name handle primary } }',
|
|
169
|
+
{},
|
|
170
|
+
// don't cache since we cache ourself
|
|
171
|
+
{ caching: false },
|
|
172
|
+
)) as { crelteSites: SiteFromGraphQl[] };
|
|
173
|
+
|
|
174
|
+
// @ts-ignore
|
|
175
|
+
globalThis['CRAFT_SITES_CACHED'] = resp.crelteSites;
|
|
176
|
+
return resp.crelteSites;
|
|
177
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import CrelteRouted, { GraphQlQuery } from '../CrelteRouted.js';
|
|
2
|
+
import Crelte from '../Crelte.js';
|
|
3
|
+
import { LoadData, callLoadData } from '../loadData/index.js';
|
|
4
|
+
import { PluginCreator } from '../plugins/Plugins.js';
|
|
5
|
+
import { LoadOptions } from '../routing/PageLoader.js';
|
|
6
|
+
|
|
7
|
+
interface App<D, E, T> {
|
|
8
|
+
loadGlobalData?: LoadData<D>;
|
|
9
|
+
|
|
10
|
+
// todo: add a generic
|
|
11
|
+
loadEntryData?: LoadData<any>;
|
|
12
|
+
|
|
13
|
+
templates?: Record<string, LazyTemplateModule<E, T>>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface TemplateModule<E, T> {
|
|
17
|
+
// svelte component
|
|
18
|
+
default: any;
|
|
19
|
+
|
|
20
|
+
loadData?(cr: CrelteRouted, entry: E): Promise<T>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type LazyTemplateModule<E, T> =
|
|
24
|
+
| (() => Promise<TemplateModule<E, T>>)
|
|
25
|
+
| TemplateModule<E, T>;
|
|
26
|
+
|
|
27
|
+
export function setupPlugins(crelte: Crelte, plugins: PluginCreator[]) {
|
|
28
|
+
for (const plugin of plugins) {
|
|
29
|
+
const p = plugin(crelte);
|
|
30
|
+
crelte.plugins.add(p);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function pluginsBeforeRender(cr: CrelteRouted): void {
|
|
35
|
+
cr.events.trigger('beforeRender', cr);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get the entry from the page
|
|
40
|
+
*
|
|
41
|
+
* entries should export sectionHandle and typeHandle
|
|
42
|
+
*
|
|
43
|
+
* products should alias productTypeHandle with typeHandle,
|
|
44
|
+
* sectionHandle will be automatically set to product
|
|
45
|
+
*/
|
|
46
|
+
export function getEntry(page: any): any {
|
|
47
|
+
if (page?.entry) return { ...page.entry };
|
|
48
|
+
if (page?.product)
|
|
49
|
+
return {
|
|
50
|
+
sectionHandle: 'product',
|
|
51
|
+
...page.product,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
sectionHandle: 'error',
|
|
56
|
+
typeHandle: '404',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function loadFn<D, E, T>(
|
|
61
|
+
cr: CrelteRouted,
|
|
62
|
+
app: App<D, E, T>,
|
|
63
|
+
entryQuery: GraphQlQuery,
|
|
64
|
+
globalQuery?: GraphQlQuery,
|
|
65
|
+
loadOpts?: LoadOptions,
|
|
66
|
+
): Promise<any> {
|
|
67
|
+
let dataProm: Promise<D> | null = null;
|
|
68
|
+
// @ts-ignore
|
|
69
|
+
if (app.loadData) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
'loadData is ambigous, choose loadGlobalData or ' +
|
|
72
|
+
'loadEntryData depending on if you need access to entry or not?',
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (app.loadGlobalData) {
|
|
77
|
+
dataProm = callLoadData(app.loadGlobalData, cr) as any;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let globalProm: Promise<any> | null = null;
|
|
81
|
+
if (globalQuery && !cr.globals._wasLoaded()) {
|
|
82
|
+
globalProm = (async () => {
|
|
83
|
+
const res = await cr.query(globalQuery);
|
|
84
|
+
// we need to do this sorcery here and can't wait until all
|
|
85
|
+
// globals functions are done, because some global function
|
|
86
|
+
// might want to use globals, and for that the function
|
|
87
|
+
// getOrWait exists on Globals
|
|
88
|
+
cr.globals._setData(cr.site.id, res);
|
|
89
|
+
return res;
|
|
90
|
+
})();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let pageProm = null;
|
|
94
|
+
if (cr.route.site) {
|
|
95
|
+
let uri = decodeURI(cr.route.uri);
|
|
96
|
+
if (uri.startsWith('/')) uri = uri.substring(1);
|
|
97
|
+
if (uri === '' || uri === '/') uri = '__home__';
|
|
98
|
+
|
|
99
|
+
pageProm = cr.query(entryQuery, {
|
|
100
|
+
uri,
|
|
101
|
+
siteId: cr.route.site.id,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const pluginsLoadGlobalData = cr.events.trigger('loadGlobalData', cr);
|
|
106
|
+
|
|
107
|
+
// loading progress is at 20%
|
|
108
|
+
loadOpts?.setProgress(0.2);
|
|
109
|
+
|
|
110
|
+
const [data, global, page] = await Promise.all([
|
|
111
|
+
dataProm,
|
|
112
|
+
globalProm,
|
|
113
|
+
pageProm,
|
|
114
|
+
...pluginsLoadGlobalData,
|
|
115
|
+
]);
|
|
116
|
+
|
|
117
|
+
if (global) {
|
|
118
|
+
cr.globals._setData(cr.site.id, global);
|
|
119
|
+
} else if (!cr.globals._wasLoaded()) {
|
|
120
|
+
// we need to set the global data to an empty object
|
|
121
|
+
// so any waiters get's triggered
|
|
122
|
+
cr.globals._setData(cr.site.id, {});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// allow cr to get the global data
|
|
126
|
+
cr._globalDataLoaded();
|
|
127
|
+
|
|
128
|
+
const entry = getEntry(page);
|
|
129
|
+
|
|
130
|
+
let template;
|
|
131
|
+
if (app.templates) {
|
|
132
|
+
template = await loadTemplate(app.templates, entry);
|
|
133
|
+
} else {
|
|
134
|
+
throw new Error('App must have templates or loadTemplate method');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// loading progress is at 60%
|
|
138
|
+
loadOpts?.setProgress(0.6);
|
|
139
|
+
|
|
140
|
+
const pluginsLoadData = cr.events.trigger('loadData', cr, entry);
|
|
141
|
+
|
|
142
|
+
let loadDataProm = null;
|
|
143
|
+
if (template.loadData) {
|
|
144
|
+
loadDataProm = callLoadData(template.loadData, cr, entry);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let entryDataProm: Promise<any> | null = null;
|
|
148
|
+
if (app.loadEntryData) {
|
|
149
|
+
entryDataProm = callLoadData(app.loadEntryData, cr, entry) as any;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const [templateData, entryData] = await Promise.all([
|
|
153
|
+
loadDataProm,
|
|
154
|
+
entryDataProm,
|
|
155
|
+
...pluginsLoadData,
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
// loading progress is at 100%
|
|
159
|
+
loadOpts?.setProgress(1);
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
...data,
|
|
163
|
+
...entryData,
|
|
164
|
+
entry,
|
|
165
|
+
template: template.default,
|
|
166
|
+
templateData: templateData! as any,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function parseFilename(path: string): [string, string] {
|
|
171
|
+
// get filename with extension
|
|
172
|
+
const slash = path.lastIndexOf('/');
|
|
173
|
+
const filename = path.substring(slash + 1);
|
|
174
|
+
|
|
175
|
+
const extPos = filename.lastIndexOf('.');
|
|
176
|
+
|
|
177
|
+
const name = filename.substring(0, extPos);
|
|
178
|
+
const ext = filename.substring(extPos + 1);
|
|
179
|
+
|
|
180
|
+
return [name, ext];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function loadTemplate<E, T>(
|
|
184
|
+
rawModules: Record<string, LazyTemplateModule<E, T>>,
|
|
185
|
+
entry: E,
|
|
186
|
+
): Promise<TemplateModule<E, T>> {
|
|
187
|
+
// parse modules
|
|
188
|
+
const modules = new Map(
|
|
189
|
+
Object.entries(rawModules)
|
|
190
|
+
.map(([path, mod]) => {
|
|
191
|
+
const [name, _ext] = parseFilename(path);
|
|
192
|
+
return [name, mod] as [string, LazyTemplateModule<E, T>];
|
|
193
|
+
})
|
|
194
|
+
.filter(([name, _mod]) => !!name),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const entr = entry as any;
|
|
198
|
+
const handle = `${entr.sectionHandle}-${entr.typeHandle}`;
|
|
199
|
+
|
|
200
|
+
if (
|
|
201
|
+
// @ts-ignore
|
|
202
|
+
import.meta.env.DEV &&
|
|
203
|
+
!modules.has(handle) &&
|
|
204
|
+
!modules.has(entr.sectionHandle)
|
|
205
|
+
) {
|
|
206
|
+
console.error(
|
|
207
|
+
`Template not found: <${handle}>, expecting file: ${handle}.svelte or ${entr.sectionHandle}.svelte`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const loadMod =
|
|
212
|
+
modules.get(handle) ??
|
|
213
|
+
modules.get(entr.sectionHandle) ??
|
|
214
|
+
modules.get('error-404');
|
|
215
|
+
if (!loadMod) throw new Error('could not find error-404 template');
|
|
216
|
+
|
|
217
|
+
if (typeof loadMod === 'function') {
|
|
218
|
+
return await loadMod();
|
|
219
|
+
}
|
|
220
|
+
return loadMod;
|
|
221
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/*
|
|
2
|
+
// returns a store which get's updated as soon as the site changes
|
|
3
|
+
const emergency = getGlobal('emergency');
|
|
4
|
+
|
|
5
|
+
// returns the data based on the current site (no store)
|
|
6
|
+
cr.getGlobal('emergency')
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Writable } from 'crelte-std/stores';
|
|
10
|
+
|
|
11
|
+
export type GlobalWaiters = [(g: Global<any> | null) => void];
|
|
12
|
+
|
|
13
|
+
export default class Globals {
|
|
14
|
+
// while the globals are not loaded if somebody calls
|
|
15
|
+
// getOrWait then we need to store the waiters
|
|
16
|
+
private waiters: Map<string, GlobalWaiters>;
|
|
17
|
+
private entries: Map<string, Global<any>>;
|
|
18
|
+
private loaded: boolean;
|
|
19
|
+
private prevSiteId: number | null;
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
this.waiters = new Map();
|
|
23
|
+
this.entries = new Map();
|
|
24
|
+
this.loaded = false;
|
|
25
|
+
this.prevSiteId = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
get(name: string): Global<any> | null {
|
|
29
|
+
return this.entries.get(name) ?? null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// call this only in loadGlobalData when by default the global
|
|
33
|
+
// is not available
|
|
34
|
+
getAsync(name: string): Promise<Global<any> | null> | Global<any> | null {
|
|
35
|
+
if (this.loaded) return this.get(name);
|
|
36
|
+
|
|
37
|
+
let waiter = this.waiters.get(name);
|
|
38
|
+
if (!waiter) {
|
|
39
|
+
waiter = [] as any;
|
|
40
|
+
this.waiters.set(name, waiter!);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return new Promise(resolve => {
|
|
44
|
+
waiter!.push(resolve);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// hidden
|
|
49
|
+
_wasLoaded(): boolean {
|
|
50
|
+
return this.loaded;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// hidden
|
|
54
|
+
// data is the data from the global graphql
|
|
55
|
+
// so it contains some keys and data which should be parsed
|
|
56
|
+
// and created a store for each key
|
|
57
|
+
_setData(siteId: number, data: any) {
|
|
58
|
+
const wasLoaded = this.loaded;
|
|
59
|
+
this.loaded = true;
|
|
60
|
+
|
|
61
|
+
for (const [key, value] of Object.entries(data)) {
|
|
62
|
+
this.entries.set(key, new Global(key, value as any, siteId));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!wasLoaded) {
|
|
66
|
+
this.waiters.forEach((waiters, key) => {
|
|
67
|
+
waiters.forEach(waiter => waiter(this.get(key)));
|
|
68
|
+
});
|
|
69
|
+
this.waiters.clear();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_globalsBySite(siteId: number): Map<string, any> {
|
|
74
|
+
const map = new Map();
|
|
75
|
+
|
|
76
|
+
for (const [key, global] of this.entries) {
|
|
77
|
+
map.set(key, global.bySiteId(siteId));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return map;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_updateSiteId(siteId: number) {
|
|
84
|
+
// todo we should only trigger
|
|
85
|
+
if (this.prevSiteId === siteId) return;
|
|
86
|
+
|
|
87
|
+
this.entries.forEach(global => global._updateSiteId(siteId));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface GlobalData {
|
|
92
|
+
siteId?: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export class Global<T extends GlobalData> {
|
|
96
|
+
private inner: Writable<T>;
|
|
97
|
+
/// if languages is null this means we always have the same data
|
|
98
|
+
private languages: T[] | null;
|
|
99
|
+
|
|
100
|
+
constructor(name: string, data: T[] | T, siteId: number) {
|
|
101
|
+
this.languages = null;
|
|
102
|
+
|
|
103
|
+
let inner: T;
|
|
104
|
+
if (Array.isArray(data)) {
|
|
105
|
+
// make sure the data contains an object with the property
|
|
106
|
+
// siteId
|
|
107
|
+
this.languages = data;
|
|
108
|
+
inner = data.find(d => d.siteId === siteId)!;
|
|
109
|
+
|
|
110
|
+
if (!inner?.siteId) {
|
|
111
|
+
throw new Error(
|
|
112
|
+
`The global query ${name} does not contain the required siteId property`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
inner = data;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.inner = new Writable(inner);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The function get's called once with the current value and then when the
|
|
124
|
+
* values changes
|
|
125
|
+
*
|
|
126
|
+
* @return a function which should be called to unsubscribe
|
|
127
|
+
*/
|
|
128
|
+
subscribe(fn: (val: T) => void): () => void {
|
|
129
|
+
return this.inner.subscribe(fn);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get(): T {
|
|
133
|
+
return this.inner.get();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// if you pass a siteId which comes from craft then you will never receive null
|
|
137
|
+
bySiteId(siteId: number): T | null {
|
|
138
|
+
if (this.languages)
|
|
139
|
+
return this.languages.find(d => d.siteId === siteId) ?? null;
|
|
140
|
+
|
|
141
|
+
return this.inner.get();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_updateSiteId(siteId: number) {
|
|
145
|
+
if (!this.languages) return;
|
|
146
|
+
|
|
147
|
+
const inner = this.languages.find(d => d.siteId === siteId);
|
|
148
|
+
this.inner.set(inner!);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type CrelteRouted from '../CrelteRouted.js';
|
|
6
|
+
import { isGraphQlQuery, type GraphQlQuery } from '../graphql/GraphQl.js';
|
|
7
|
+
import type Globals from './Globals.js';
|
|
8
|
+
import type { Global } from './Globals.js';
|
|
9
|
+
|
|
10
|
+
export type { Globals, Global };
|
|
11
|
+
|
|
12
|
+
export type LoadData<T> =
|
|
13
|
+
| ((cr: CrelteRouted, ...args: any[]) => Promise<T>)
|
|
14
|
+
| GraphQlQuery
|
|
15
|
+
| T;
|
|
16
|
+
|
|
17
|
+
export async function callLoadData(
|
|
18
|
+
ld: LoadData<unknown>,
|
|
19
|
+
cr: CrelteRouted,
|
|
20
|
+
...args: any[]
|
|
21
|
+
): Promise<unknown> {
|
|
22
|
+
// either we have a function
|
|
23
|
+
if (typeof ld === 'function') {
|
|
24
|
+
return await ld(cr, ...args);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// or a graphql query
|
|
28
|
+
if (isGraphQlQuery(ld)) {
|
|
29
|
+
return await cr.query(ld);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// or an object
|
|
33
|
+
if (typeof ld === 'object' && ld !== null) {
|
|
34
|
+
const data = await Promise.all(
|
|
35
|
+
Object.values(ld).map(nld => callLoadData(nld, cr, ...args)),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
Object.keys(ld).map((key, i) => [key, data[i]]),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return ld;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Spread the data of two loadData functions.
|
|
48
|
+
*
|
|
49
|
+
* ## Example
|
|
50
|
+
* ```
|
|
51
|
+
* export const loadData = mergeLoadData(
|
|
52
|
+
* {
|
|
53
|
+
* filter: (cr) => cr.route.search.get('filter'),
|
|
54
|
+
* },
|
|
55
|
+
* (cr) => cr.query(myQuery, { siteId: cr.site.id })
|
|
56
|
+
* )
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function mergeLoadData(...lds: LoadData<object>[]): LoadData<object> {
|
|
60
|
+
return async (cr: CrelteRouted, ...args: any[]) => {
|
|
61
|
+
const datas = await Promise.all(
|
|
62
|
+
lds.map(ld => callLoadData(ld, cr, ...args) as Promise<object>),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return datas.reduce((acc, data) => ({ ...acc, ...data }), {});
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { CrelteRouted } from '../index.js';
|
|
2
|
+
|
|
3
|
+
export default class Events {
|
|
4
|
+
inner: Map<string, Set<any>>;
|
|
5
|
+
|
|
6
|
+
constructor() {
|
|
7
|
+
this.inner = new Map();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* Listens for an event.
|
|
12
|
+
*/
|
|
13
|
+
// override this function to add your own function signatures
|
|
14
|
+
on(
|
|
15
|
+
ev: 'loadGlobalData',
|
|
16
|
+
fn: (cr: CrelteRouted) => Promise<any>,
|
|
17
|
+
): () => void;
|
|
18
|
+
on(
|
|
19
|
+
ev: 'loadData',
|
|
20
|
+
fn: (cr: CrelteRouted, entry: any, data: any) => Promise<any>,
|
|
21
|
+
): () => void;
|
|
22
|
+
on(ev: 'beforeRender', fn: (cr: CrelteRouted) => void): () => void;
|
|
23
|
+
on(ev: string, fn: (...args: any[]) => any): () => void {
|
|
24
|
+
let set = this.inner.get(ev);
|
|
25
|
+
if (!set) {
|
|
26
|
+
set = new Set();
|
|
27
|
+
this.inner.set(ev, set);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set.add(fn);
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
set!.delete(fn);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
remove(ev: string, fn: any) {
|
|
38
|
+
const set = this.inner.get(ev);
|
|
39
|
+
if (!set) return;
|
|
40
|
+
|
|
41
|
+
set.delete(fn);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
trigger(ev: string, ...args: any[]): any[] {
|
|
45
|
+
const set = this.inner.get(ev);
|
|
46
|
+
if (!set) return [];
|
|
47
|
+
|
|
48
|
+
return Array.from(set).map(fn => fn(...args));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import Crelte from '../Crelte.js';
|
|
2
|
+
|
|
3
|
+
export interface Plugin {
|
|
4
|
+
name: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type PluginCreator = (crelte: Crelte) => Plugin;
|
|
8
|
+
|
|
9
|
+
export default class Plugins {
|
|
10
|
+
plugins: Map<string, Plugin>;
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
this.plugins = new Map();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
add(plugin: Plugin) {
|
|
17
|
+
this.plugins.set(plugin.name, plugin);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get(name: string): Plugin | null {
|
|
21
|
+
return this.plugins.get(name) ?? null;
|
|
22
|
+
}
|
|
23
|
+
}
|