-
Notifications
You must be signed in to change notification settings - Fork 0
/
dynamicImport.mjs
84 lines (77 loc) · 3.33 KB
/
dynamicImport.mjs
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
export default async function dynamicImport(specifier, referrer) {
if (!('resolve' in dynamicImport)) {
const {assign, create, defineProperties, freeze, getOwnPropertyDescriptors, setPrototypeOf} = Object;
let base;
const resolve = freeze((specifier, referrer) => `${new URL(specifier, referrer || base || import.meta.url)}`);
const properties = {resolve};
try {
properties.import = freeze((1, eval)(`url => import(url)`));
properties.export = freeze(() => {
throw Error(`Invalid invokation of dynamicImport.exports()`);
});
} catch (exception) {
const reflect = freeze(getOwnPropertyDescriptors.bind(null));
const createElement = document.createElement.bind(document);
const insertElement = document.appendChild.bind(document.head);
const records = {};
const resolvers = {};
const binders = new WeakMap();
const proxy = new Proxy(freeze(create(null)), {
get: (target, property, receiver) => {
const binder = binders.get(receiver);
if (binder && binders.delete(setPrototypeOf(receiver, null)))
return freeze(defineProperties(receiver, {...binder(reflect), ...reflect(receiver)}))[property];
},
});
const Import = class Import {
constructor(url) {
const {wrap = Import.wrap, inject = Import.inject} = new.target;
this.url = url;
this.namespace = create(proxy, {
[Symbol.toStringTag]: {value: `Module‹${url}›`},
});
this.source = wrap(url, (this.binder = `reflect => reflect(namespace)`), (this.loader = import.meta.url));
this.promise = inject(this);
freeze(this);
}
static wrap(url, binder, loader) {
return `import * as namespace from "${url}";\nimport loader from "${loader}";\nloader.export("${url}", ${binder});`;
}
static inject({url, source: textContent, namespace, crossOrigin = 'anonymous', type = 'module'}) {
let timeout;
const script = assign(createElement('script'), {type: 'module', crossOrigin, textContent});
const promise = new Promise(
resolve => (timeout = setTimeout((script.onerror = resolvers[url] = resolve), 5000, {type: 'timeout'})),
).then(event => {
resolvers[url] = script.onerror = void clearTimeout(timeout);
if (!event || event.type === 'load') return namespace;
throw Error(`Failed to load "${url}"`);
});
insertElement(script).remove();
return promise;
}
};
freeze(freeze(Import.prototype).constructor);
properties.import = freeze(url => {
const {[url]: record = (records[url] = new Import(resolve(url)))} = records;
return record.promise;
});
properties.export = freeze((url, binder) => {
const {[url]: record} = records;
if (!record || typeof binder !== 'function' || `${binder}` !== record.binder || binders.has(record.namesapce))
throw Error(`Invalid invokation of dynamicImport.exports()`);
binders.set(record.namespace, binder);
resolvers[url] && resolvers[url]();
});
{
const global = (typeof globalThis === 'object' && globalThis) || (1, eval)('this');
!global || 'dynamicImport' in global || (global.dynamicImport = dynamicImport);
}
}
defineProperties(dynamicImport, {
...getOwnPropertyDescriptors(freeze(properties)),
base: {get: () => base, set: value => (base = `${new URL(value)}`)},
});
}
return dynamicImport.import(dynamicImport.resolve(specifier, referrer));
}