Skip to content

Commit f0de288

Browse files
authored
examples: chainloading (#67)
1 parent 789767c commit f0de288

File tree

4 files changed

+160
-0
lines changed

4 files changed

+160
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#!/usr/bin/env -S deno run --allow-read --allow-write --allow-net --no-check
2+
import { load } from "../../loader.ts";
3+
import { build, LoadResponse } from "../../mod.ts";
4+
// import { load } from "https://deno.land/x/eszip@v0.18.0/loader.ts";
5+
// import { build, LoadResponse } from "https://deno.land/x/eszip@v0.18.0/mod.ts";
6+
7+
import * as path from "https://deno.land/std@0.127.0/path/mod.ts";
8+
9+
const STAGE1_SPECIFIER = "acme:stage1";
10+
const STAGE2_SPECIFIER = "acme:stage2";
11+
12+
function stage1() {
13+
return `
14+
import * as stage2 from "${STAGE2_SPECIFIER}";
15+
import { serve } from "https://deno.land/std@0.127.0/http/server.ts";
16+
17+
await serve(async (req) => {
18+
const url = new URL(req.url);
19+
const handler = stage2.config.urls[url.pathname];
20+
const handlerFn = stage2.handlers[handler];
21+
return handlerFn ? await handlerFn(req) : new Response("DEFAULT");
22+
});
23+
`;
24+
}
25+
26+
function inlineTsMod(specifier: string, content: string): LoadResponse {
27+
return {
28+
content,
29+
headers: {
30+
"content-type": "application/typescript",
31+
},
32+
kind: "module",
33+
specifier,
34+
};
35+
}
36+
37+
function stage1Loader() {
38+
return async (specifier: string): Promise<LoadResponse | undefined> => {
39+
if (specifier === STAGE1_SPECIFIER) {
40+
// Load stage 1 wrapper from net/disk or codegenerated in-memory
41+
return inlineTsMod(specifier, stage1());
42+
} else if (specifier === STAGE2_SPECIFIER) {
43+
// Dangling reference to stage2
44+
return { kind: "external", specifier };
45+
}
46+
// Falling back to the default loading logic.
47+
return load(specifier);
48+
};
49+
}
50+
51+
interface Stage2Config {
52+
paths: Record<string, string>; // handler_name => entrypoint_path (absolute)
53+
urls: Record<string, string>; // url => handler_name mapping
54+
localSrcRoot: string;
55+
}
56+
57+
function stage2(conf: Stage2Config) {
58+
const handlerNames = Object.keys(conf.paths);
59+
const handlerImports = handlerNames.map((name) => {
60+
return `import ${name} from "${nsSpec(conf, name)}";`;
61+
}).join("\n");
62+
return `
63+
${handlerImports}
64+
export const handlers = { ${handlerNames.join(", ")} };
65+
export const config = ${JSON.stringify(conf)};
66+
`;
67+
}
68+
69+
/// Namespaced specifier, file:///src/... (relative to root)
70+
function nsSpec(conf: Stage2Config, name: string) {
71+
const filepath = conf.paths[name];
72+
const rel = path.relative(conf.localSrcRoot, filepath);
73+
return path.toFileUrl(path.join("/src", rel));
74+
}
75+
76+
function stage2Loader(conf: Stage2Config) {
77+
return async (specifier: string): Promise<LoadResponse | undefined> => {
78+
if (specifier === STAGE2_SPECIFIER) {
79+
// Codegen stage2 from config to include handler specifics
80+
return inlineTsMod(specifier, stage2(conf));
81+
} else if (specifier.startsWith("file:///src/")) {
82+
// Local specifier (handler entrypoint or relative import)
83+
const localRoot = path.toFileUrl(conf.localSrcRoot).toString();
84+
const trueSpec = specifier.replace("file:///src", localRoot);
85+
const resp = await load(trueSpec);
86+
return resp && {
87+
...resp,
88+
specifier, // preserve original spec
89+
};
90+
}
91+
// Falling back to the default loading logic.
92+
return await load(specifier);
93+
};
94+
}
95+
96+
function unifiedLoader(conf: Stage2Config) {
97+
const s2Loader = stage2Loader(conf);
98+
return async (specifier: string): Promise<LoadResponse | undefined> => {
99+
if (specifier === STAGE1_SPECIFIER) {
100+
return inlineTsMod(specifier, stage1());
101+
}
102+
return s2Loader(specifier);
103+
};
104+
}
105+
106+
async function main(allArgs: string[]) {
107+
// Specifics of how you obtain the list of handlers is up to you
108+
// e.g: conf/project files, FS walking, etc...
109+
const cwd = Deno.cwd();
110+
const stage2Config: Stage2Config = {
111+
paths: {
112+
foo: path.join(cwd, "handlers", "foo.ts"),
113+
bar: path.join(cwd, "handlers", "bar", "index.ts"),
114+
},
115+
urls: {
116+
"/foo": "foo",
117+
"/kungfu": "foo",
118+
"/bar": "bar",
119+
"/drink": "bar",
120+
},
121+
localSrcRoot: cwd,
122+
};
123+
124+
const [cmd, ...args] = allArgs;
125+
switch (cmd) {
126+
// Produces an ESZIP with only stage1
127+
case "stage1": {
128+
const [destPath] = args;
129+
const bytes = await build([STAGE1_SPECIFIER], stage1Loader());
130+
return await Deno.writeFile(destPath, bytes);
131+
}
132+
// Produces an ESZIP with only stage2
133+
case "stage2": {
134+
const [destPath] = args;
135+
const bytes = await build([STAGE2_SPECIFIER], stage2Loader(stage2Config));
136+
return await Deno.writeFile(destPath, bytes);
137+
}
138+
// Produces an ESZIP with stage1 & stage2
139+
case "unified": {
140+
const [destPath] = args;
141+
const bytes = await build(
142+
[STAGE1_SPECIFIER],
143+
unifiedLoader(stage2Config),
144+
);
145+
return await Deno.writeFile(destPath, bytes);
146+
}
147+
}
148+
}
149+
await main(Deno.args);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function add(x, y) {
2+
return x + y;
3+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { add } from "./helpers.js";
2+
3+
export default async (req: Request) => {
4+
return new Response(`bar: ${req.url} [${add(1, 2)}]`);
5+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export default async (req: Request) => {
2+
return new Response(`foo: ${req.url}`);
3+
};

0 commit comments

Comments
 (0)