-
Notifications
You must be signed in to change notification settings - Fork 2
/
index.ts
104 lines (91 loc) · 4.12 KB
/
index.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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import type {
Loader,
OnLoadArgs,
OnLoadResult,
OnResolveArgs,
Plugin
} from "https://deno.land/x/esbuild/mod.d.ts";
const namespace = "http-import";
const possibleLoaders: Loader[] = [ 'js', 'jsx', 'ts', 'tsx', 'css', 'json', 'text', 'base64', 'file', 'dataurl', 'binary', 'default' ];
const binaryLoaders: Loader[] = [ 'binary', 'file', "dataurl" ];
const responseCache: { [ path in string ]: Response } = {}
export type Options = {
allowPrivateModules?: boolean;
defaultToJavascriptIfNothingElseFound?: boolean;
disableCache?: boolean;
onCacheMiss?: (path: string) => void;
onCacheHit?: (path: string) => void;
};
export const httpImports = (options: Options = {}): Plugin => ({
name: namespace,
setup(build) {
build.onResolve({ filter: /^https:\/\// }, ({ path }: OnResolveArgs) => ({ path, namespace }));
build.onResolve({ filter: /.*/, namespace }, ({ path, importer }: OnResolveArgs) => ({ path: new URL(path.replace(/\?.*/, ""), importer).toString(), namespace }));
build.onLoad({ filter: /.*/, namespace }, async ({ path }: OnLoadArgs): Promise<OnLoadResult> => {
const headers = new Headers();
if (options.allowPrivateModules) appendAuthHeaderFromPrivateModules(path, headers);
const source = await useResponseCacheElseLoad(options, path, headers);
if (!source.ok) throw new Error(`GET ${path} failed: status ${source.status}`);
let contents = await source.clone().text();
contents = await handeSourceMaps(contents, source, headers);
const { pathname } = new URL(path);
// Find perfect Loader for extension
const loader = build.initialOptions.loader?.[ `.${pathname.split(".").at(-1)}` ] ?? (pathname.match(/[^.]+$/)?.[ 0 ]) as (Loader | undefined);
return {
contents: binaryLoaders.includes(loader ?? "default")
? new Uint8Array(await source.clone().arrayBuffer())
: contents,
loader: options.defaultToJavascriptIfNothingElseFound
? (loader && possibleLoaders.includes(loader)
? loader
: "js")
: loader
};
});
}
})
const loadMap = async (url: URL, headers: Headers) => {
const map = await fetch(url.href, { headers });
const type = map.headers.get("content-type")?.replace(/\s/g, "");
const buffer = await map.arrayBuffer();
const blob = new Blob([ buffer ], { type });
const reader = new FileReader();
return new Promise((cb) => {
reader.onload = (e) => cb(e.target?.result);
reader.readAsDataURL(blob);
});
};
async function useResponseCacheElseLoad(options: Options, path: string, headers: Headers): Promise<Response> {
if (responseCache[ path ] && !options.disableCache) {
options.onCacheHit?.(path);
return responseCache[ path ];
}
options.onCacheMiss?.(path);
return responseCache[ path ] = await fetch(path.split("?")[ 0 ], { headers });
}
async function handeSourceMaps(contents: string, source: Response, headers: Headers) {
const pattern = /\/\/# sourceMappingURL=(\S+)/;
const match = contents.match(pattern);
if (match) {
const url = new URL(match[ 1 ], source.url);
const dataurl = await loadMap(url, headers);
const comment = `//# sourceMappingURL=${dataurl}`;
return contents.replace(pattern, comment);
}
return contents;
}
function appendAuthHeaderFromPrivateModules(path: string, headers: Headers) {
const env = Deno.env.get("DENO_AUTH_TOKENS")?.trim();
if (!env) return;
try {
const denoAuthToken = env.split(";").find(x => new URL(`https://${x.split("@").at(-1)!}`).hostname == new URL(path).hostname);
if (!denoAuthToken) return;
if (denoAuthToken.includes(":"))
headers.append("Authorization", `Basic ${btoa(denoAuthToken.split('@')[ 0 ])}`);
else
headers.append("Authorization", `Bearer ${denoAuthToken.split('@')[ 0 ]}`);
} catch (error) {
console.log(error, env);
return;
}
}