@bleedingdev/modern-js-app-tools 3.5.0-ultramodern.3 → 3.5.0-ultramodern.30
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/dist/cjs/builder/generator/getBuilderEnvironments.js +1 -0
- package/dist/cjs/index.js +50 -44
- package/dist/cjs/plugins/backendFederationBuild.js +347 -0
- package/dist/cjs/plugins/deploy/platforms/cloudflare-output-contract.js +91 -0
- package/dist/cjs/plugins/deploy/platforms/cloudflare-output-plan.js +93 -0
- package/dist/cjs/plugins/deploy/platforms/cloudflare-output-verifier.js +402 -0
- package/dist/cjs/plugins/deploy/platforms/cloudflare.js +272 -29
- package/dist/cjs/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
- package/dist/esm/builder/generator/getBuilderEnvironments.mjs +1 -0
- package/dist/esm/index.mjs +2 -0
- package/dist/esm/plugins/backendFederationBuild.mjs +296 -0
- package/dist/esm/plugins/deploy/platforms/cloudflare-output-contract.mjs +17 -0
- package/dist/esm/plugins/deploy/platforms/cloudflare-output-plan.mjs +45 -0
- package/dist/esm/plugins/deploy/platforms/cloudflare-output-verifier.mjs +347 -0
- package/dist/esm/plugins/deploy/platforms/cloudflare.mjs +272 -29
- package/dist/esm/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
- package/dist/esm-node/builder/generator/getBuilderEnvironments.mjs +1 -0
- package/dist/esm-node/index.mjs +2 -0
- package/dist/esm-node/plugins/backendFederationBuild.mjs +297 -0
- package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-contract.mjs +18 -0
- package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-plan.mjs +46 -0
- package/dist/esm-node/plugins/deploy/platforms/cloudflare-output-verifier.mjs +348 -0
- package/dist/esm-node/plugins/deploy/platforms/cloudflare.mjs +272 -29
- package/dist/esm-node/plugins/deploy/platforms/templates/cloudflare-entry.mjs +346 -43
- package/dist/types/plugins/backendFederationBuild.d.ts +11 -0
- package/dist/types/plugins/deploy/platforms/cloudflare-output-contract.d.ts +13 -0
- package/dist/types/plugins/deploy/platforms/cloudflare-output-plan.d.ts +33 -0
- package/dist/types/plugins/deploy/platforms/cloudflare-output-verifier.d.ts +32 -0
- package/dist/types/types/config/cloudflareDeploy.d.ts +22 -0
- package/dist/types/types/config/deploy.d.ts +15 -2
- package/lib/types.d.ts +2 -3
- package/package.json +17 -11
|
@@ -2,6 +2,8 @@ const ASSETS_BINDING = 'ASSETS';
|
|
|
2
2
|
const MODERN_WORKER_MANIFEST = p_workerManifest;
|
|
3
3
|
const WORKER_MODULE_LOADERS = p_workerModuleLoaders;
|
|
4
4
|
const workerModulePromises = new Map();
|
|
5
|
+
const effectBffDispatcherPromises = new Map();
|
|
6
|
+
let effectEdgeRuntimePromise;
|
|
5
7
|
const remoteJsonPromises = new Map();
|
|
6
8
|
const CORS_POLICY = MODERN_WORKER_MANIFEST.security?.cors || {};
|
|
7
9
|
const ASSET_CORS_ENABLED = false !== CORS_POLICY.assets;
|
|
@@ -150,19 +152,10 @@ async function createCorsPreflightResponse(request, env) {
|
|
|
150
152
|
status: 204
|
|
151
153
|
});
|
|
152
154
|
}
|
|
153
|
-
|
|
154
|
-
const assets = env?.[ASSETS_BINDING];
|
|
155
|
-
if (!assets || 'function' != typeof assets.fetch) return null;
|
|
156
|
-
const assetResponse = await assets.fetch(new Request(request.url, {
|
|
157
|
-
method: 'HEAD'
|
|
158
|
-
}));
|
|
159
|
-
if (!assetResponse || 404 === assetResponse.status) return null;
|
|
160
|
-
return new Response(null, {
|
|
161
|
-
headers: ASSET_CORS_HEADERS,
|
|
162
|
-
status: 204
|
|
163
|
-
});
|
|
155
|
+
return null;
|
|
164
156
|
}
|
|
165
157
|
async function fetchAsset(request, env) {
|
|
158
|
+
if ('GET' !== request.method && 'HEAD' !== request.method) return null;
|
|
166
159
|
const assets = env?.[ASSETS_BINDING];
|
|
167
160
|
if (!assets || 'function' != typeof assets.fetch) return null;
|
|
168
161
|
const response = await assets.fetch(request);
|
|
@@ -211,7 +204,7 @@ function getPathExtension(pathname) {
|
|
|
211
204
|
}
|
|
212
205
|
function isAssetLikePathname(pathname) {
|
|
213
206
|
const extension = getPathExtension(pathname);
|
|
214
|
-
return '' !== extension
|
|
207
|
+
return '' !== extension;
|
|
215
208
|
}
|
|
216
209
|
function routeMatchesExactly(route, pathname) {
|
|
217
210
|
if ('string' != typeof route?.urlPath) return false;
|
|
@@ -223,6 +216,255 @@ function routeMatches(route, pathname) {
|
|
|
223
216
|
const requestPath = normalizeRoutePath(pathname);
|
|
224
217
|
return routePath === requestPath || '/' === routePath && route.isSSR || requestPath.startsWith(`${routePath}/`);
|
|
225
218
|
}
|
|
219
|
+
function getRouteI18nEntry(route) {
|
|
220
|
+
if (!route || 'string' != typeof route.entryName) return;
|
|
221
|
+
return MODERN_WORKER_MANIFEST.i18n?.entries?.[route.entryName];
|
|
222
|
+
}
|
|
223
|
+
function getRouteBasePath(route) {
|
|
224
|
+
const routePath = 'string' == typeof route?.urlPath ? normalizeRoutePath(route.urlPath) : '/';
|
|
225
|
+
return routePath || '/';
|
|
226
|
+
}
|
|
227
|
+
function getRouteRemainingPathname(route, pathname) {
|
|
228
|
+
const basePath = getRouteBasePath(route);
|
|
229
|
+
if ('/' === basePath) return pathname || '/';
|
|
230
|
+
if (!matchesPrefix(pathname, basePath)) return pathname || '/';
|
|
231
|
+
const remaining = pathname.slice(basePath.length);
|
|
232
|
+
return remaining ? remaining.startsWith('/') ? remaining : `/${remaining}` : '/';
|
|
233
|
+
}
|
|
234
|
+
function normalizeLanguage(value, languages) {
|
|
235
|
+
if ('string' != typeof value || '' === value.trim()) return null;
|
|
236
|
+
const normalized = value.trim().toLowerCase();
|
|
237
|
+
const exact = languages.find((language)=>language.toLowerCase() === normalized);
|
|
238
|
+
if (exact) return exact;
|
|
239
|
+
const baseLanguage = normalized.split(/[-_]/u)[0];
|
|
240
|
+
return languages.find((language)=>language.toLowerCase() === baseLanguage) || null;
|
|
241
|
+
}
|
|
242
|
+
function getLanguageFromPath(route, pathname, languages) {
|
|
243
|
+
const remainingPathname = getRouteRemainingPathname(route, pathname);
|
|
244
|
+
const firstSegment = remainingPathname.split('/').filter(Boolean)[0];
|
|
245
|
+
return normalizeLanguage(firstSegment, languages);
|
|
246
|
+
}
|
|
247
|
+
function safeDecodeURIComponent(value) {
|
|
248
|
+
try {
|
|
249
|
+
return decodeURIComponent(value);
|
|
250
|
+
} catch {
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function detectLanguageFromCookie(cookieHeader, lookupCookie, languages) {
|
|
255
|
+
if (!cookieHeader) return null;
|
|
256
|
+
for (const cookiePart of cookieHeader.split(';')){
|
|
257
|
+
const [rawName, ...rawValue] = cookiePart.trim().split('=');
|
|
258
|
+
if (rawName === lookupCookie && 0 !== rawValue.length) return normalizeLanguage(safeDecodeURIComponent(rawValue.join('=')), languages);
|
|
259
|
+
}
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
function detectLanguageFromAcceptLanguage(acceptLanguage, languages) {
|
|
263
|
+
if (!acceptLanguage) return null;
|
|
264
|
+
return acceptLanguage.split(',').map((entry, index)=>{
|
|
265
|
+
const [rawRange, ...rawParameters] = entry.trim().split(';');
|
|
266
|
+
const qualityParameter = rawParameters.find((parameter)=>parameter.trim().startsWith('q='));
|
|
267
|
+
const quality = qualityParameter ? Number.parseFloat(qualityParameter.split('=')[1] || '') : 1;
|
|
268
|
+
return {
|
|
269
|
+
index,
|
|
270
|
+
language: normalizeLanguage(rawRange, languages),
|
|
271
|
+
quality: Number.isFinite(quality) ? quality : 1
|
|
272
|
+
};
|
|
273
|
+
}).filter((candidate)=>candidate.language && candidate.quality > 0).sort((left, right)=>right.quality - left.quality || left.index - right.index)[0]?.language || null;
|
|
274
|
+
}
|
|
275
|
+
function detectRedirectLanguage(request, entry) {
|
|
276
|
+
if (!entry.i18nextDetector) return null;
|
|
277
|
+
const url = new URL(request.url);
|
|
278
|
+
const detection = entry.detection || {};
|
|
279
|
+
const order = Array.isArray(detection.order) ? detection.order : [
|
|
280
|
+
'querystring',
|
|
281
|
+
'cookie',
|
|
282
|
+
'header'
|
|
283
|
+
];
|
|
284
|
+
for (const method of order){
|
|
285
|
+
const language = 'querystring' === method ? normalizeLanguage(url.searchParams.get(detection.lookupQuerystring || 'lng'), entry.languages) : 'cookie' === method ? detectLanguageFromCookie(request.headers.get('cookie'), detection.lookupCookie || 'i18next', entry.languages) : 'header' === method ? detectLanguageFromAcceptLanguage(request.headers.get(detection.lookupHeader || 'accept-language'), entry.languages) : null;
|
|
286
|
+
if (language) return language;
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
function normaliseSlashes(pathname) {
|
|
291
|
+
const withoutDuplicateSlashes = pathname.replace(/\/+/g, '/');
|
|
292
|
+
const withLeadingSlash = withoutDuplicateSlashes.startsWith('/') ? withoutDuplicateSlashes : `/${withoutDuplicateSlashes}`;
|
|
293
|
+
return withLeadingSlash.length > 1 ? withLeadingSlash.replace(/\/+$/u, '') : withLeadingSlash;
|
|
294
|
+
}
|
|
295
|
+
function normalisePathPattern(pathname) {
|
|
296
|
+
return normaliseSlashes(pathname).replace(/\[(.+?)\]/g, ':$1');
|
|
297
|
+
}
|
|
298
|
+
function stripLanguagePrefix(pathname, languages) {
|
|
299
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
300
|
+
const firstSegment = segments[0];
|
|
301
|
+
if (firstSegment && normalizeLanguage(firstSegment, languages)) return `/${segments.slice(1).join('/')}` || '/';
|
|
302
|
+
return pathname || '/';
|
|
303
|
+
}
|
|
304
|
+
function escapeRegExp(value) {
|
|
305
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
306
|
+
}
|
|
307
|
+
function getPatternParamName(segment) {
|
|
308
|
+
return segment.slice(1).replace(/\?$/, '');
|
|
309
|
+
}
|
|
310
|
+
function compilePathPattern(pattern) {
|
|
311
|
+
const names = [];
|
|
312
|
+
const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
|
|
313
|
+
const source = segments.map((segment)=>{
|
|
314
|
+
if (segment.startsWith(':')) {
|
|
315
|
+
names.push(getPatternParamName(segment));
|
|
316
|
+
return segment.endsWith('?') ? '(?:/([^/]+))?' : '/([^/]+)';
|
|
317
|
+
}
|
|
318
|
+
if ('*' === segment) {
|
|
319
|
+
names.push('*');
|
|
320
|
+
return '/(.*)';
|
|
321
|
+
}
|
|
322
|
+
return `/${escapeRegExp(segment)}`;
|
|
323
|
+
}).join('');
|
|
324
|
+
return {
|
|
325
|
+
names,
|
|
326
|
+
regexp: new RegExp(`^${source || '/'}$`)
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function matchPathPattern(pathname, pattern) {
|
|
330
|
+
const { names, regexp } = compilePathPattern(pattern);
|
|
331
|
+
const match = regexp.exec(normaliseSlashes(pathname));
|
|
332
|
+
if (!match) return null;
|
|
333
|
+
const params = {};
|
|
334
|
+
for(let index = 0; index < names.length; index++)try {
|
|
335
|
+
params[names[index]] = decodeURIComponent(match[index + 1] || '');
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
return params;
|
|
340
|
+
}
|
|
341
|
+
function buildPathFromPattern(pattern, params) {
|
|
342
|
+
const path = normalisePathPattern(pattern).split('/').filter(Boolean).map((segment)=>{
|
|
343
|
+
if (segment.startsWith(':')) {
|
|
344
|
+
const param = params[getPatternParamName(segment)];
|
|
345
|
+
return param ? encodeURIComponent(param) : '';
|
|
346
|
+
}
|
|
347
|
+
if ('*' === segment) return params['*'] || '';
|
|
348
|
+
return segment;
|
|
349
|
+
}).filter(Boolean).join('/');
|
|
350
|
+
return `/${path}`;
|
|
351
|
+
}
|
|
352
|
+
function patternSpecificity(pattern) {
|
|
353
|
+
const segments = normalisePathPattern(pattern).split('/').filter(Boolean);
|
|
354
|
+
let staticSegments = 0;
|
|
355
|
+
let dynamicSegments = 0;
|
|
356
|
+
let splatSegments = 0;
|
|
357
|
+
for (const segment of segments)if ('*' === segment) splatSegments++;
|
|
358
|
+
else if (segment.startsWith(':')) dynamicSegments++;
|
|
359
|
+
else staticSegments++;
|
|
360
|
+
return {
|
|
361
|
+
dynamicSegments,
|
|
362
|
+
splatSegments,
|
|
363
|
+
staticSegments,
|
|
364
|
+
totalSegments: segments.length
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
function comparePatternSpecificity(left, right) {
|
|
368
|
+
const leftSpecificity = patternSpecificity(left);
|
|
369
|
+
const rightSpecificity = patternSpecificity(right);
|
|
370
|
+
return rightSpecificity.staticSegments - leftSpecificity.staticSegments || rightSpecificity.totalSegments - leftSpecificity.totalSegments || leftSpecificity.splatSegments - rightSpecificity.splatSegments || leftSpecificity.dynamicSegments - rightSpecificity.dynamicSegments;
|
|
371
|
+
}
|
|
372
|
+
function sortLocalisedPatterns(patterns) {
|
|
373
|
+
return patterns.map((pattern, index)=>({
|
|
374
|
+
pattern,
|
|
375
|
+
index
|
|
376
|
+
})).sort((left, right)=>comparePatternSpecificity(left.pattern.pattern, right.pattern.pattern) || left.index - right.index).map(({ pattern })=>pattern);
|
|
377
|
+
}
|
|
378
|
+
function resolveLocalisedPath(pathname, targetLanguage, languages, localisedUrls) {
|
|
379
|
+
const normalizedPathname = normaliseSlashes(pathname);
|
|
380
|
+
const canonicalCandidates = sortLocalisedPatterns(Object.entries(localisedUrls || {}).map(([canonicalPattern, localisedUrlEntry])=>({
|
|
381
|
+
canonicalPattern,
|
|
382
|
+
localisedUrlEntry,
|
|
383
|
+
pattern: canonicalPattern
|
|
384
|
+
})));
|
|
385
|
+
for (const { canonicalPattern, localisedUrlEntry } of canonicalCandidates){
|
|
386
|
+
const targetPattern = localisedUrlEntry[targetLanguage];
|
|
387
|
+
const params = targetPattern ? matchPathPattern(normalizedPathname, canonicalPattern) : null;
|
|
388
|
+
if (params) return buildPathFromPattern(targetPattern, params);
|
|
389
|
+
}
|
|
390
|
+
const localisedCandidates = sortLocalisedPatterns(Object.values(localisedUrls || {}).flatMap((localisedUrlEntry)=>{
|
|
391
|
+
const targetPattern = localisedUrlEntry[targetLanguage];
|
|
392
|
+
if (!targetPattern) return [];
|
|
393
|
+
return languages.map((language)=>localisedUrlEntry[language]).filter(Boolean).map((sourcePattern)=>({
|
|
394
|
+
pattern: sourcePattern,
|
|
395
|
+
sourcePattern,
|
|
396
|
+
targetPattern
|
|
397
|
+
}));
|
|
398
|
+
}));
|
|
399
|
+
for (const { sourcePattern, targetPattern } of localisedCandidates){
|
|
400
|
+
const params = matchPathPattern(normalizedPathname, sourcePattern);
|
|
401
|
+
if (params) return buildPathFromPattern(targetPattern, params);
|
|
402
|
+
}
|
|
403
|
+
return normalizedPathname;
|
|
404
|
+
}
|
|
405
|
+
function localiseTargetPathname(pathname, language, languages, localisedUrls) {
|
|
406
|
+
const pathWithoutLanguage = stripLanguagePrefix(pathname, languages);
|
|
407
|
+
const resolvedPath = localisedUrls ? resolveLocalisedPath(pathWithoutLanguage, language, languages, localisedUrls) : pathWithoutLanguage;
|
|
408
|
+
const resolvedSegments = resolvedPath.split('/').filter(Boolean);
|
|
409
|
+
return `/${[
|
|
410
|
+
language,
|
|
411
|
+
...resolvedSegments
|
|
412
|
+
].join('/')}`;
|
|
413
|
+
}
|
|
414
|
+
function shouldIgnoreLocaleRedirect(pathname, route, entry) {
|
|
415
|
+
const remainingPathname = getRouteRemainingPathname(route, pathname);
|
|
416
|
+
const normalizedPathname = remainingPathname.startsWith('/') ? remainingPathname : `/${remainingPathname}`;
|
|
417
|
+
const ignoredPrefixes = [
|
|
418
|
+
...entry.ignoreRedirectRoutes || [],
|
|
419
|
+
...entry.staticRoutePrefixes || [],
|
|
420
|
+
'/static',
|
|
421
|
+
'/upload'
|
|
422
|
+
];
|
|
423
|
+
return ignoredPrefixes.some((prefix)=>{
|
|
424
|
+
const normalizedPrefix = normaliseSlashes(prefix);
|
|
425
|
+
return normalizedPathname === normalizedPrefix || normalizedPathname.startsWith(`${normalizedPrefix}/`);
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
function buildLocalizedUrl(route, request, language, entry) {
|
|
429
|
+
const url = new URL(request.url);
|
|
430
|
+
const basePath = getRouteBasePath(route);
|
|
431
|
+
const remainingPathname = getRouteRemainingPathname(route, url.pathname);
|
|
432
|
+
const localizedPathname = localiseTargetPathname(remainingPathname, language, entry.languages, entry.localisedUrls);
|
|
433
|
+
const nextPathname = '/' === basePath ? localizedPathname : normaliseSlashes(`${basePath}/${localizedPathname}`);
|
|
434
|
+
return `${nextPathname}${url.search}`;
|
|
435
|
+
}
|
|
436
|
+
function createLocaleRedirectResponse(location, entry) {
|
|
437
|
+
const headers = new Headers({
|
|
438
|
+
'cache-control': 'private, no-store',
|
|
439
|
+
location
|
|
440
|
+
});
|
|
441
|
+
const vary = [];
|
|
442
|
+
const detectionOrder = entry.i18nextDetector ? entry.detection?.order || [] : [];
|
|
443
|
+
if (detectionOrder.includes('header')) vary.push('Accept-Language');
|
|
444
|
+
if (detectionOrder.includes('cookie')) vary.push('Cookie');
|
|
445
|
+
if (vary.length > 0) headers.set('vary', vary.join(', '));
|
|
446
|
+
return new Response(null, {
|
|
447
|
+
headers,
|
|
448
|
+
status: 302
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
function createLocaleRedirectResponseForRequest(route, request) {
|
|
452
|
+
const entry = getRouteI18nEntry(route);
|
|
453
|
+
if (!entry || !Array.isArray(entry.languages) || 0 === entry.languages.length) return null;
|
|
454
|
+
const { pathname, search } = new URL(request.url);
|
|
455
|
+
if (isAssetLikePathname(pathname)) return null;
|
|
456
|
+
if (shouldIgnoreLocaleRedirect(pathname, route, entry)) return null;
|
|
457
|
+
const language = getLanguageFromPath(route, pathname, entry.languages);
|
|
458
|
+
if (!language) {
|
|
459
|
+
const targetLanguage = detectRedirectLanguage(request, entry) || entry.fallbackLanguage;
|
|
460
|
+
return createLocaleRedirectResponse(buildLocalizedUrl(route, request, targetLanguage, entry), entry);
|
|
461
|
+
}
|
|
462
|
+
if (entry.localisedUrls) {
|
|
463
|
+
const expectedUrl = buildLocalizedUrl(route, request, language, entry);
|
|
464
|
+
if (expectedUrl !== `${pathname}${search}`) return createLocaleRedirectResponse(expectedUrl, entry);
|
|
465
|
+
}
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
226
468
|
function findRoute(request) {
|
|
227
469
|
const { pathname } = new URL(request.url);
|
|
228
470
|
const routes = MODERN_WORKER_MANIFEST.routeSpec.routes;
|
|
@@ -399,21 +641,40 @@ async function collectRenderedRemoteCssHrefs(html, request, env) {
|
|
|
399
641
|
function escapeAttribute(value) {
|
|
400
642
|
return String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
401
643
|
}
|
|
644
|
+
function isAbsoluteUrl(value) {
|
|
645
|
+
return /^[a-z][a-z\d+.-]*:/iu.test(value);
|
|
646
|
+
}
|
|
647
|
+
function toRouteCssHtmlHref(asset) {
|
|
648
|
+
if (isAbsoluteUrl(asset) || asset.startsWith('/')) return asset;
|
|
649
|
+
return `/${asset.replace(/^\/+/u, '')}`;
|
|
650
|
+
}
|
|
402
651
|
async function withRouteCssLinks(response, route, routeManifest, request, env) {
|
|
403
652
|
const contentType = response.headers.get('content-type') || '';
|
|
404
653
|
if (!contentType.includes('text/html')) return response;
|
|
405
654
|
const html = await response.text();
|
|
406
|
-
const
|
|
407
|
-
...collectRouteCssAssets(route, routeManifest).map((asset)=>
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
655
|
+
const cssEntries = [
|
|
656
|
+
...collectRouteCssAssets(route, routeManifest).map((asset)=>{
|
|
657
|
+
const href = toRouteCssHtmlHref(asset);
|
|
658
|
+
return {
|
|
659
|
+
href,
|
|
660
|
+
preloadHref: new URL(href, request.url).toString()
|
|
661
|
+
};
|
|
662
|
+
}),
|
|
663
|
+
...(await collectRenderedRemoteCssHrefs(html, request, env)).map((href)=>({
|
|
664
|
+
href,
|
|
665
|
+
preloadHref: new URL(href, request.url).toString()
|
|
666
|
+
}))
|
|
413
667
|
];
|
|
668
|
+
if (0 === cssEntries.length) return response;
|
|
669
|
+
const seenCssHrefs = new Set();
|
|
670
|
+
const uniqueCssEntries = cssEntries.filter((entry)=>{
|
|
671
|
+
if (seenCssHrefs.has(entry.preloadHref)) return false;
|
|
672
|
+
seenCssHrefs.add(entry.preloadHref);
|
|
673
|
+
return true;
|
|
674
|
+
});
|
|
414
675
|
const headers = new Headers(response.headers);
|
|
415
|
-
for (const
|
|
416
|
-
const links =
|
|
676
|
+
for (const { preloadHref } of uniqueCssEntries)headers.append('link', `<${preloadHref}>; rel=preload; as=style`);
|
|
677
|
+
const links = uniqueCssEntries.filter(({ href, preloadHref })=>!html.includes(href) && !html.includes(preloadHref)).map(({ href })=>`<link rel="stylesheet" href="${escapeAttribute(href)}">`);
|
|
417
678
|
if (0 === links.length || !html.includes('</head>')) return new Response(html, {
|
|
418
679
|
headers,
|
|
419
680
|
status: response.status,
|
|
@@ -444,6 +705,10 @@ async function loadWorkerModule(workerPath) {
|
|
|
444
705
|
if (!workerModulePromises.has(workerPath)) workerModulePromises.set(workerPath, loader());
|
|
445
706
|
return workerModulePromises.get(workerPath);
|
|
446
707
|
}
|
|
708
|
+
async function loadEffectEdgeRuntime() {
|
|
709
|
+
effectEdgeRuntimePromise ??= import('@modern-js/plugin-bff/effect-edge');
|
|
710
|
+
return effectEdgeRuntimePromise;
|
|
711
|
+
}
|
|
447
712
|
function getRuntimeModule(workerModule) {
|
|
448
713
|
const defaultExport = workerModule.default;
|
|
449
714
|
const nestedDefaultExport = defaultExport && 'object' == typeof defaultExport ? defaultExport.default : void 0;
|
|
@@ -508,21 +773,6 @@ function createRequestForMountedPrefix(request, prefix) {
|
|
|
508
773
|
url.pathname = nextPath.startsWith('/') ? nextPath : `/${nextPath}`;
|
|
509
774
|
return new Request(url, request);
|
|
510
775
|
}
|
|
511
|
-
function createEffectContext(originalRequest, mountedRequest, env) {
|
|
512
|
-
const url = new URL(originalRequest.url);
|
|
513
|
-
return {
|
|
514
|
-
request: mountedRequest,
|
|
515
|
-
env: env || {},
|
|
516
|
-
path: url.pathname,
|
|
517
|
-
method: originalRequest.method,
|
|
518
|
-
operationContext: {
|
|
519
|
-
request: mountedRequest,
|
|
520
|
-
env: env || {},
|
|
521
|
-
path: url.pathname,
|
|
522
|
-
method: originalRequest.method
|
|
523
|
-
}
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
776
|
async function dispatchBffRequest(request, env) {
|
|
527
777
|
const bff = MODERN_WORKER_MANIFEST.bff;
|
|
528
778
|
if (!bff?.worker || !matchesPrefix(new URL(request.url).pathname, bff.prefix)) return null;
|
|
@@ -535,9 +785,23 @@ async function dispatchBffRequest(request, env) {
|
|
|
535
785
|
}
|
|
536
786
|
});
|
|
537
787
|
const mountedRequest = createRequestForMountedPrefix(request, bff.prefix);
|
|
538
|
-
const effectContext = createEffectContext(request, mountedRequest, env);
|
|
539
788
|
const defaultExport = workerModule.default;
|
|
540
789
|
const runtime = getRuntimeModule(workerModule);
|
|
790
|
+
if ('effect' === bff.runtimeFramework) {
|
|
791
|
+
let effectDispatcherPromise = effectBffDispatcherPromises.get(bff.worker);
|
|
792
|
+
if (!effectDispatcherPromise) {
|
|
793
|
+
const { createEffectBffEdgeDispatcher } = await loadEffectEdgeRuntime();
|
|
794
|
+
effectDispatcherPromise = createEffectBffEdgeDispatcher({
|
|
795
|
+
module: runtime,
|
|
796
|
+
prefix: bff.prefix
|
|
797
|
+
});
|
|
798
|
+
effectBffDispatcherPromises.set(bff.worker, effectDispatcherPromise);
|
|
799
|
+
}
|
|
800
|
+
const effectDispatcher = await effectDispatcherPromise;
|
|
801
|
+
return effectDispatcher.dispatch(request, {
|
|
802
|
+
env
|
|
803
|
+
});
|
|
804
|
+
}
|
|
541
805
|
const directHandler = 'function' == typeof runtime.handler && runtime.handler || 'function' == typeof defaultExport && defaultExport;
|
|
542
806
|
const createdHandler = 'function' == typeof runtime.createHandler ? runtime.createHandler().handler : void 0;
|
|
543
807
|
const handler = directHandler || createdHandler;
|
|
@@ -548,27 +812,66 @@ async function dispatchBffRequest(request, env) {
|
|
|
548
812
|
'x-modern-js-bff-worker': bff.worker
|
|
549
813
|
}
|
|
550
814
|
});
|
|
551
|
-
|
|
815
|
+
const effectContext = {
|
|
816
|
+
request: mountedRequest,
|
|
817
|
+
env: env || {},
|
|
818
|
+
path: new URL(request.url).pathname,
|
|
819
|
+
method: request.method,
|
|
820
|
+
operationContext: {
|
|
821
|
+
request: mountedRequest,
|
|
822
|
+
env: env || {},
|
|
823
|
+
path: new URL(request.url).pathname,
|
|
824
|
+
method: request.method
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
return handler(mountedRequest, effectContext);
|
|
828
|
+
}
|
|
829
|
+
async function dispatchServiceBindingRequest(request, env) {
|
|
830
|
+
const serviceBindings = MODERN_WORKER_MANIFEST.serviceBindings;
|
|
831
|
+
if (!Array.isArray(serviceBindings) || 0 === serviceBindings.length) return null;
|
|
832
|
+
const pathname = new URL(request.url).pathname;
|
|
833
|
+
for (const binding of serviceBindings){
|
|
834
|
+
if (!binding?.binding || !binding?.prefix) continue;
|
|
835
|
+
if (!matchesPrefix(pathname, binding.prefix)) continue;
|
|
836
|
+
const service = env?.[binding.binding];
|
|
837
|
+
if (!service || 'function' != typeof service.fetch) return new Response(`Cloudflare service binding not available: ${binding.binding}`, {
|
|
838
|
+
status: 502,
|
|
839
|
+
headers: {
|
|
840
|
+
'content-type': 'text/plain; charset=utf-8',
|
|
841
|
+
'x-modern-js-service-binding': binding.binding
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
return service.fetch(request);
|
|
845
|
+
}
|
|
846
|
+
return null;
|
|
552
847
|
}
|
|
553
848
|
export default {
|
|
554
849
|
async fetch (request, env, ctx) {
|
|
555
850
|
const corsPreflightResponse = await createCorsPreflightResponse(request, env);
|
|
556
851
|
if (corsPreflightResponse) return finalizeResponseForRequest(corsPreflightResponse, request);
|
|
557
|
-
const assetResponse = await fetchAsset(request, env);
|
|
558
|
-
if (assetResponse) return finalizeResponseForRequest(assetResponse, request);
|
|
559
852
|
const bffResponse = await dispatchBffRequest(request, env);
|
|
560
853
|
if (bffResponse) return finalizeResponseForRequest(withAppCorsHeaders(bffResponse, request), request);
|
|
854
|
+
const serviceBindingResponse = await dispatchServiceBindingRequest(request, env);
|
|
855
|
+
if (serviceBindingResponse) return finalizeResponseForRequest(withAppCorsHeaders(serviceBindingResponse, request), request);
|
|
561
856
|
const route = findRoute(request);
|
|
562
857
|
const { pathname } = new URL(request.url);
|
|
563
|
-
if (isAssetLikePathname(pathname) && !routeMatchesExactly(route, pathname))
|
|
564
|
-
|
|
565
|
-
|
|
858
|
+
if (isAssetLikePathname(pathname) && !routeMatchesExactly(route, pathname)) {
|
|
859
|
+
const assetResponse = await fetchAsset(request, env);
|
|
860
|
+
if (assetResponse) return finalizeResponseForRequest(assetResponse, request);
|
|
861
|
+
return finalizeResponseForRequest(withAppCorsHeaders(new Response('Not found', {
|
|
862
|
+
status: 404
|
|
863
|
+
}), request), request);
|
|
864
|
+
}
|
|
865
|
+
const localeRedirectResponse = createLocaleRedirectResponseForRequest(route, request);
|
|
866
|
+
if (localeRedirectResponse) return finalizeResponseForRequest(withAppCorsHeaders(localeRedirectResponse, request), request);
|
|
566
867
|
if (route?.worker) {
|
|
567
868
|
const renderableRequest = createRenderableRequest(request);
|
|
568
869
|
return finalizeResponseForRequest(withAppCorsHeaders(await dispatchRouteWorker(route, renderableRequest, env, ctx), request), request);
|
|
569
870
|
}
|
|
570
871
|
const htmlResponse = await fetchRouteHtml(route, request, env);
|
|
571
872
|
if (htmlResponse) return finalizeResponseForRequest(htmlResponse, request);
|
|
873
|
+
const assetResponse = await fetchAsset(request, env);
|
|
874
|
+
if (assetResponse) return finalizeResponseForRequest(assetResponse, request);
|
|
572
875
|
return finalizeResponseForRequest(withAppCorsHeaders(new Response('Not found', {
|
|
573
876
|
status: 404
|
|
574
877
|
}), request), request);
|
|
@@ -259,6 +259,7 @@ function getBuilderEnvironments(normalizedConfig, appContext, tempBuilderConfig)
|
|
|
259
259
|
chain.output.module(true).library({
|
|
260
260
|
type: 'module'
|
|
261
261
|
}).chunkFormat('module').chunkLoading('import').workerChunkLoading('import');
|
|
262
|
+
chain.optimization.usedExports(false).providedExports(true).innerGraph(false).sideEffects(false);
|
|
262
263
|
chain.plugins.delete('plugin-module-federation');
|
|
263
264
|
if (tanstackRouterSsrServerFile) {
|
|
264
265
|
chain.resolve.alias.set('@tanstack/router-core/ssr/server$', tanstackRouterSsrServerFile);
|
package/dist/esm/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { compatPlugin } from "./compat/index.mjs";
|
|
|
8
8
|
import { DEFAULT_RUNTIME_CONFIG_FILE } from "./constants.mjs";
|
|
9
9
|
import { i18n } from "./locale/index.mjs";
|
|
10
10
|
import analyze from "./plugins/analyze/index.mjs";
|
|
11
|
+
import backendFederationBuild from "./plugins/backendFederationBuild.mjs";
|
|
11
12
|
import deploy from "./plugins/deploy/index.mjs";
|
|
12
13
|
import initialize from "./plugins/initialize/index.mjs";
|
|
13
14
|
import serverBuild from "./plugins/serverBuild.mjs";
|
|
@@ -28,6 +29,7 @@ const appTools = ()=>({
|
|
|
28
29
|
initialize(),
|
|
29
30
|
analyze(),
|
|
30
31
|
serverBuild(),
|
|
32
|
+
backendFederationBuild(),
|
|
31
33
|
deploy()
|
|
32
34
|
],
|
|
33
35
|
post: [
|