Skip to content

Commit 9d781c6

Browse files
committed
Step 2 - Pre-generate importmap to bypass es-module-shims.
1 parent a4099cb commit 9d781c6

File tree

7 files changed

+188
-6
lines changed

7 files changed

+188
-6
lines changed

libs/nx-angular-mf/src/builders/helpers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ export * from './prepare-config'
33
export * from './dependencies'
44
export * from './patch-builder-context'
55
export * from './load-module'
6+
export * from './init-import-map-utils'
7+
export * from './transform-html'
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
export function addToPathName(currentPathname, addPathname) {
2+
return [
3+
...currentPathname.split('/').filter((i) => !!i),
4+
...addPathname.split('/').filter((i) => !!i),
5+
].join('/');
6+
}
7+
8+
export async function fetchImportMap(host: string) {
9+
const urlHost = new URL(host);
10+
const pathName = addToPathName(urlHost.pathname, 'importmap.json');
11+
const url = new URL(pathName, urlHost.origin).toString();
12+
try {
13+
const r = await fetch(url);
14+
return await r.json();
15+
} catch (e) {
16+
console.log(url);
17+
throw e;
18+
}
19+
}
20+
21+
export async function getResultImportMap(mainImportMap) {
22+
const resultImportMap = {
23+
imports: mainImportMap['imports'],
24+
scopes: {},
25+
};
26+
27+
if (
28+
mainImportMap['remoteEntry'] &&
29+
typeof mainImportMap['remoteEntry'] === 'object'
30+
) {
31+
const resultPromise = Object.entries<string>(
32+
mainImportMap['remoteEntry']
33+
).map(([key, value]) => {
34+
return fetchImportMap(value).then((r) => {
35+
return {
36+
imports: Object.entries<string>(r.exposes).reduce((acum, [k, v]) => {
37+
const UrlRemote = new URL(value);
38+
39+
acum[`${key}/${k}`] = new URL(
40+
addToPathName(UrlRemote.pathname, v),
41+
UrlRemote.origin
42+
).toString();
43+
return acum;
44+
}, {}),
45+
scopes: {
46+
[value]: Object.entries(r.imports)
47+
.filter(([k, v]) => {
48+
if (!resultImportMap['imports'][k]) {
49+
return true;
50+
}
51+
const path = resultImportMap['imports'][k];
52+
// @ts-expect-error should be using as string function
53+
return !path.endsWith(v.split('/').at(-1));
54+
})
55+
.reduce((acum, [k, v]) => {
56+
acum[k] = v;
57+
return acum;
58+
}, {}),
59+
},
60+
};
61+
});
62+
});
63+
64+
const result = await Promise.all(resultPromise);
65+
for (const resultItem of result) {
66+
resultImportMap['imports'] = {
67+
...resultImportMap['imports'],
68+
...resultItem['imports'],
69+
};
70+
resultImportMap['scopes'] = {
71+
...resultImportMap['scopes'],
72+
...resultItem['scopes'],
73+
};
74+
}
75+
}
76+
return resultImportMap;
77+
}

