@monkeyplus/flow 6.0.6 → 6.0.8
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/modules/content/module.mjs +17 -2
- package/modules/content/query.d.ts +25 -0
- package/modules/content/query.mjs +105 -19
- package/modules/content/runtime/client.d.ts +2 -0
- package/modules/content/runtime/client.mjs +1 -0
- package/modules/images/ipx.d.ts +2 -0
- package/modules/images/ipx.mjs +55 -0
- package/modules/images/module.d.ts +4 -0
- package/modules/images/module.mjs +146 -0
- package/modules/images/runtime/build.d.ts +6 -0
- package/modules/images/runtime/build.mjs +174 -0
- package/modules/images/runtime/helpers.d.ts +16 -0
- package/modules/images/runtime/helpers.mjs +45 -0
- package/modules/images/runtime/image.d.ts +7 -0
- package/modules/images/runtime/image.mjs +252 -0
- package/modules/images/runtime/renames.d.ts +2 -0
- package/modules/images/runtime/renames.mjs +79 -0
- package/modules/images/runtime/server.d.ts +3 -0
- package/modules/images/runtime/server.mjs +80 -0
- package/modules/images/runtime/types.d.ts +79 -0
- package/modules/images/runtime/types.mjs +0 -0
- package/modules/sitemap/handler.mjs +6 -7
- package/modules/sitemap/module.mjs +236 -22
- package/modules/sitemap/xml.d.ts +7 -0
- package/modules/sitemap/xml.mjs +87 -0
- package/package.json +7 -1
- package/server/lib/pages.mjs +20 -21
- package/server/lib/render.mjs +16 -0
- package/server/renderer.d.ts +1 -1
- package/server/renderer.mjs +2 -1
- package/src/public/components.d.ts +3 -0
- package/src/public/components.mjs +3 -0
- package/src/public/index.d.ts +5 -3
- package/src/public/index.mjs +1 -0
- package/src/public/modules/images.d.ts +2 -0
- package/src/public/modules/images.mjs +1 -0
- package/src/public/nitro.mjs +4 -1
- package/src/public/query-content.d.ts +7 -0
- package/src/public/query-content.mjs +130 -0
- package/src/public/vite.mjs +18 -2
- package/src/runtime/components/MkImage.d.ts +188 -0
- package/src/runtime/components/MkImage.mjs +131 -0
- package/src/runtime/components/MkLink.d.ts +22 -0
- package/src/runtime/components/MkLink.mjs +72 -0
- package/src/runtime/components/MkPicture.d.ts +199 -0
- package/src/runtime/components/MkPicture.mjs +172 -0
- package/src/runtime/components/image-shared.d.ts +28 -0
- package/src/runtime/components/image-shared.mjs +68 -0
- package/src/runtime/config.d.ts +22 -0
- package/src/runtime/config.mjs +5 -2
- package/src/runtime/locale-routing.d.ts +12 -0
- package/src/runtime/locale-routing.mjs +93 -0
- package/src/runtime/modules.mjs +1 -0
- package/src/runtime/page-discovery.mjs +8 -15
- package/src/runtime/pages.d.ts +16 -0
- package/src/runtime/virtual.d.ts +17 -0
- package/src/runtime/vue.mjs +6 -0
|
@@ -2,13 +2,14 @@ import { mkdirSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { resolvePackagePath } from "../../src/public/shared.mjs";
|
|
4
4
|
import { defineFlowModule } from "../../src/runtime/config.mjs";
|
|
5
|
-
function createGeneratedSitemapHandler(siteUrl,
|
|
5
|
+
function createGeneratedSitemapHandler(siteUrl, localeConfig) {
|
|
6
6
|
return `
|
|
7
7
|
import pageDefinitions from 'virtual:flow/pages';
|
|
8
8
|
|
|
9
9
|
const dynamicRouteCache = new Map();
|
|
10
10
|
const configuredSiteUrl = ${JSON.stringify(siteUrl || "")};
|
|
11
|
-
const
|
|
11
|
+
const localeConfig = ${JSON.stringify(localeConfig)};
|
|
12
|
+
const enabledLocales = localeConfig.locales;
|
|
12
13
|
|
|
13
14
|
function escapeXml(value) {
|
|
14
15
|
return value
|
|
@@ -27,6 +28,26 @@ function normalizePath(value) {
|
|
|
27
28
|
return value.length > 1 && value.endsWith('/') ? value.slice(0, -1) : value;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
function ensureLeadingSlash(value) {
|
|
32
|
+
if (!value) {
|
|
33
|
+
return '/';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return value.startsWith('/') ? value : '/' + value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ensureTrailingSlash(value) {
|
|
40
|
+
if (value === '/' || value.endsWith('/')) {
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return value + '/';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function preserveTrailingSlash(value, source) {
|
|
48
|
+
return source.endsWith('/') ? ensureTrailingSlash(value) : value;
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
function replacePath(pattern, url) {
|
|
31
52
|
let resolved = pattern;
|
|
32
53
|
|
|
@@ -53,8 +74,198 @@ function toPublicRoutePattern(url) {
|
|
|
53
74
|
return normalizePath(url.replaceAll('*', ''));
|
|
54
75
|
}
|
|
55
76
|
|
|
77
|
+
function trimLeadingSlash(value) {
|
|
78
|
+
let result = value;
|
|
79
|
+
|
|
80
|
+
while (result.startsWith('/')) {
|
|
81
|
+
result = result.slice(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function trimTrailingSlash(value) {
|
|
88
|
+
let result = value;
|
|
89
|
+
|
|
90
|
+
while (result.endsWith('/')) {
|
|
91
|
+
result = result.slice(0, -1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function combineName(name, dynamicName) {
|
|
98
|
+
return trimTrailingSlash(name) + '/' + trimLeadingSlash(dynamicName);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getLanguage(locale) {
|
|
102
|
+
const [language = localeConfig.language || 'es'] = locale.split('-');
|
|
103
|
+
return language;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getLocation(locale) {
|
|
107
|
+
const [, location = localeConfig.location || 'ec'] = locale.split('-');
|
|
108
|
+
return location;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getDefaultLocaleCode() {
|
|
112
|
+
return localeConfig.language + '-' + localeConfig.location;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function routeHasPrefix(route, prefix) {
|
|
116
|
+
return route === prefix || route === prefix + '/' || route.startsWith(prefix + '/');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getLocalePrefixVariants(localeCode) {
|
|
120
|
+
const lang = getLanguage(localeCode);
|
|
121
|
+
const loc = getLocation(localeCode);
|
|
122
|
+
const prefixes = new Set();
|
|
123
|
+
|
|
124
|
+
if (loc === localeConfig.location) {
|
|
125
|
+
prefixes.add('/' + lang);
|
|
126
|
+
return [...prefixes];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
prefixes.add('/' + lang + '-' + loc);
|
|
130
|
+
prefixes.add('/' + lang + '/' + loc);
|
|
131
|
+
|
|
132
|
+
return [...prefixes];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getLocalePrefix(localeCode) {
|
|
136
|
+
if (localeConfig.prefixStrategy === 'manual') {
|
|
137
|
+
return '';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (localeCode === getDefaultLocaleCode()) {
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const lang = getLanguage(localeCode);
|
|
145
|
+
const loc = getLocation(localeCode);
|
|
146
|
+
|
|
147
|
+
if (loc === localeConfig.location) {
|
|
148
|
+
return '/' + lang;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (localeConfig.prefixFormat === 'nested') {
|
|
152
|
+
return '/' + lang + '/' + loc;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return '/' + lang + '-' + loc;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function localizeRoutePattern(localeCode, route) {
|
|
159
|
+
const normalizedRoute = ensureLeadingSlash(route || '/');
|
|
160
|
+
const prefix = getLocalePrefix(localeCode);
|
|
161
|
+
|
|
162
|
+
if (!prefix) {
|
|
163
|
+
return normalizedRoute;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (getLocalePrefixVariants(localeCode).some(candidate => routeHasPrefix(normalizedRoute, candidate))) {
|
|
167
|
+
return normalizedRoute;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (normalizedRoute === '/') {
|
|
171
|
+
return ensureTrailingSlash(prefix);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return preserveTrailingSlash(normalizePath(prefix) + normalizedRoute, normalizedRoute);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function toPublicRoute(localeCode, route) {
|
|
178
|
+
const localizedRoute = localizeRoutePattern(localeCode, route);
|
|
179
|
+
const publicRoute = localizedRoute.replaceAll('*', '');
|
|
180
|
+
|
|
181
|
+
return preserveTrailingSlash(normalizePath(publicRoute), localizedRoute);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function toAbsoluteUrl(origin, url) {
|
|
185
|
+
return new URL(url, origin).toString();
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildAlternateLink(hreflang, origin, url) {
|
|
189
|
+
return '<xhtml:link rel="alternate" hreflang="' + escapeXml(hreflang) + '" href="' + escapeXml(toAbsoluteUrl(origin, url)) + '"/>';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildUrl(origin, url, lastMod, alternates = []) {
|
|
193
|
+
return [
|
|
194
|
+
' <url>',
|
|
195
|
+
' <loc>' + escapeXml(toAbsoluteUrl(origin, url)) + '</loc>',
|
|
196
|
+
' <lastmod>' + escapeXml(lastMod) + '</lastmod>',
|
|
197
|
+
...alternates.map(alternate => ' ' + alternate),
|
|
198
|
+
' </url>',
|
|
199
|
+
].join('\\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function groupByName(entries) {
|
|
203
|
+
const grouped = new Map();
|
|
204
|
+
|
|
205
|
+
for (const entry of entries) {
|
|
206
|
+
const group = grouped.get(entry.name) || [];
|
|
207
|
+
group.push(entry);
|
|
208
|
+
grouped.set(entry.name, group);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return [...grouped.values()];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildLocalizedUrls(entries, origin, lastMod) {
|
|
215
|
+
const defaultLocale = getDefaultLocaleCode();
|
|
216
|
+
|
|
217
|
+
return groupByName(entries).map((pages) => {
|
|
218
|
+
const pagesByLanguage = pages.reduce((result, page) => {
|
|
219
|
+
const language = getLanguage(page.locale);
|
|
220
|
+
const group = result.get(language) || [];
|
|
221
|
+
group.push(page);
|
|
222
|
+
result.set(language, group);
|
|
223
|
+
return result;
|
|
224
|
+
}, new Map());
|
|
225
|
+
const defaultEntry = pages.find(page => page.locale === defaultLocale) || pages[0];
|
|
226
|
+
const alternates = [buildAlternateLink('x-default', origin, defaultEntry.url)];
|
|
227
|
+
|
|
228
|
+
for (const [language, localizedPages] of [...pagesByLanguage.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
229
|
+
const sortedPages = [...localizedPages].sort((left, right) => left.locale.localeCompare(right.locale));
|
|
230
|
+
|
|
231
|
+
if (sortedPages.length > 1) {
|
|
232
|
+
const languageDefault = sortedPages.find(page => getLocation(page.locale) === localeConfig.location) || sortedPages[0];
|
|
233
|
+
alternates.push(buildAlternateLink(language, origin, languageDefault.url));
|
|
234
|
+
|
|
235
|
+
for (const page of sortedPages) {
|
|
236
|
+
alternates.push(buildAlternateLink(page.locale, origin, page.url));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
alternates.push(buildAlternateLink(language, origin, sortedPages[0].url));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return buildUrl(origin, defaultEntry.url, lastMod, alternates);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function buildSitemapXml(entries, origin) {
|
|
250
|
+
const normalizedEntries = entries
|
|
251
|
+
.map((entry) => typeof entry === 'string'
|
|
252
|
+
? { url: entry, locale: localeConfig.language + '-' + localeConfig.location, name: entry }
|
|
253
|
+
: entry)
|
|
254
|
+
.sort((left, right) => left.url.localeCompare(right.url));
|
|
255
|
+
const lastMod = new Date().toISOString().split('T')[0];
|
|
256
|
+
const isMultiple = localeConfig.locales.length > 1;
|
|
257
|
+
const urls = isMultiple
|
|
258
|
+
? buildLocalizedUrls(normalizedEntries, origin, lastMod)
|
|
259
|
+
: normalizedEntries.map(entry => buildUrl(origin, entry.url, lastMod));
|
|
260
|
+
const alternateNamespace = isMultiple
|
|
261
|
+
? ' xmlns:xhtml="http://www.w3.org/1999/xhtml"'
|
|
262
|
+
: '';
|
|
263
|
+
|
|
264
|
+
return '<?xml version="1.0" encoding="UTF-8"?>\\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"' + alternateNamespace + '>\\n' + urls.join('\\n') + '\\n</urlset>';
|
|
265
|
+
}
|
|
266
|
+
|
|
56
267
|
function createLocale(code) {
|
|
57
|
-
const [lang = 'es', loc = 'ec'] = code.split('-');
|
|
268
|
+
const [lang = localeConfig.language || 'es', loc = localeConfig.location || 'ec'] = code.split('-');
|
|
58
269
|
|
|
59
270
|
return {
|
|
60
271
|
code,
|
|
@@ -91,7 +302,7 @@ async function getUrl(namePage, localeCode, options = {}) {
|
|
|
91
302
|
|
|
92
303
|
if (localePage.dynamic && options.dynamicName) {
|
|
93
304
|
const locale = createLocale(code);
|
|
94
|
-
const ctx = createContext(definition, locale, localePage, localePage.url, {});
|
|
305
|
+
const ctx = createContext(definition, locale, localePage, toPublicRoute(code, localePage.url), {});
|
|
95
306
|
const entries = await getDynamicEntries(definition, code, ctx);
|
|
96
307
|
const entry = entries.find(candidate => candidate.name === options.dynamicName);
|
|
97
308
|
|
|
@@ -99,10 +310,10 @@ async function getUrl(namePage, localeCode, options = {}) {
|
|
|
99
310
|
return undefined;
|
|
100
311
|
}
|
|
101
312
|
|
|
102
|
-
return replacePath(localePage.url, entry.url);
|
|
313
|
+
return replacePath(localizeRoutePattern(code, localePage.url), entry.url);
|
|
103
314
|
}
|
|
104
315
|
|
|
105
|
-
return
|
|
316
|
+
return toPublicRoute(code, localePage.url);
|
|
106
317
|
}
|
|
107
318
|
}
|
|
108
319
|
|
|
@@ -125,19 +336,19 @@ async function getUrls(withLocale = false, omitNoPublish = false) {
|
|
|
125
336
|
|
|
126
337
|
if (localePage.dynamic) {
|
|
127
338
|
const locale = createLocale(localeCode);
|
|
128
|
-
const ctx = createContext(definition, locale, localePage, localePage.url, {});
|
|
339
|
+
const ctx = createContext(definition, locale, localePage, toPublicRoute(localeCode, localePage.url), {});
|
|
129
340
|
const entries = await getDynamicEntries(definition, localeCode, ctx);
|
|
130
341
|
|
|
131
342
|
for (const entry of entries) {
|
|
132
|
-
const url = replacePath(localePage.url, entry.url);
|
|
133
|
-
urls.push(withLocale ? { url, locale: localeCode } : url);
|
|
343
|
+
const url = replacePath(localizeRoutePattern(localeCode, localePage.url), entry.url);
|
|
344
|
+
urls.push(withLocale ? { url, locale: localeCode, name: combineName(definition.name, entry.name) } : url);
|
|
134
345
|
}
|
|
135
346
|
|
|
136
347
|
continue;
|
|
137
348
|
}
|
|
138
349
|
|
|
139
|
-
const url =
|
|
140
|
-
urls.push(withLocale ? { url, locale: localeCode } : url);
|
|
350
|
+
const url = toPublicRoute(localeCode, localePage.url);
|
|
351
|
+
urls.push(withLocale ? { url, locale: localeCode, name: definition.name } : url);
|
|
141
352
|
}
|
|
142
353
|
}
|
|
143
354
|
|
|
@@ -201,12 +412,7 @@ export default async function sitemapHandler(event) {
|
|
|
201
412
|
? new URL(event.req.url, configuredSiteUrl || 'http://localhost')
|
|
202
413
|
: new URL(configuredSiteUrl || 'http://localhost');
|
|
203
414
|
const origin = configuredSiteUrl || requestUrl.origin;
|
|
204
|
-
|
|
205
|
-
const xml = '<?xml version="1.0" encoding="UTF-8"?>\\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\\n' + urls
|
|
206
|
-
.map((entry) => typeof entry === 'string'
|
|
207
|
-
? ' <url><loc>' + escapeXml(new URL(entry, origin).toString()) + '</loc></url>'
|
|
208
|
-
: ' <url><loc>' + escapeXml(new URL(entry.url, origin).toString()) + '</loc><xhtml:link rel="alternate" hreflang="' + escapeXml(entry.locale) + '" href="' + escapeXml(new URL(entry.url, origin).toString()) + '" xmlns:xhtml="http://www.w3.org/1999/xhtml" /></url>')
|
|
209
|
-
.join('\\n') + '\\n</urlset>';
|
|
415
|
+
const xml = buildSitemapXml(urls, origin);
|
|
210
416
|
|
|
211
417
|
return new Response(xml, {
|
|
212
418
|
headers: {
|
|
@@ -216,12 +422,18 @@ export default async function sitemapHandler(event) {
|
|
|
216
422
|
}
|
|
217
423
|
`;
|
|
218
424
|
}
|
|
219
|
-
function ensureGeneratedSitemapHandler(projectRoot,
|
|
425
|
+
function ensureGeneratedSitemapHandler(projectRoot, flowConfig) {
|
|
220
426
|
const handlerPath = resolve(projectRoot, ".flow/generated/modules/sitemap/handler.mjs");
|
|
221
427
|
mkdirSync(dirname(handlerPath), { recursive: true });
|
|
222
428
|
writeFileSync(handlerPath, createGeneratedSitemapHandler(
|
|
223
|
-
typeof
|
|
224
|
-
|
|
429
|
+
typeof flowConfig.siteUrl === "string" ? flowConfig.siteUrl : void 0,
|
|
430
|
+
{
|
|
431
|
+
locales: Array.isArray(flowConfig.locale?.locales) ? flowConfig.locale.locales : ["es-ec"],
|
|
432
|
+
language: typeof flowConfig.locale?.language === "string" ? flowConfig.locale.language : "es",
|
|
433
|
+
location: typeof flowConfig.locale?.location === "string" ? flowConfig.locale.location : "ec",
|
|
434
|
+
prefixStrategy: flowConfig.locale?.prefixStrategy === "manual" ? "manual" : "auto",
|
|
435
|
+
prefixFormat: flowConfig.locale?.prefixFormat === "nested" ? "nested" : "compact"
|
|
436
|
+
}
|
|
225
437
|
), "utf8");
|
|
226
438
|
return handlerPath;
|
|
227
439
|
}
|
|
@@ -235,9 +447,11 @@ export default defineFlowModule({
|
|
|
235
447
|
prerender: true
|
|
236
448
|
},
|
|
237
449
|
setup(options, context) {
|
|
238
|
-
const contextRuntimeConfig = typeof context.nitro.runtimeConfig.flow === "object" && context.nitro.runtimeConfig.flow ? context.nitro.runtimeConfig.flow : {};
|
|
239
450
|
const localHandlerPath = resolve(context.projectRoot, "modules/sitemap/handler.ts");
|
|
240
|
-
const handlerPath = context.projectRoot === resolvePackagePath() ? localHandlerPath : ensureGeneratedSitemapHandler(context.projectRoot,
|
|
451
|
+
const handlerPath = context.projectRoot === resolvePackagePath() ? localHandlerPath : ensureGeneratedSitemapHandler(context.projectRoot, {
|
|
452
|
+
siteUrl: context.flowConfig.siteUrl,
|
|
453
|
+
locale: context.flowConfig.locale
|
|
454
|
+
});
|
|
241
455
|
context.nitro.handlers.push({
|
|
242
456
|
method: "get",
|
|
243
457
|
route: options.route,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PageUrlInfo } from '../../src/runtime/pages.ts';
|
|
2
|
+
export interface SitemapLocaleConfig {
|
|
3
|
+
locales: string[];
|
|
4
|
+
language: string;
|
|
5
|
+
location: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function buildSitemapXml(entries: Array<string | PageUrlInfo>, origin: string, locale: SitemapLocaleConfig): string;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
function escapeXml(value) {
|
|
2
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3
|
+
}
|
|
4
|
+
function getLanguage(locale) {
|
|
5
|
+
const [language = "es"] = locale.split("-");
|
|
6
|
+
return language;
|
|
7
|
+
}
|
|
8
|
+
function getLocation(locale) {
|
|
9
|
+
const [, location = "ec"] = locale.split("-");
|
|
10
|
+
return location;
|
|
11
|
+
}
|
|
12
|
+
function toAbsoluteUrl(origin, url) {
|
|
13
|
+
return new URL(url, origin).toString();
|
|
14
|
+
}
|
|
15
|
+
function buildAlternateLink(hreflang, origin, url) {
|
|
16
|
+
return `<xhtml:link rel="alternate" hreflang="${escapeXml(hreflang)}" href="${escapeXml(toAbsoluteUrl(origin, url))}"/>`;
|
|
17
|
+
}
|
|
18
|
+
function buildUrl(origin, url, lastMod, alternates = []) {
|
|
19
|
+
const lines = [
|
|
20
|
+
" <url>",
|
|
21
|
+
` <loc>${escapeXml(toAbsoluteUrl(origin, url))}</loc>`,
|
|
22
|
+
` <lastmod>${escapeXml(lastMod)}</lastmod>`,
|
|
23
|
+
...alternates.map((alternate) => ` ${alternate}`),
|
|
24
|
+
" </url>"
|
|
25
|
+
];
|
|
26
|
+
return lines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
function normalizeEntries(entries, locale) {
|
|
29
|
+
const defaultLocale = `${locale.language}-${locale.location}`;
|
|
30
|
+
return entries.map((entry) => {
|
|
31
|
+
if (typeof entry === "string") {
|
|
32
|
+
return {
|
|
33
|
+
url: entry,
|
|
34
|
+
locale: defaultLocale,
|
|
35
|
+
name: entry
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return entry;
|
|
39
|
+
}).sort((left, right) => left.url.localeCompare(right.url));
|
|
40
|
+
}
|
|
41
|
+
function groupByName(entries) {
|
|
42
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const group = grouped.get(entry.name) || [];
|
|
45
|
+
group.push(entry);
|
|
46
|
+
grouped.set(entry.name, group);
|
|
47
|
+
}
|
|
48
|
+
return [...grouped.values()];
|
|
49
|
+
}
|
|
50
|
+
function buildLocalizedUrls(entries, origin, locale, lastMod) {
|
|
51
|
+
const defaultLocale = `${locale.language}-${locale.location}`;
|
|
52
|
+
return groupByName(entries).map((pages) => {
|
|
53
|
+
const pagesByLanguage = pages.reduce((result, page) => {
|
|
54
|
+
const language = getLanguage(page.locale);
|
|
55
|
+
const group = result.get(language) || [];
|
|
56
|
+
group.push(page);
|
|
57
|
+
result.set(language, group);
|
|
58
|
+
return result;
|
|
59
|
+
}, /* @__PURE__ */ new Map());
|
|
60
|
+
const defaultEntry = pages.find((page) => page.locale === defaultLocale) || pages[0];
|
|
61
|
+
const alternates = [buildAlternateLink("x-default", origin, defaultEntry.url)];
|
|
62
|
+
for (const [language, localizedPages] of [...pagesByLanguage.entries()].sort(([left], [right]) => left.localeCompare(right))) {
|
|
63
|
+
const sortedPages = [...localizedPages].sort((left, right) => left.locale.localeCompare(right.locale));
|
|
64
|
+
if (sortedPages.length > 1) {
|
|
65
|
+
const languageDefault = sortedPages.find((page) => getLocation(page.locale) === locale.location) || sortedPages[0];
|
|
66
|
+
alternates.push(buildAlternateLink(language, origin, languageDefault.url));
|
|
67
|
+
for (const page of sortedPages) {
|
|
68
|
+
alternates.push(buildAlternateLink(page.locale, origin, page.url));
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
alternates.push(buildAlternateLink(language, origin, sortedPages[0].url));
|
|
73
|
+
}
|
|
74
|
+
return buildUrl(origin, defaultEntry.url, lastMod, alternates);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export function buildSitemapXml(entries, origin, locale) {
|
|
78
|
+
const normalizedEntries = normalizeEntries(entries, locale);
|
|
79
|
+
const lastMod = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
80
|
+
const isMultiple = locale.locales.length > 1;
|
|
81
|
+
const urls = isMultiple ? buildLocalizedUrls(normalizedEntries, origin, locale, lastMod) : normalizedEntries.map((entry) => buildUrl(origin, entry.url, lastMod));
|
|
82
|
+
const alternateNamespace = isMultiple ? ' xmlns:xhtml="http://www.w3.org/1999/xhtml"' : "";
|
|
83
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
84
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"${alternateNamespace}>
|
|
85
|
+
${urls.join("\n")}
|
|
86
|
+
</urlset>`;
|
|
87
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monkeyplus/flow",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.8",
|
|
4
4
|
"description": "@monkeyplus/flow package-first runtime with Vite, Nitro, Vue and a workspace playground.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -40,6 +40,10 @@
|
|
|
40
40
|
"types": "./modules/strapi/runtime/client.d.ts",
|
|
41
41
|
"import": "./modules/strapi/runtime/client.mjs"
|
|
42
42
|
},
|
|
43
|
+
"./modules/images": {
|
|
44
|
+
"types": "./src/public/modules/images.d.ts",
|
|
45
|
+
"import": "./src/public/modules/images.mjs"
|
|
46
|
+
},
|
|
43
47
|
"./modules/sitemap": {
|
|
44
48
|
"types": "./src/public/modules/sitemap.d.ts",
|
|
45
49
|
"import": "./src/public/modules/sitemap.mjs"
|
|
@@ -72,8 +76,10 @@
|
|
|
72
76
|
"@unhead/vue": "^3.1.0",
|
|
73
77
|
"@vitejs/plugin-vue": "^6.0.5",
|
|
74
78
|
"@vue/server-renderer": "^3.5.13",
|
|
79
|
+
"ipx": "^3.1.1",
|
|
75
80
|
"nitro": "3.0.260429-beta",
|
|
76
81
|
"std-env": "^4.0.0",
|
|
82
|
+
"ufo": "^1.6.1",
|
|
77
83
|
"unhead": "^3.1.0",
|
|
78
84
|
"unplugin-icons": "^23.0.1",
|
|
79
85
|
"vite": "^8.0.5",
|
package/server/lib/pages.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import pageDefinitions from "virtual:flow/pages";
|
|
3
|
+
import { getFlowImageRuntimeUtils } from "../../modules/images/runtime/server.mjs";
|
|
4
|
+
import { localizeRoutePattern, normalizePath, toPublicRoute } from "../../src/runtime/locale-routing.mjs";
|
|
3
5
|
const dynamicRouteCache = /* @__PURE__ */ new Map();
|
|
4
6
|
let runtimeConfigRequire;
|
|
5
7
|
function getFlowRuntimeConfig() {
|
|
@@ -11,12 +13,6 @@ function getFlowRuntimeConfig() {
|
|
|
11
13
|
return {};
|
|
12
14
|
}
|
|
13
15
|
}
|
|
14
|
-
function normalizePath(value) {
|
|
15
|
-
if (!value || value === "/") {
|
|
16
|
-
return "/";
|
|
17
|
-
}
|
|
18
|
-
return value.length > 1 && value.endsWith("/") ? value.slice(0, -1) : value;
|
|
19
|
-
}
|
|
20
16
|
function scorePattern(pattern) {
|
|
21
17
|
return normalizePath(pattern).split("/").filter(Boolean).reduce((score, segment) => {
|
|
22
18
|
if (segment === "**") {
|
|
@@ -69,6 +65,7 @@ function createLocale(code) {
|
|
|
69
65
|
};
|
|
70
66
|
}
|
|
71
67
|
function createContext(definition, locale, localePage, pathname, params, dynamic) {
|
|
68
|
+
const imageUtils = getFlowImageRuntimeUtils();
|
|
72
69
|
return {
|
|
73
70
|
dynamic,
|
|
74
71
|
locale,
|
|
@@ -82,11 +79,12 @@ function createContext(definition, locale, localePage, pathname, params, dynamic
|
|
|
82
79
|
return locale;
|
|
83
80
|
},
|
|
84
81
|
async getUrl(namePage, localeCode, options) {
|
|
85
|
-
return await getUrl(namePage, localeCode, options);
|
|
82
|
+
return await getUrl(namePage, localeCode || locale.code, options);
|
|
86
83
|
},
|
|
87
84
|
async getUrls(withLocale, omitNoPublish) {
|
|
88
85
|
return await getUrls(withLocale, omitNoPublish);
|
|
89
|
-
}
|
|
86
|
+
},
|
|
87
|
+
...imageUtils || {}
|
|
90
88
|
}
|
|
91
89
|
};
|
|
92
90
|
}
|
|
@@ -109,9 +107,6 @@ function replacePath(pattern, url) {
|
|
|
109
107
|
}
|
|
110
108
|
return normalizePath(resolved.replaceAll("**", ""));
|
|
111
109
|
}
|
|
112
|
-
function toPublicRoutePattern(url) {
|
|
113
|
-
return normalizePath(url.replaceAll("*", ""));
|
|
114
|
-
}
|
|
115
110
|
function combineName(name, dynamicName) {
|
|
116
111
|
return `${name.replace(/\/*$/, "")}/${dynamicName.replace(/^\/*/, "")}`;
|
|
117
112
|
}
|
|
@@ -147,6 +142,7 @@ async function resolveDynamicEntry(definition, localeCode, localePage, ctx, path
|
|
|
147
142
|
export async function getUrl(namePage, localeCode, options = {}) {
|
|
148
143
|
const enabledLocales = getEnabledLocaleCodes();
|
|
149
144
|
const targetLocale = localeCode || enabledLocales[0];
|
|
145
|
+
const runtimeConfig = getFlowRuntimeConfig();
|
|
150
146
|
for (const definition of pageDefinitions) {
|
|
151
147
|
if (definition.name !== namePage) {
|
|
152
148
|
continue;
|
|
@@ -154,32 +150,34 @@ export async function getUrl(namePage, localeCode, options = {}) {
|
|
|
154
150
|
const localeEntries = targetLocale ? [targetLocale] : Object.keys(definition.locales);
|
|
155
151
|
for (const code of localeEntries) {
|
|
156
152
|
const localePage = definition.locales[code];
|
|
153
|
+
const localizedRoute = localizeRoutePattern(runtimeConfig.flow, code, localePage?.url || "/");
|
|
157
154
|
if (!localePage) {
|
|
158
155
|
continue;
|
|
159
156
|
}
|
|
160
157
|
if (options.url) {
|
|
161
|
-
return replacePath(
|
|
158
|
+
return replacePath(localizedRoute, options.url);
|
|
162
159
|
}
|
|
163
160
|
if (options.params) {
|
|
164
|
-
return replacePath(
|
|
161
|
+
return replacePath(localizedRoute, options.params);
|
|
165
162
|
}
|
|
166
163
|
if (localePage.dynamic && options.dynamicName) {
|
|
167
164
|
const locale = createLocale(code);
|
|
168
|
-
const ctx = createContext(definition, locale, localePage, localePage.url, {});
|
|
165
|
+
const ctx = createContext(definition, locale, localePage, toPublicRoute(runtimeConfig.flow, code, localePage.url), {});
|
|
169
166
|
const entries = await getDynamicEntries(definition, code, ctx);
|
|
170
167
|
const entry = entries.find((candidate) => candidate.name === options.dynamicName);
|
|
171
168
|
if (!entry) {
|
|
172
169
|
return void 0;
|
|
173
170
|
}
|
|
174
|
-
return replacePath(
|
|
171
|
+
return replacePath(localizedRoute, entry.url);
|
|
175
172
|
}
|
|
176
|
-
return
|
|
173
|
+
return toPublicRoute(runtimeConfig.flow, code, localePage.url);
|
|
177
174
|
}
|
|
178
175
|
}
|
|
179
176
|
return void 0;
|
|
180
177
|
}
|
|
181
178
|
export async function getUrls(withLocale = false, omitNoPublish = false) {
|
|
182
179
|
const enabledLocales = getEnabledLocaleCodes();
|
|
180
|
+
const runtimeConfig = getFlowRuntimeConfig();
|
|
183
181
|
const urls = [];
|
|
184
182
|
for (const definition of pageDefinitions) {
|
|
185
183
|
if (omitNoPublish && definition.noPublish) {
|
|
@@ -191,15 +189,15 @@ export async function getUrls(withLocale = false, omitNoPublish = false) {
|
|
|
191
189
|
}
|
|
192
190
|
if (localePage.dynamic) {
|
|
193
191
|
const locale = createLocale(localeCode);
|
|
194
|
-
const ctx = createContext(definition, locale, localePage, localePage.url, {});
|
|
192
|
+
const ctx = createContext(definition, locale, localePage, toPublicRoute(runtimeConfig.flow, localeCode, localePage.url), {});
|
|
195
193
|
const entries = await getDynamicEntries(definition, localeCode, ctx);
|
|
196
194
|
for (const entry of entries) {
|
|
197
|
-
const url2 = replacePath(localePage.url, entry.url);
|
|
195
|
+
const url2 = replacePath(localizeRoutePattern(runtimeConfig.flow, localeCode, localePage.url), entry.url);
|
|
198
196
|
urls.push(withLocale ? { url: url2, locale: localeCode, name: combineName(definition.name, entry.name) } : url2);
|
|
199
197
|
}
|
|
200
198
|
continue;
|
|
201
199
|
}
|
|
202
|
-
const url =
|
|
200
|
+
const url = toPublicRoute(runtimeConfig.flow, localeCode, localePage.url);
|
|
203
201
|
urls.push(withLocale ? { url, locale: localeCode, name: definition.name } : url);
|
|
204
202
|
}
|
|
205
203
|
}
|
|
@@ -214,14 +212,15 @@ export async function resolvePage(pathname) {
|
|
|
214
212
|
if (enabledLocales.length && !enabledLocales.includes(localeCode)) {
|
|
215
213
|
continue;
|
|
216
214
|
}
|
|
217
|
-
const
|
|
215
|
+
const localizedRoute = localizeRoutePattern(runtimeConfig.flow, localeCode, localePage.url);
|
|
216
|
+
const params = matchPattern(localizedRoute, pathname);
|
|
218
217
|
if (params) {
|
|
219
218
|
candidates.push({
|
|
220
219
|
definition,
|
|
221
220
|
localeCode,
|
|
222
221
|
localePage,
|
|
223
222
|
params,
|
|
224
|
-
score: scorePattern(
|
|
223
|
+
score: scorePattern(localizedRoute)
|
|
225
224
|
});
|
|
226
225
|
}
|
|
227
226
|
}
|
package/server/lib/render.mjs
CHANGED
|
@@ -5,7 +5,9 @@ import bases from "virtual:flow/bases";
|
|
|
5
5
|
import layouts from "virtual:flow/layouts";
|
|
6
6
|
import templates from "virtual:flow/templates";
|
|
7
7
|
import { createSSRApp, defineComponent, h } from "vue";
|
|
8
|
+
import { getFlowImageRuntimeUtils } from "../../modules/images/runtime/server.mjs";
|
|
8
9
|
import { installFlowVuePlugins } from "../../src/runtime/vue";
|
|
10
|
+
import { getUrl, getUrls } from "./pages.mjs";
|
|
9
11
|
function escapeHtml(value) {
|
|
10
12
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
11
13
|
}
|
|
@@ -104,6 +106,19 @@ async function renderBody(page) {
|
|
|
104
106
|
params: page.params,
|
|
105
107
|
path: page.pathname
|
|
106
108
|
};
|
|
109
|
+
const imageUtils = getFlowImageRuntimeUtils();
|
|
110
|
+
const utils = {
|
|
111
|
+
getLocale() {
|
|
112
|
+
return page.locale;
|
|
113
|
+
},
|
|
114
|
+
async getUrl(namePage, localeCode, options) {
|
|
115
|
+
return await getUrl(namePage, localeCode || page.locale.code, options);
|
|
116
|
+
},
|
|
117
|
+
async getUrls(withLocale, omitNoPublish) {
|
|
118
|
+
return await getUrls(withLocale, omitNoPublish);
|
|
119
|
+
},
|
|
120
|
+
...imageUtils || {}
|
|
121
|
+
};
|
|
107
122
|
const TemplateRoot = defineComponent({
|
|
108
123
|
render() {
|
|
109
124
|
const viewNode = h(TemplateComponent, context);
|
|
@@ -117,6 +132,7 @@ async function renderBody(page) {
|
|
|
117
132
|
});
|
|
118
133
|
const app = createSSRApp(TemplateRoot);
|
|
119
134
|
app.provide("context", context);
|
|
135
|
+
app.provide("utils", utils);
|
|
120
136
|
const head = createHead();
|
|
121
137
|
head.push(normalizeSeoToHead(page.head, fallbackTitle, fallbackDescription));
|
|
122
138
|
app.use(head);
|
package/server/renderer.d.ts
CHANGED
package/server/renderer.mjs
CHANGED
|
@@ -38,7 +38,8 @@ async function resolvePrerenderFetchHandler() {
|
|
|
38
38
|
export default async function renderPage(input) {
|
|
39
39
|
const viteEnvs = globalThis.__nitro_vite_envs__;
|
|
40
40
|
const request = resolveRequest(input);
|
|
41
|
-
const
|
|
41
|
+
const viteEnv = viteEnvs?.ssr;
|
|
42
|
+
const viteFetchHandler = typeof viteEnv?.fetch === "function" ? viteEnv.fetch.bind(viteEnv) : void 0;
|
|
42
43
|
if (!request) {
|
|
43
44
|
return new Response("Not Found", { status: 404 });
|
|
44
45
|
}
|
|
@@ -1 +1,4 @@
|
|
|
1
1
|
export { default as FlowIsland } from '../runtime/components/FlowIsland.ts';
|
|
2
|
+
export { default as MkImage } from '../runtime/components/MkImage.ts';
|
|
3
|
+
export { default as MkLink } from '../runtime/components/MkLink.ts';
|
|
4
|
+
export { default as MkPicture } from '../runtime/components/MkPicture.ts';
|
|
@@ -1 +1,4 @@
|
|
|
1
1
|
export { default as FlowIsland } from "../runtime/components/FlowIsland.mjs";
|
|
2
|
+
export { default as MkImage } from "../runtime/components/MkImage.mjs";
|
|
3
|
+
export { default as MkLink } from "../runtime/components/MkLink.mjs";
|
|
4
|
+
export { default as MkPicture } from "../runtime/components/MkPicture.mjs";
|