-
Notifications
You must be signed in to change notification settings - Fork 0
/
arenaless-rollup-plugin.ts
164 lines (157 loc) · 6.16 KB
/
arenaless-rollup-plugin.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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import { type Plugin, type ResolveIdResult } from "@rollup/browser";
import * as path from 'path-browserify';
import AliasesFromTSConfig from "./aliasesFromTSConfig";
import { Base64 } from 'js-base64';
const VFS_PREFIX = "\0fs:";
const specifiers = ["npm:", "jsr:", "http:", "https:"];
function hasSpecifiers(id: string) {
if (id.startsWith(VFS_PREFIX)) {
return false;
}
let regex = /^([a-zA-Z0-9_]+):(.+)$/;
return regex.test(id);
}
function resolveIdWithSpecifiers(id: string): ResolveIdResult {
let specifier = specifiers.find(specifier => id.startsWith(specifier));
if (specifier == null) {
throw Error(`Specifier ${specifier} not found`);
}
let content = id.replace(specifier, "");
if (specifier == "npm:") {
return { id: `https://esm.sh/${content}`, };
} else if (specifier == "jsr:") {
return { id: `https://esm.sh/jsr/${content}` };
} else if (specifier == "http:" || specifier == "https:") {
return { id: id };
}
}
function resolveVirtualFS(id: string, importer: string | undefined, modules: Record<string, any>, aliasmode: boolean = false) {
if (!importer) {
return `${VFS_PREFIX}${id}`;
}
const importerNoPrefix = importer.startsWith(VFS_PREFIX)
? importer.slice(VFS_PREFIX.length)
: importer;
if (id.startsWith("./")) {
id = id.slice(2);
}
let resolved = path.join(path.dirname(importerNoPrefix), id).replace(/\\/g, "/");
if (aliasmode) resolved = id;
if (resolved.startsWith("./")) {
resolved = resolved.slice(2);
}
if (resolved.endsWith(".js") || resolved.endsWith(".ts") || resolved.endsWith(".jsx") || resolved.endsWith(".tsx")) {
return VFS_PREFIX + resolved;
} else if (modules[resolved]) {
return VFS_PREFIX + resolved;
}
else if (modules[resolved + ".ts"]) {
return VFS_PREFIX + resolved + ".ts";
}
else if (modules[resolved + ".js"]) {
return VFS_PREFIX + resolved + ".js";
} else if (modules[resolved + ".jsx"]) {
return VFS_PREFIX + resolved + ".jsx";
}else if(modules[resolved + ".tsx"]){
return VFS_PREFIX + resolved + ".tsx";
} else {
throw Error(`File ${resolved} not found`);
}
}
class Cache {
cache: Record<string, any>;
max: number = 64;
constructor() {
this.cache = {};
}
get(key: string) {
return this.cache[key];
}
set(key: string, value: any) {
let keys = Object.keys(this.cache);
if (keys.length >= this.max) {
// delete one
delete this.cache[keys[0]];
}
this.cache[key] = value;
}
clear() {
this.cache = {};
}
}
export const alCache = new Cache();
export function arenaless(config: { modules_raw: Record<string, Uint8Array>, tsconfig?: any }): Plugin {
let modules: Record<string, string | { binary: boolean }> = {};
for (let key in config.modules_raw) {
// test if text can be decoded
let b64 = Base64.fromUint8Array(config.modules_raw[key]);
modules[`${key}?binary`] = `import {toByteArray as $arenaless_internel_base64ToUint8Array} from "https://esm.sh/base64-js@1.5.1";export default $arenaless_internel_base64ToUint8Array("${b64}");`;
modules[`${key}?base64`] = `export default "${b64}";`;
try {
let text = new TextDecoder("utf-8", { fatal: true }).decode(config.modules_raw[key]);
modules[key] = text;
modules[`${key}?text`] = `export default \`${text}\`;`;
} catch (e) {
modules[key] = { binary: true };
if (key.endsWith(".wasm")) modules[`${key}?wasm`] = `import {toByteArray as $arenaless_internel_base64ToUint8Array} from "https://esm.sh/base64-js@1.5.1";let buf=$arenaless_internel_base64ToUint8Array("${b64}");export default async()=>{let module=await WebAssembly.compile(buf);let instance=await WebAssembly.instantiate(module,{});return instance;}`
}
}
let aliasResolver = new AliasesFromTSConfig(JSON.stringify({ compilerOptions: config.tsconfig }));
return {
name: "arenaless",
resolveId(id, importer, options) {
if (hasSpecifiers(id)) {
return resolveIdWithSpecifiers(id);
}
if ((id.startsWith("/") || id.startsWith("./")) && importer && (importer.startsWith("http://") || importer.startsWith("https://")) && hasSpecifiers(importer!)) {
try {
new URL(id, importer);
return new URL(id, importer).href;
} catch {
}
}
// resolve alias
if (aliasResolver.hasAlias(id)) {
let newId = aliasResolver.apply(id);
// console.log(`alias ${id} -> ${newId}`)
if (hasSpecifiers(newId)) {
return resolveIdWithSpecifiers(newId);
}
return resolveVirtualFS(newId, importer, modules, true);
}
return resolveVirtualFS(id, importer, modules);
}, async load(id) {
// vfs
if (id.startsWith(VFS_PREFIX)) {
let file = id.slice(VFS_PREFIX.length);
let content = modules[file];
if (typeof content == "string") {
return content;
} else if (typeof content == "object" && content.binary) {
throw Error(`File ${file} is binary, if you need binary data, please import <path>?binary"`);
}
}
// url
if (id.startsWith("http://") || id.startsWith("https://")) {
// console.log("loading url",id)
let content = alCache.get(id);
if (content) {
return content;
}
content = await (await fetch(id)).text();
alCache.set(id, content);
return content;
}
}
}
}
export function jsonLoader(): Plugin {
return {
name: "jsonLoader",
transform(code, id) {
if (id.endsWith(".json")) {
return `export default ${JSON.stringify(JSON.parse(code))};`
}
},
}
}