forked from esm-dev/esm.sh
-
Notifications
You must be signed in to change notification settings - Fork 0
/
run.ts
87 lines (84 loc) · 2.54 KB
/
run.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/*! esm.sh/run
*
* Add `<script type="module" src="https://esm.sh/run"></script>` to run jsx/ts in browser without build step.
*
*/
const d = document;
const l = localStorage;
const stringify = JSON.stringify;
const modUrl = new URL(import.meta.url);
const MiB = 10 << 20;
const kRun = "esm.sh/run";
const kScript = "script";
const kImportmap = "importmap";
const loaders = ["js", "jsx", "ts", "tsx", "babel"];
const importMapSupported = HTMLScriptElement.supports?.(kImportmap);
const imports: Record<string, string> = {};
const scopes: Record<string, typeof imports> = {};
const runScripts: { loader: string; source: string }[] = [];
// lookup run scripts
d.querySelectorAll(kScript).forEach((el) => {
let loader: string | null = null;
if (el.type === kImportmap) {
const v = JSON.parse(el.innerHTML);
for (const k in v.imports) {
if (!importMapSupported || k === "@jsxImportSource") {
imports[k] = v.imports[k];
}
}
if (!importMapSupported) {
Object.assign(scopes, v.scopes);
}
} else if (el.type.startsWith("text/")) {
loader = el.type.slice(5);
if (loaders.includes(loader)) {
const source = el.innerHTML.trim();
if (source.length > MiB) {
throw new Error(kRun + " " + source.length + " bytes exceeded limit.");
}
runScripts.push({ loader, source });
}
}
});
// transform and insert run scripts
const importMap = stringify({ imports, scopes });
runScripts.forEach(async (input, idx) => {
const buffer = new Uint8Array(
await crypto.subtle.digest(
"SHA-1",
new TextEncoder().encode(
input.loader + input.source + importMap,
),
),
);
const hash = [...buffer].map((b) => b.toString(16).padStart(2, "0")).join("");
const jsCacheKey = kRun + ":" + idx;
const hashCacheKey = jsCacheKey + ".hash";
let js = l.getItem(jsCacheKey);
if (js && l.getItem(hashCacheKey) !== hash) {
js = null;
}
if (!js) {
const { origin } = modUrl;
const res = await fetch(origin + `/+${hash}.mjs`);
if (res.ok) {
js = await res.text();
} else {
const res = await fetch(origin + "/transform", {
method: "POST",
body: stringify({ ...input, importMap, hash }),
});
const { code, error } = await res.json();
if (error) {
throw new Error(kRun + " " + error.message);
}
js = code;
}
l.setItem(jsCacheKey, js!);
l.setItem(hashCacheKey, hash);
}
const script = d.createElement(kScript);
script.type = "module";
script.innerHTML = js!;
d.body.appendChild(script);
});