|
// GIST: https://jsbin.com/gist/985fa2ef7681dfb48f7bce7b67fa0b18 |
|
// GIST: https://gist.github.com/SMotaal/985fa2ef7681dfb48f7bce7b67fa0b18 |
|
// LOCAL: https://jsbin.com/local/spring-night-2A7 |
|
(() => { |
|
console.clear(); |
|
|
|
/** @type {typeof global & typeof window} */ |
|
const scope = this |
|
|| typeof window === 'object' && window |
|
|| typeof global === 'object' && global |
|
|| typeof self === 'object' && self |
|
|| {}; |
|
|
|
//Logging |
|
const |
|
ENTRIES = new class Entries extends Array { }, |
|
ERRORS = new class Errors extends Array { }, |
|
MARK = (OP, ID, value) => |
|
ENTRIES.push({ name: `«${ID}» — ${OP}`, value }); |
|
|
|
// Executors |
|
const maybe = ƒ => { try { return ƒ() } catch (e) { } }; |
|
const noop = () => { }; |
|
|
|
// Formatters |
|
const reindent = ( |
|
string, indent = (/\n?( *)$/.exec(string))[1].length |
|
) => string.replace(new RegExp(`^ {0,${indent}}`, 'gm'), ''); |
|
|
|
(function (helpers) { |
|
|
|
const { |
|
runtime: { global, DynamicImport, RegisterModule }, |
|
performance: { START = noop, END = noop }, |
|
} = helpers; |
|
|
|
const ModuleSources = global['[[ModuleSources]]'] = {}; |
|
|
|
(() => { |
|
|
|
const origin = `x:/`; |
|
const normalize = (specifier, referrer) => { |
|
const base = referrer && ( |
|
referrer.includes(':/') && referrer || normalize(referrer, origin) |
|
) || origin; |
|
const reference = /^#|[.]{0,2}[/]|\w+:/.test(specifier) |
|
? specifier.replace(/^[/]{2,}/, '/') : `/#${specifier}`; |
|
return `${new URL(reference, base)}`; |
|
} |
|
const unnormalize = (url) => url.replace(/^x:(\/#|)/, ''); |
|
|
|
const ModuleLoader = global['[[ModuleLoader]]'] = { |
|
resolve: (specifier, referrer) => |
|
unnormalize(normalize(specifier, referrer)), |
|
|
|
load: async (specifier, referrer) => { |
|
const sourceURL = ModuleLoader.resolve(specifier, referrer) |
|
if (sourceURL in ModuleMap) |
|
return ModuleMap[sourceURL]; |
|
|
|
if (sourceURL in ModuleSources) |
|
return ModuleMap.define(ModuleSources[sourceURL], specifier, referrer); |
|
|
|
throw `Failed to load module from "${sourceURL}"`; |
|
}, |
|
|
|
import: async (specifier, referrer) => { |
|
return (await ModuleLoader.load(specifier, referrer)).namespace(); |
|
} |
|
}; |
|
|
|
const ValidModuleSpecifiers = /^((import(?= +\* +as \w+| +(?:\w+, *)?\{[\w\s,]*?\}| +\w+)|export(?= +\*| +\{[\w\s,]*?\})) +(.*?) +from +(['"]))(.*?)(\4)/gmsu; |
|
|
|
class Module { |
|
constructor(properties) { |
|
Object.assign(this, properties); |
|
} |
|
|
|
async link() { |
|
const sourceURL = this.sourceURL; |
|
const links = this.links; |
|
|
|
START('[3] link', sourceURL); |
|
|
|
let linkedSource = this.prelinkedSource; |
|
|
|
for (const link of links) { |
|
const requiredModule = ModuleMap[link.url]; |
|
linkedSource = linkedSource.replace(link.marker, `${link.head}${ |
|
requiredModule |
|
&& ( |
|
requiredModule.url || (await requiredModule.link()) |
|
) || await ( |
|
(await ModuleLoader.load(link.specifier, sourceURL)).link() |
|
) || link.specifier |
|
}${link.tail}`.padEnd(link.declaration.length)); |
|
} |
|
|
|
const url = this.url = RegisterModule(this.linkedSource = linkedSource, sourceURL); |
|
|
|
ModuleMap[sourceURL] = ModuleMap[url] = this; |
|
|
|
END('[3] link', sourceURL); |
|
|
|
return url; |
|
} |
|
|
|
async instantiate() { |
|
const sourceURL = this.sourceURL; |
|
const url = this.url || await this.link(); |
|
|
|
START('[4] instantiate', sourceURL); |
|
try { |
|
await (this.namespace = () => DynamicImport(url))(); |
|
} catch (exception) { |
|
this.error = exception; |
|
} |
|
END('[4] instantiate', sourceURL); |
|
|
|
return ModuleMap[sourceURL] = ModuleMap[url] = this; |
|
} |
|
|
|
async namespace() { |
|
return (await this.instantiate()).namespace(); |
|
} |
|
} |
|
|
|
const ModuleMap = global['[[ModuleMap]]'] = { |
|
|
|
define: (source, specifier, referrer = origin) => { |
|
|
|
// const mappedURL = normalize(specifier, referrer); |
|
// const sourceURL = unnormalize(mappedURL); |
|
const sourceURL = ModuleLoader.resolve(specifier, referrer); |
|
const baseURL = /^\w/.test(sourceURL) ? origin : sourceURL; |
|
|
|
if (sourceURL in ModuleMap) |
|
console.warn(`\n\nRedefining "${sourceURL}"!\n\n`); |
|
|
|
START('[1] define', sourceURL); |
|
|
|
const links = []; |
|
|
|
START('[2] prelink', sourceURL); |
|
|
|
const prelinkedSource = source.replace(ValidModuleSpecifiers, ( |
|
declaration, head, type, bindings, quote, specifier, tail |
|
) => { |
|
const marker = `««—${links.length}—${specifier}—»»`; |
|
links.push({ |
|
declaration, head, type, bindings, quote, specifier, tail, marker, |
|
url: ModuleLoader.resolve(specifier, baseURL) |
|
}); |
|
|
|
return marker; |
|
}); |
|
|
|
END('[2] prelink', sourceURL); |
|
|
|
const module = new Module({ |
|
source, sourceURL, links, prelinkedSource |
|
}); |
|
|
|
END('[1] define', sourceURL); |
|
return ModuleMap[sourceURL] = module; |
|
}, |
|
|
|
} |
|
|
|
return ModuleSources; |
|
})(); |
|
|
|
setTimeout(() => test({ global, timeline: ENTRIES }), 100); |
|
})({ |
|
runtime: RuntimeHelpers(), |
|
performance: PerformanceHelpers(), |
|
}); |
|
|
|
function RuntimeHelpers() { |
|
const |
|
JS = 'text/javascript', |
|
UID = () => ~~(1e6 * Math.random()), |
|
SRC = (uid = UID()) => |
|
`VirtualModule-${uid}`, |
|
SourceText = (source, url = SRC()) => |
|
`${source}\n\n//# sourceURL=${url}\n`; |
|
|
|
if (typeof window !== 'undefined' && ( |
|
typeof process === 'undefined' |
|
|| process.__nwjs |
|
)) { |
|
const importModule = async (url) => eval(`import("${url}")`); |
|
|
|
const |
|
SourceFile = (source, url = SRC(), type = JS) => |
|
new File([SourceText(source, url)], url, { type }), |
|
SourceFileURL = (source, url = SRC(), type = JS) => |
|
URL.createObjectURL(SourceFile(source, url, type)), |
|
RegisterModule = (source, sourceURL) => |
|
SourceFileURL(SourceText(source, sourceURL)), |
|
DynamicImport = (url) => importModule(url); |
|
|
|
return { global: window, RegisterModule, DynamicImport }; |
|
} |
|
|
|
if (typeof process !== 'undefined') { |
|
global.URL || (global.URL = require('url').URL); |
|
// const moduleHelpers = require('./node-vm-module-helpers'); |
|
// const moduleHelpers = require('./node-module-wrap-helpers'); |
|
const moduleHelpers = require('/Users/daflair/Projects/imaging-js/packages/kernel/node-module-wrap-helpers.js'); |
|
moduleHelpers.global = global; |
|
|
|
return moduleHelpers; |
|
} |
|
|
|
throw Error('Unsupported runtime'); |
|
} |
|
|
|
function PerformanceHelpers() { |
|
const { performance, PerformanceObserver } = |
|
(scope.PerformanceObserver && scope.performance) |
|
&& (!scope.process || !scope.process.moduleLoadList) |
|
&& scope || maybe(() => require('perf_hooks')) || {}; |
|
|
|
performance && PerformanceObserver && new PerformanceObserver( |
|
(list, observer) => ENTRIES.push(...list.getEntries()) |
|
).observe({ entryTypes: ['measure'] }); // console.log({ performance, scope }) |
|
|
|
return performance ? { |
|
performance, ENTRIES, MARK, |
|
START: (OP, ID) => |
|
performance.mark(`START: «${ID}» — ${OP}`), |
|
END: (OP, ID) => |
|
performance.measure( |
|
`«${ID}» — ${OP}`, `START: «${ID}» — ${OP}`, ( |
|
performance.mark(`END: «${ID}» — ${OP}`), |
|
`END: «${ID}» — ${OP}` |
|
)), |
|
} : { performance, ENTRIES, MARK }; |
|
} |
|
|
|
async function test({ |
|
global, |
|
ModuleLoader = global['[[ModuleLoader]]'], |
|
ModuleSources = global['[[ModuleSources]]'], |
|
records = new class Records { }, |
|
namespaces = new class Namespaces { }, |
|
timeline = [], |
|
sources = { |
|
['/modules/typescript']: reindent(` |
|
export const typescript = true; |
|
export default "TypeScript" |
|
`), |
|
['typescript']: reindent(` |
|
export * from '/modules/typescript'; |
|
export {\ndefault\n} from '/modules/typescript'; |
|
`), |
|
['/index']: reindent(` |
|
import ts from 'typescript'; // import ts, {a, b, c} from 'typescript'; |
|
export * from 'typescript'; |
|
export const index = true; |
|
export { ts }; |
|
`), |
|
['/tests/works']: reindent(` |
|
import * as ts1 from '../modules/typescript'; |
|
import * as ts2 from '//modules/x/../typescript'; |
|
import * as ts3 from 'typescript'; |
|
console.log('/tests/works', { |
|
'/modules/typescript': ts1, |
|
'//modules/x/../typescript': ts2, |
|
'typescript': ts3 |
|
}); |
|
`), |
|
['/tests/async/resolve']: reindent(` |
|
export function then(resolve, reject) { |
|
try { resolve([1, 2, 3, 'Resolved Module']) } |
|
catch (e) { console.warn('Thenable module not resolved!') } |
|
} |
|
`), |
|
['/tests/async/reject']: reindent(` |
|
export function then(resolve, reject) { |
|
try { reject('Rejected Module') } |
|
catch (e) { console.warn('Thenable module not rejected!') } |
|
} |
|
`), |
|
['/tests/fails']: `import x from 'xyz';`, |
|
['https://unpkg.com/lit-html@0.9.0/lit-html.js']: `export {};`, |
|
['lit-html']: `export * from 'https://unpkg.com/lit-html@0.9.0/lit-html.js';`, |
|
['/tests/deadlock/a']: `export * from './b';\nexport default 'A';\nconsole.log('deadlock a');`, |
|
['/tests/deadlock/b']: `export * from './a';\nexport default 'B';\nconsole.log('deadlock b');`, |
|
}, |
|
specifiers = Object.keys(sources), |
|
}) { |
|
Object.assign(ModuleSources, sources); |
|
for (const specifier of specifiers) { |
|
try { |
|
records[specifier] = await ModuleLoader.load(specifier); |
|
namespaces[specifier] = await ModuleLoader.import(specifier); |
|
} catch (exception) { |
|
namespaces[specifier] = exception; |
|
} |
|
} |
|
console.dir(timeline.reduce( |
|
(entries, { name, duration, value }) => (( |
|
entries[name] = value || duration || true |
|
), entries), { |
|
'[RECORDS]': records, |
|
'[NAMESPACES]': namespaces |
|
} |
|
)); |
|
} |
|
})() |