libs/nx-angular-mf/src/builders/helpers/prepare-config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ export async function prepareConfig(
5050
(i) => !skipList.includes(i.key)
5151
),
5252
outPutFileNames: [],
53-
esPlugins
53+
esPlugins,
54+
allImportMap: {},
5455
};
5556
}
5657

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { parse, serialize, parseFragment } from 'parse5';
2+
3+
import { getDataForImportMap } from './utils';
4+
import { ConfigMf } from '../types';
5+
import { getResultImportMap } from './init-import-map-utils';
6+
7+
function findBody(node) {
8+
let bodyNode = null;
9+
const innerFunction = (node) => {
10+
if (!node.childNodes) return;
11+
12+
for (const child of node.childNodes) {
13+
if (child.tagName === 'body') {
14+
bodyNode = child;
15+
break;
16+
}
17+
innerFunction(child);
18+
}
19+
};
20+
innerFunction(node);
21+
return bodyNode;
22+
}
23+
24+
function removeScriptModules(node) {
25+
const scriptModules = [];
26+
let bodyNode = null;
27+
const innerFunction = (node) => {
28+
if (!node.childNodes) return;
29+
30+
node.childNodes = node.childNodes.filter((child) => {
31+
const isPreloadLink =
32+
child['tagName'] === 'link' &&
33+
child['attrs'].some(
34+
(attr) => attr.name === 'rel' && attr.value === 'modulepreload'
35+
);
36+
37+
if (isPreloadLink) {
38+
scriptModules.push(child);
39+
return !isPreloadLink;
40+
}
41+
42+
const isModuleScript =
43+
child['tagName'] === 'script' &&
44+
(child['attrs'] || []).some(
45+
(attr) => attr.name === 'type' && attr.value === 'module'
46+
);
47+
48+
if (child.tagName === 'body') {
49+
bodyNode = child;
50+
}
51+
52+
if (isModuleScript) {
53+
scriptModules.push(child);
54+
}
55+
return !isModuleScript;
56+
});
57+
for (const child of node.childNodes) {
58+
innerFunction(child);
59+
}
60+
};
61+
62+
innerFunction(node);
63+
64+
return { scriptModules, bodyNode };
65+
}
66+
67+
export async function indexHtml(
68+
mfeConfig: ConfigMf,
69+
): Promise<(input: string) => Promise<string>> {
70+
const dataImport = getDataForImportMap(mfeConfig);
71+
const allImportMap = await getResultImportMap(dataImport);
72+
mfeConfig.allImportMap = allImportMap;
73+
return async (input: string) => {
74+
const importMapStr = JSON.stringify(allImportMap);
75+
76+
const importScriptElement = {
77+
nodeName: 'script',
78+
tagName: 'script',
79+
attrs: [
80+
{
81+
name: 'type',
82+
value: 'importmap',
83+
},
84+
],
85+
namespaceURI: 'http://www.w3.org/1999/xhtml',
86+
childNodes: [
87+
{
88+
nodeName: '#text',
89+
value: importMapStr,
90+
},
91+
],
92+
parentNode: null,
93+
};
94+
95+
const document = parse(input);
96+
const { bodyNode, scriptModules } = removeScriptModules(document);
97+
bodyNode.childNodes.push(...[importScriptElement, ...scriptModules]);
98+
return serialize(document);
99+
};
100+
}

libs/nx-angular-mf/src/builders/helpers/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export function getDataForImportMap(
5050
return {
5151
imports: [...mapShareObject.entries()].reduce((acum, [key, val]) => {
5252

53-
acum[val.packageName] = key + '.js';
53+
acum[val.packageName] = `./${key}.js`;
5454

5555
return acum;
5656
}, {}),

libs/nx-angular-mf/src/builders/serve/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { Plugin } from 'esbuild';
1212

1313
import { ServeExecutorSchema } from './schema';
1414
import { BuildExecutorSchema } from '../build/schema';
15-
import { deepMergeObject, getMapName, loadModule, patchBuilderContext, prepareConfig } from '../helpers';
15+
import { deepMergeObject, getMapName, indexHtml, loadModule, patchBuilderContext, prepareConfig } from '../helpers';
1616
import { entryPointForExtendDependencies } from '../es-plugin';
1717

1818

@@ -94,10 +94,11 @@ export async function* runBuilder(
9494
buildPlugins: resultEsBuild,
9595
};
9696

97+
const mainTransform = await indexHtml(optionsMfe);
98+
99+
97100
const transforms = {
98-
indexHtml: async (input: string) => {
99-
return input;
100-
},
101+
indexHtml: mainTransform,
101102
};
102103

103104
const runServer = serveWithVite(

libs/nx-angular-mf/src/builders/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export type ConfigMf = {
55
sharedMappings: { key: string; path: string }[];
66
outPutFileNames: string[];
77
esPlugins: string[];
8+
allImportMap: Record<string, unknown>;
89
};
910

1011
export type ShareOptions = {

0 commit comments

Comments
 (0)