Skip to content

Commit d8eba55

Browse files
committed
Step 3 - Implement module loader: Fetch import-map-config.json via deployUrl to build importmap for dependency loading; restart SSR server for mini-SPA updates.
1 parent 4157431 commit d8eba55

File tree

1 file changed

+104
-0
lines changed

1 file changed

+104
-0
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import type { Context, NextResolve } from './types';
2+
import {
3+
checkIfFileProtocol,
4+
checkIfNodeProtocol,
5+
DeferredPromise,
6+
resolveModulePath,
7+
} from './custom-loader-utils';
8+
import { getResultImportMap } from '../helpers';
9+
import { pathToFileURL } from 'url';
10+
// @ts-expect-error should be esm
11+
import type { ImportMap } from '@jspm/import-map';
12+
import process from 'node:process';
13+
import { join } from 'path';
14+
15+
export const DEPLOY_URL = 'DEPLOY_URL';
16+
17+
const deferredInit = new DeferredPromise<ImportMap>();
18+
19+
const fakeRootPath = pathToFileURL(
20+
join(process.cwd(), '../browser', '/')
21+
).toString();
22+
23+
async function initImportMap(deployHost: string) {
24+
const deployUrl = new URL('importmap.json', deployHost);
25+
const importMapConfig = await fetch(deployUrl)
26+
.then((r) => r.json())
27+
.catch((err) => {
28+
const newError = new Error(
29+
`Fetch "importmap.json" from "${deployHost}" failed`,
30+
{ cause: err }
31+
);
32+
throw newError;
33+
});
34+
35+
const resultImportMap = await getResultImportMap(importMapConfig);
36+
const importMapModule = await import('@jspm/import-map').then(
37+
(r) => r.ImportMap
38+
);
39+
40+
const importMap = new importMapModule({
41+
map: resultImportMap,
42+
mapUrl: deployUrl,
43+
rootUrl: pathToFileURL(process.cwd()).toString(),
44+
});
45+
deferredInit.resolve(importMap);
46+
}
47+
48+
async function initialize({ port }: { port: MessagePort }) {
49+
port.onmessage = async (event) => {
50+
if (event.data.kind === DEPLOY_URL) {
51+
initImportMap(event.data.result);
52+
}
53+
};
54+
}
55+
56+
async function resolve(
57+
specifier: string,
58+
context: Context,
59+
nextResolve: NextResolve
60+
) {
61+
const { parentURL } = context;
62+
63+
const importMap = await deferredInit;
64+
65+
const result = resolveModulePath(importMap, specifier, parentURL);
66+
67+
if (!result) {
68+
return nextResolve(specifier, context, nextResolve);
69+
}
70+
71+
if (checkIfNodeProtocol(result) || checkIfFileProtocol(result)) {
72+
return nextResolve(result, context, nextResolve);
73+
}
74+
75+
if (!result.startsWith('http')) {
76+
return nextResolve(result, context, nextResolve);
77+
}
78+
const specifierUrl = new URL(specifier, fakeRootPath);
79+
return {
80+
url: specifierUrl.toString(),
81+
shortCircuit: true,
82+
};
83+
}
84+
85+
async function load(url: string, context: Context, defaultLoad: NextResolve) {
86+
url = url.split('?').at(0);
87+
const { parentURL } = context;
88+
const specifier = url.replace(fakeRootPath, '');
89+
const importMap = await deferredInit;
90+
91+
const resolveUrl = resolveModulePath(importMap, specifier, parentURL);
92+
93+
if (resolveUrl.startsWith('http')) {
94+
const response = await fetch(resolveUrl).then((r) => r.text());
95+
return {
96+
format: 'module',
97+
source: 'var ngServerMode = true;\n' + response,
98+
shortCircuit: true,
99+
};
100+
}
101+
return defaultLoad(url, context, defaultLoad);
102+
}
103+
104+
export { initialize, resolve, load };

0 commit comments

Comments
 (0)