@bleedingdev/modern-js-app-tools 3.2.0-ultramodern.65 → 3.2.0-ultramodern.67
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.
|
@@ -2,6 +2,7 @@ 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 remoteJsonPromises = new Map();
|
|
5
6
|
const CORS_HEADERS = {
|
|
6
7
|
'access-control-allow-headers': '*',
|
|
7
8
|
'access-control-allow-methods': 'GET, HEAD, OPTIONS',
|
|
@@ -121,6 +122,127 @@ function createRequestHandlerOptions({ route, htmlTemplate, routeManifest, loada
|
|
|
121
122
|
}
|
|
122
123
|
};
|
|
123
124
|
}
|
|
125
|
+
function collectRouteCssAssets(route, routeManifest) {
|
|
126
|
+
const routeAssets = routeManifest?.routeAssets || {};
|
|
127
|
+
const candidateKeys = [
|
|
128
|
+
route.entryName,
|
|
129
|
+
`async-${route.entryName}`
|
|
130
|
+
].filter(Boolean);
|
|
131
|
+
const assets = new Set();
|
|
132
|
+
for (const key of candidateKeys){
|
|
133
|
+
const routeAsset = routeAssets[key];
|
|
134
|
+
const cssAssets = [
|
|
135
|
+
...Array.isArray(routeAsset?.referenceCssAssets) ? routeAsset.referenceCssAssets : [],
|
|
136
|
+
...Array.isArray(routeAsset?.assets) ? routeAsset.assets : []
|
|
137
|
+
];
|
|
138
|
+
for (const asset of cssAssets)if ('string' == typeof asset && asset.endsWith('.css')) assets.add(asset);
|
|
139
|
+
}
|
|
140
|
+
return [
|
|
141
|
+
...assets
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
function collectRenderedFederatedExposes(html) {
|
|
145
|
+
const renderedExposes = [];
|
|
146
|
+
const tagPattern = /<[^>]*data-modern-(?:boundary-id|mf-expose)=["'][^"']+["'][^>]*>/g;
|
|
147
|
+
const attributePattern = /\s(data-modern-(?:boundary-id|mf-expose))=["']([^"']+)["']/g;
|
|
148
|
+
for (const [tag] of html.matchAll(tagPattern)){
|
|
149
|
+
const attributes = {};
|
|
150
|
+
for (const [, name, value] of tag.matchAll(attributePattern))attributes[name] = value;
|
|
151
|
+
const boundaryId = attributes['data-modern-boundary-id'];
|
|
152
|
+
const expose = attributes['data-modern-mf-expose'];
|
|
153
|
+
if (boundaryId && expose) renderedExposes.push({
|
|
154
|
+
boundaryId,
|
|
155
|
+
expose
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return renderedExposes;
|
|
159
|
+
}
|
|
160
|
+
function getRemoteManifestUrl(remote, request) {
|
|
161
|
+
const entry = remote?.entry;
|
|
162
|
+
if ('string' != typeof entry || 0 === entry.length) return;
|
|
163
|
+
return new URL(entry, request.url).toString();
|
|
164
|
+
}
|
|
165
|
+
async function fetchRemoteJson(jsonUrl) {
|
|
166
|
+
if (!remoteJsonPromises.has(jsonUrl)) remoteJsonPromises.set(jsonUrl, fetch(jsonUrl).then((response)=>response.ok ? response.json() : {}).catch(()=>({})));
|
|
167
|
+
return remoteJsonPromises.get(jsonUrl);
|
|
168
|
+
}
|
|
169
|
+
function findRemoteExpose(remoteManifest, exposePath) {
|
|
170
|
+
const exposes = Array.isArray(remoteManifest?.exposes) ? remoteManifest.exposes : [];
|
|
171
|
+
const normalizedExpose = exposePath.replace(/^\.\//u, '');
|
|
172
|
+
return exposes.find((expose)=>{
|
|
173
|
+
if (!expose || 'object' != typeof expose) return false;
|
|
174
|
+
return expose.path === exposePath || expose.path === `./${normalizedExpose}` || expose.name === normalizedExpose;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function collectCssAssetEntries(assets) {
|
|
178
|
+
const cssAssets = assets?.css;
|
|
179
|
+
return [
|
|
180
|
+
...Array.isArray(cssAssets?.sync) ? cssAssets.sync : [],
|
|
181
|
+
...Array.isArray(cssAssets?.async) ? cssAssets.async : []
|
|
182
|
+
].filter((asset)=>'string' == typeof asset && asset.endsWith('.css'));
|
|
183
|
+
}
|
|
184
|
+
function collectRouteManifestCssAssets(routeManifest) {
|
|
185
|
+
const routeAssets = routeManifest?.routeAssets || {};
|
|
186
|
+
const assets = new Set();
|
|
187
|
+
for (const routeAsset of Object.values(routeAssets)){
|
|
188
|
+
const cssAssets = [
|
|
189
|
+
...Array.isArray(routeAsset?.referenceCssAssets) ? routeAsset.referenceCssAssets : [],
|
|
190
|
+
...Array.isArray(routeAsset?.assets) ? routeAsset.assets : []
|
|
191
|
+
];
|
|
192
|
+
for (const asset of cssAssets)if ('string' == typeof asset && asset.endsWith('.css')) assets.add(asset);
|
|
193
|
+
}
|
|
194
|
+
return [
|
|
195
|
+
...assets
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
async function collectRenderedRemoteCssHrefs(html, request, env) {
|
|
199
|
+
const renderedExposes = collectRenderedFederatedExposes(html);
|
|
200
|
+
if (0 === renderedExposes.length) return [];
|
|
201
|
+
const hostManifest = await readAssetJson('mf-manifest.json', request, env);
|
|
202
|
+
const remotes = Array.isArray(hostManifest?.remotes) ? hostManifest.remotes : [];
|
|
203
|
+
const remoteByBoundary = new Map();
|
|
204
|
+
const hrefs = new Set();
|
|
205
|
+
for (const remote of remotes){
|
|
206
|
+
if ('string' == typeof remote?.alias) remoteByBoundary.set(remote.alias, remote);
|
|
207
|
+
if ('string' == typeof remote?.federationContainerName) remoteByBoundary.set(remote.federationContainerName, remote);
|
|
208
|
+
}
|
|
209
|
+
await Promise.all(renderedExposes.map(async ({ boundaryId, expose })=>{
|
|
210
|
+
const remote = remoteByBoundary.get(boundaryId);
|
|
211
|
+
const manifestUrl = remote ? getRemoteManifestUrl(remote, request) : void 0;
|
|
212
|
+
if (!manifestUrl) return;
|
|
213
|
+
const remoteManifest = await fetchRemoteJson(manifestUrl);
|
|
214
|
+
const remoteExpose = findRemoteExpose(remoteManifest, expose);
|
|
215
|
+
const publicPath = 'string' == typeof remoteManifest?.metaData?.publicPath ? remoteManifest.metaData.publicPath : manifestUrl;
|
|
216
|
+
const remoteRouteManifest = await fetchRemoteJson(new URL('routes-manifest.json', publicPath).toString());
|
|
217
|
+
for (const asset of collectCssAssetEntries(remoteExpose?.assets))hrefs.add(new URL(asset, publicPath).toString());
|
|
218
|
+
for (const asset of collectRouteManifestCssAssets(remoteRouteManifest))hrefs.add(new URL(asset, publicPath).toString());
|
|
219
|
+
}));
|
|
220
|
+
return [
|
|
221
|
+
...hrefs
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
function escapeAttribute(value) {
|
|
225
|
+
return String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
226
|
+
}
|
|
227
|
+
async function withRouteCssLinks(response, route, routeManifest, request, env) {
|
|
228
|
+
const contentType = response.headers.get('content-type') || '';
|
|
229
|
+
if (!contentType.includes('text/html')) return response;
|
|
230
|
+
const html = await response.text();
|
|
231
|
+
const cssHrefs = [
|
|
232
|
+
...collectRouteCssAssets(route, routeManifest).map((asset)=>new URL(asset, request.url).toString()),
|
|
233
|
+
...await collectRenderedRemoteCssHrefs(html, request, env)
|
|
234
|
+
];
|
|
235
|
+
if (0 === cssHrefs.length) return response;
|
|
236
|
+
const links = [
|
|
237
|
+
...new Set(cssHrefs)
|
|
238
|
+
].filter((href)=>!html.includes(href)).map((href)=>`<link rel="stylesheet" href="${escapeAttribute(href)}">`);
|
|
239
|
+
if (0 === links.length || !html.includes('</head>')) return new Response(html, response);
|
|
240
|
+
return new Response(html.replace('</head>', `${links.join('')}</head>`), {
|
|
241
|
+
headers: response.headers,
|
|
242
|
+
status: response.status,
|
|
243
|
+
statusText: response.statusText
|
|
244
|
+
});
|
|
245
|
+
}
|
|
124
246
|
async function getRequestHandlerOptions(route, request, env) {
|
|
125
247
|
const [htmlTemplate, routeManifest, loadableStats] = await Promise.all([
|
|
126
248
|
readAssetText(route.entryPath, request, env),
|
|
@@ -180,7 +302,7 @@ async function dispatchRouteWorker(route, request, env, ctx) {
|
|
|
180
302
|
const requestHandler = await getRequestHandler(workerModule);
|
|
181
303
|
if ('function' == typeof requestHandler) {
|
|
182
304
|
const requestHandlerOptions = await getRequestHandlerOptions(route, request, env);
|
|
183
|
-
return requestHandler(request, requestHandlerOptions);
|
|
305
|
+
return withRouteCssLinks(await requestHandler(request, requestHandlerOptions), route, requestHandlerOptions.resource.routeManifest, request, env);
|
|
184
306
|
}
|
|
185
307
|
return new Response(`Worker bundle has no fetch or requestHandler export: ${workerPath}`, {
|
|
186
308
|
status: 500,
|
|
@@ -2,6 +2,7 @@ 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 remoteJsonPromises = new Map();
|
|
5
6
|
const CORS_HEADERS = {
|
|
6
7
|
'access-control-allow-headers': '*',
|
|
7
8
|
'access-control-allow-methods': 'GET, HEAD, OPTIONS',
|
|
@@ -121,6 +122,127 @@ function createRequestHandlerOptions({ route, htmlTemplate, routeManifest, loada
|
|
|
121
122
|
}
|
|
122
123
|
};
|
|
123
124
|
}
|
|
125
|
+
function collectRouteCssAssets(route, routeManifest) {
|
|
126
|
+
const routeAssets = routeManifest?.routeAssets || {};
|
|
127
|
+
const candidateKeys = [
|
|
128
|
+
route.entryName,
|
|
129
|
+
`async-${route.entryName}`
|
|
130
|
+
].filter(Boolean);
|
|
131
|
+
const assets = new Set();
|
|
132
|
+
for (const key of candidateKeys){
|
|
133
|
+
const routeAsset = routeAssets[key];
|
|
134
|
+
const cssAssets = [
|
|
135
|
+
...Array.isArray(routeAsset?.referenceCssAssets) ? routeAsset.referenceCssAssets : [],
|
|
136
|
+
...Array.isArray(routeAsset?.assets) ? routeAsset.assets : []
|
|
137
|
+
];
|
|
138
|
+
for (const asset of cssAssets)if ('string' == typeof asset && asset.endsWith('.css')) assets.add(asset);
|
|
139
|
+
}
|
|
140
|
+
return [
|
|
141
|
+
...assets
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
function collectRenderedFederatedExposes(html) {
|
|
145
|
+
const renderedExposes = [];
|
|
146
|
+
const tagPattern = /<[^>]*data-modern-(?:boundary-id|mf-expose)=["'][^"']+["'][^>]*>/g;
|
|
147
|
+
const attributePattern = /\s(data-modern-(?:boundary-id|mf-expose))=["']([^"']+)["']/g;
|
|
148
|
+
for (const [tag] of html.matchAll(tagPattern)){
|
|
149
|
+
const attributes = {};
|
|
150
|
+
for (const [, name, value] of tag.matchAll(attributePattern))attributes[name] = value;
|
|
151
|
+
const boundaryId = attributes['data-modern-boundary-id'];
|
|
152
|
+
const expose = attributes['data-modern-mf-expose'];
|
|
153
|
+
if (boundaryId && expose) renderedExposes.push({
|
|
154
|
+
boundaryId,
|
|
155
|
+
expose
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return renderedExposes;
|
|
159
|
+
}
|
|
160
|
+
function getRemoteManifestUrl(remote, request) {
|
|
161
|
+
const entry = remote?.entry;
|
|
162
|
+
if ('string' != typeof entry || 0 === entry.length) return;
|
|
163
|
+
return new URL(entry, request.url).toString();
|
|
164
|
+
}
|
|
165
|
+
async function fetchRemoteJson(jsonUrl) {
|
|
166
|
+
if (!remoteJsonPromises.has(jsonUrl)) remoteJsonPromises.set(jsonUrl, fetch(jsonUrl).then((response)=>response.ok ? response.json() : {}).catch(()=>({})));
|
|
167
|
+
return remoteJsonPromises.get(jsonUrl);
|
|
168
|
+
}
|
|
169
|
+
function findRemoteExpose(remoteManifest, exposePath) {
|
|
170
|
+
const exposes = Array.isArray(remoteManifest?.exposes) ? remoteManifest.exposes : [];
|
|
171
|
+
const normalizedExpose = exposePath.replace(/^\.\//u, '');
|
|
172
|
+
return exposes.find((expose)=>{
|
|
173
|
+
if (!expose || 'object' != typeof expose) return false;
|
|
174
|
+
return expose.path === exposePath || expose.path === `./${normalizedExpose}` || expose.name === normalizedExpose;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function collectCssAssetEntries(assets) {
|
|
178
|
+
const cssAssets = assets?.css;
|
|
179
|
+
return [
|
|
180
|
+
...Array.isArray(cssAssets?.sync) ? cssAssets.sync : [],
|
|
181
|
+
...Array.isArray(cssAssets?.async) ? cssAssets.async : []
|
|
182
|
+
].filter((asset)=>'string' == typeof asset && asset.endsWith('.css'));
|
|
183
|
+
}
|
|
184
|
+
function collectRouteManifestCssAssets(routeManifest) {
|
|
185
|
+
const routeAssets = routeManifest?.routeAssets || {};
|
|
186
|
+
const assets = new Set();
|
|
187
|
+
for (const routeAsset of Object.values(routeAssets)){
|
|
188
|
+
const cssAssets = [
|
|
189
|
+
...Array.isArray(routeAsset?.referenceCssAssets) ? routeAsset.referenceCssAssets : [],
|
|
190
|
+
...Array.isArray(routeAsset?.assets) ? routeAsset.assets : []
|
|
191
|
+
];
|
|
192
|
+
for (const asset of cssAssets)if ('string' == typeof asset && asset.endsWith('.css')) assets.add(asset);
|
|
193
|
+
}
|
|
194
|
+
return [
|
|
195
|
+
...assets
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
async function collectRenderedRemoteCssHrefs(html, request, env) {
|
|
199
|
+
const renderedExposes = collectRenderedFederatedExposes(html);
|
|
200
|
+
if (0 === renderedExposes.length) return [];
|
|
201
|
+
const hostManifest = await readAssetJson('mf-manifest.json', request, env);
|
|
202
|
+
const remotes = Array.isArray(hostManifest?.remotes) ? hostManifest.remotes : [];
|
|
203
|
+
const remoteByBoundary = new Map();
|
|
204
|
+
const hrefs = new Set();
|
|
205
|
+
for (const remote of remotes){
|
|
206
|
+
if ('string' == typeof remote?.alias) remoteByBoundary.set(remote.alias, remote);
|
|
207
|
+
if ('string' == typeof remote?.federationContainerName) remoteByBoundary.set(remote.federationContainerName, remote);
|
|
208
|
+
}
|
|
209
|
+
await Promise.all(renderedExposes.map(async ({ boundaryId, expose })=>{
|
|
210
|
+
const remote = remoteByBoundary.get(boundaryId);
|
|
211
|
+
const manifestUrl = remote ? getRemoteManifestUrl(remote, request) : void 0;
|
|
212
|
+
if (!manifestUrl) return;
|
|
213
|
+
const remoteManifest = await fetchRemoteJson(manifestUrl);
|
|
214
|
+
const remoteExpose = findRemoteExpose(remoteManifest, expose);
|
|
215
|
+
const publicPath = 'string' == typeof remoteManifest?.metaData?.publicPath ? remoteManifest.metaData.publicPath : manifestUrl;
|
|
216
|
+
const remoteRouteManifest = await fetchRemoteJson(new URL('routes-manifest.json', publicPath).toString());
|
|
217
|
+
for (const asset of collectCssAssetEntries(remoteExpose?.assets))hrefs.add(new URL(asset, publicPath).toString());
|
|
218
|
+
for (const asset of collectRouteManifestCssAssets(remoteRouteManifest))hrefs.add(new URL(asset, publicPath).toString());
|
|
219
|
+
}));
|
|
220
|
+
return [
|
|
221
|
+
...hrefs
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
function escapeAttribute(value) {
|
|
225
|
+
return String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
226
|
+
}
|
|
227
|
+
async function withRouteCssLinks(response, route, routeManifest, request, env) {
|
|
228
|
+
const contentType = response.headers.get('content-type') || '';
|
|
229
|
+
if (!contentType.includes('text/html')) return response;
|
|
230
|
+
const html = await response.text();
|
|
231
|
+
const cssHrefs = [
|
|
232
|
+
...collectRouteCssAssets(route, routeManifest).map((asset)=>new URL(asset, request.url).toString()),
|
|
233
|
+
...await collectRenderedRemoteCssHrefs(html, request, env)
|
|
234
|
+
];
|
|
235
|
+
if (0 === cssHrefs.length) return response;
|
|
236
|
+
const links = [
|
|
237
|
+
...new Set(cssHrefs)
|
|
238
|
+
].filter((href)=>!html.includes(href)).map((href)=>`<link rel="stylesheet" href="${escapeAttribute(href)}">`);
|
|
239
|
+
if (0 === links.length || !html.includes('</head>')) return new Response(html, response);
|
|
240
|
+
return new Response(html.replace('</head>', `${links.join('')}</head>`), {
|
|
241
|
+
headers: response.headers,
|
|
242
|
+
status: response.status,
|
|
243
|
+
statusText: response.statusText
|
|
244
|
+
});
|
|
245
|
+
}
|
|
124
246
|
async function getRequestHandlerOptions(route, request, env) {
|
|
125
247
|
const [htmlTemplate, routeManifest, loadableStats] = await Promise.all([
|
|
126
248
|
readAssetText(route.entryPath, request, env),
|
|
@@ -180,7 +302,7 @@ async function dispatchRouteWorker(route, request, env, ctx) {
|
|
|
180
302
|
const requestHandler = await getRequestHandler(workerModule);
|
|
181
303
|
if ('function' == typeof requestHandler) {
|
|
182
304
|
const requestHandlerOptions = await getRequestHandlerOptions(route, request, env);
|
|
183
|
-
return requestHandler(request, requestHandlerOptions);
|
|
305
|
+
return withRouteCssLinks(await requestHandler(request, requestHandlerOptions), route, requestHandlerOptions.resource.routeManifest, request, env);
|
|
184
306
|
}
|
|
185
307
|
return new Response(`Worker bundle has no fetch or requestHandler export: ${workerPath}`, {
|
|
186
308
|
status: 500,
|
|
@@ -2,6 +2,7 @@ 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 remoteJsonPromises = new Map();
|
|
5
6
|
const CORS_HEADERS = {
|
|
6
7
|
'access-control-allow-headers': '*',
|
|
7
8
|
'access-control-allow-methods': 'GET, HEAD, OPTIONS',
|
|
@@ -121,6 +122,127 @@ function createRequestHandlerOptions({ route, htmlTemplate, routeManifest, loada
|
|
|
121
122
|
}
|
|
122
123
|
};
|
|
123
124
|
}
|
|
125
|
+
function collectRouteCssAssets(route, routeManifest) {
|
|
126
|
+
const routeAssets = routeManifest?.routeAssets || {};
|
|
127
|
+
const candidateKeys = [
|
|
128
|
+
route.entryName,
|
|
129
|
+
`async-${route.entryName}`
|
|
130
|
+
].filter(Boolean);
|
|
131
|
+
const assets = new Set();
|
|
132
|
+
for (const key of candidateKeys){
|
|
133
|
+
const routeAsset = routeAssets[key];
|
|
134
|
+
const cssAssets = [
|
|
135
|
+
...Array.isArray(routeAsset?.referenceCssAssets) ? routeAsset.referenceCssAssets : [],
|
|
136
|
+
...Array.isArray(routeAsset?.assets) ? routeAsset.assets : []
|
|
137
|
+
];
|
|
138
|
+
for (const asset of cssAssets)if ('string' == typeof asset && asset.endsWith('.css')) assets.add(asset);
|
|
139
|
+
}
|
|
140
|
+
return [
|
|
141
|
+
...assets
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
function collectRenderedFederatedExposes(html) {
|
|
145
|
+
const renderedExposes = [];
|
|
146
|
+
const tagPattern = /<[^>]*data-modern-(?:boundary-id|mf-expose)=["'][^"']+["'][^>]*>/g;
|
|
147
|
+
const attributePattern = /\s(data-modern-(?:boundary-id|mf-expose))=["']([^"']+)["']/g;
|
|
148
|
+
for (const [tag] of html.matchAll(tagPattern)){
|
|
149
|
+
const attributes = {};
|
|
150
|
+
for (const [, name, value] of tag.matchAll(attributePattern))attributes[name] = value;
|
|
151
|
+
const boundaryId = attributes['data-modern-boundary-id'];
|
|
152
|
+
const expose = attributes['data-modern-mf-expose'];
|
|
153
|
+
if (boundaryId && expose) renderedExposes.push({
|
|
154
|
+
boundaryId,
|
|
155
|
+
expose
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return renderedExposes;
|
|
159
|
+
}
|
|
160
|
+
function getRemoteManifestUrl(remote, request) {
|
|
161
|
+
const entry = remote?.entry;
|
|
162
|
+
if ('string' != typeof entry || 0 === entry.length) return;
|
|
163
|
+
return new URL(entry, request.url).toString();
|
|
164
|
+
}
|
|
165
|
+
async function fetchRemoteJson(jsonUrl) {
|
|
166
|
+
if (!remoteJsonPromises.has(jsonUrl)) remoteJsonPromises.set(jsonUrl, fetch(jsonUrl).then((response)=>response.ok ? response.json() : {}).catch(()=>({})));
|
|
167
|
+
return remoteJsonPromises.get(jsonUrl);
|
|
168
|
+
}
|
|
169
|
+
function findRemoteExpose(remoteManifest, exposePath) {
|
|
170
|
+
const exposes = Array.isArray(remoteManifest?.exposes) ? remoteManifest.exposes : [];
|
|
171
|
+
const normalizedExpose = exposePath.replace(/^\.\//u, '');
|
|
172
|
+
return exposes.find((expose)=>{
|
|
173
|
+
if (!expose || 'object' != typeof expose) return false;
|
|
174
|
+
return expose.path === exposePath || expose.path === `./${normalizedExpose}` || expose.name === normalizedExpose;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function collectCssAssetEntries(assets) {
|
|
178
|
+
const cssAssets = assets?.css;
|
|
179
|
+
return [
|
|
180
|
+
...Array.isArray(cssAssets?.sync) ? cssAssets.sync : [],
|
|
181
|
+
...Array.isArray(cssAssets?.async) ? cssAssets.async : []
|
|
182
|
+
].filter((asset)=>'string' == typeof asset && asset.endsWith('.css'));
|
|
183
|
+
}
|
|
184
|
+
function collectRouteManifestCssAssets(routeManifest) {
|
|
185
|
+
const routeAssets = routeManifest?.routeAssets || {};
|
|
186
|
+
const assets = new Set();
|
|
187
|
+
for (const routeAsset of Object.values(routeAssets)){
|
|
188
|
+
const cssAssets = [
|
|
189
|
+
...Array.isArray(routeAsset?.referenceCssAssets) ? routeAsset.referenceCssAssets : [],
|
|
190
|
+
...Array.isArray(routeAsset?.assets) ? routeAsset.assets : []
|
|
191
|
+
];
|
|
192
|
+
for (const asset of cssAssets)if ('string' == typeof asset && asset.endsWith('.css')) assets.add(asset);
|
|
193
|
+
}
|
|
194
|
+
return [
|
|
195
|
+
...assets
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
async function collectRenderedRemoteCssHrefs(html, request, env) {
|
|
199
|
+
const renderedExposes = collectRenderedFederatedExposes(html);
|
|
200
|
+
if (0 === renderedExposes.length) return [];
|
|
201
|
+
const hostManifest = await readAssetJson('mf-manifest.json', request, env);
|
|
202
|
+
const remotes = Array.isArray(hostManifest?.remotes) ? hostManifest.remotes : [];
|
|
203
|
+
const remoteByBoundary = new Map();
|
|
204
|
+
const hrefs = new Set();
|
|
205
|
+
for (const remote of remotes){
|
|
206
|
+
if ('string' == typeof remote?.alias) remoteByBoundary.set(remote.alias, remote);
|
|
207
|
+
if ('string' == typeof remote?.federationContainerName) remoteByBoundary.set(remote.federationContainerName, remote);
|
|
208
|
+
}
|
|
209
|
+
await Promise.all(renderedExposes.map(async ({ boundaryId, expose })=>{
|
|
210
|
+
const remote = remoteByBoundary.get(boundaryId);
|
|
211
|
+
const manifestUrl = remote ? getRemoteManifestUrl(remote, request) : void 0;
|
|
212
|
+
if (!manifestUrl) return;
|
|
213
|
+
const remoteManifest = await fetchRemoteJson(manifestUrl);
|
|
214
|
+
const remoteExpose = findRemoteExpose(remoteManifest, expose);
|
|
215
|
+
const publicPath = 'string' == typeof remoteManifest?.metaData?.publicPath ? remoteManifest.metaData.publicPath : manifestUrl;
|
|
216
|
+
const remoteRouteManifest = await fetchRemoteJson(new URL('routes-manifest.json', publicPath).toString());
|
|
217
|
+
for (const asset of collectCssAssetEntries(remoteExpose?.assets))hrefs.add(new URL(asset, publicPath).toString());
|
|
218
|
+
for (const asset of collectRouteManifestCssAssets(remoteRouteManifest))hrefs.add(new URL(asset, publicPath).toString());
|
|
219
|
+
}));
|
|
220
|
+
return [
|
|
221
|
+
...hrefs
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
function escapeAttribute(value) {
|
|
225
|
+
return String(value).replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
|
226
|
+
}
|
|
227
|
+
async function withRouteCssLinks(response, route, routeManifest, request, env) {
|
|
228
|
+
const contentType = response.headers.get('content-type') || '';
|
|
229
|
+
if (!contentType.includes('text/html')) return response;
|
|
230
|
+
const html = await response.text();
|
|
231
|
+
const cssHrefs = [
|
|
232
|
+
...collectRouteCssAssets(route, routeManifest).map((asset)=>new URL(asset, request.url).toString()),
|
|
233
|
+
...await collectRenderedRemoteCssHrefs(html, request, env)
|
|
234
|
+
];
|
|
235
|
+
if (0 === cssHrefs.length) return response;
|
|
236
|
+
const links = [
|
|
237
|
+
...new Set(cssHrefs)
|
|
238
|
+
].filter((href)=>!html.includes(href)).map((href)=>`<link rel="stylesheet" href="${escapeAttribute(href)}">`);
|
|
239
|
+
if (0 === links.length || !html.includes('</head>')) return new Response(html, response);
|
|
240
|
+
return new Response(html.replace('</head>', `${links.join('')}</head>`), {
|
|
241
|
+
headers: response.headers,
|
|
242
|
+
status: response.status,
|
|
243
|
+
statusText: response.statusText
|
|
244
|
+
});
|
|
245
|
+
}
|
|
124
246
|
async function getRequestHandlerOptions(route, request, env) {
|
|
125
247
|
const [htmlTemplate, routeManifest, loadableStats] = await Promise.all([
|
|
126
248
|
readAssetText(route.entryPath, request, env),
|
|
@@ -180,7 +302,7 @@ async function dispatchRouteWorker(route, request, env, ctx) {
|
|
|
180
302
|
const requestHandler = await getRequestHandler(workerModule);
|
|
181
303
|
if ('function' == typeof requestHandler) {
|
|
182
304
|
const requestHandlerOptions = await getRequestHandlerOptions(route, request, env);
|
|
183
|
-
return requestHandler(request, requestHandlerOptions);
|
|
305
|
+
return withRouteCssLinks(await requestHandler(request, requestHandlerOptions), route, requestHandlerOptions.resource.routeManifest, request, env);
|
|
184
306
|
}
|
|
185
307
|
return new Response(`Worker bundle has no fetch or requestHandler export: ${workerPath}`, {
|
|
186
308
|
status: 500,
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"modern",
|
|
18
18
|
"modern.js"
|
|
19
19
|
],
|
|
20
|
-
"version": "3.2.0-ultramodern.
|
|
20
|
+
"version": "3.2.0-ultramodern.67",
|
|
21
21
|
"types": "./dist/types/index.d.ts",
|
|
22
22
|
"main": "./dist/cjs/index.js",
|
|
23
23
|
"exports": {
|
|
@@ -99,16 +99,16 @@
|
|
|
99
99
|
"ndepe": "^0.1.13",
|
|
100
100
|
"pkg-types": "^2.3.1",
|
|
101
101
|
"std-env": "4.1.0",
|
|
102
|
-
"@modern-js/
|
|
103
|
-
"@modern-js/
|
|
104
|
-
"@modern-js/
|
|
105
|
-
"@modern-js/server": "npm:@bleedingdev/modern-js-server@3.2.0-ultramodern.
|
|
106
|
-
"@modern-js/
|
|
107
|
-
"@modern-js/
|
|
108
|
-
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.2.0-ultramodern.
|
|
109
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.
|
|
110
|
-
"@modern-js/
|
|
111
|
-
"@modern-js/
|
|
102
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.2.0-ultramodern.67",
|
|
103
|
+
"@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.2.0-ultramodern.67",
|
|
104
|
+
"@modern-js/plugin-data-loader": "npm:@bleedingdev/modern-js-plugin-data-loader@3.2.0-ultramodern.67",
|
|
105
|
+
"@modern-js/prod-server": "npm:@bleedingdev/modern-js-prod-server@3.2.0-ultramodern.67",
|
|
106
|
+
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.67",
|
|
107
|
+
"@modern-js/server": "npm:@bleedingdev/modern-js-server@3.2.0-ultramodern.67",
|
|
108
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.2.0-ultramodern.67",
|
|
109
|
+
"@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.2.0-ultramodern.67",
|
|
110
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.2.0-ultramodern.67",
|
|
111
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.2.0-ultramodern.67"
|
|
112
112
|
},
|
|
113
113
|
"devDependencies": {
|
|
114
114
|
"@rslib/core": "0.21.5",
|