Public
Edited
Apr 21, 2020
Importers
5 stars
Insert cell
Insert cell
Insert cell
fetchText = async (url, ...args) => {
const req = await fetch(url, ...args)
if (req.status >= 400) {
throw new TypeError(`Error fetching "${url}": Server responses with status code ${req.status}.`)
}
return await req.text()
}
Insert cell
toModuleStub = source => {
const ast = acorn.Parser.parse(source, {
sourceType: 'module'
})
const depDefs = []
// dep name to list of nodes
const depToNodes = new Map()

acornWalk.simple(ast, {
ImportDeclaration(node) {
depDefs.push(node)
},
ExportNamedDeclaration(node) {
if (node.source) {
depDefs.push(node)
}
}
})
const dependencies = new Set(depDefs.map(node => {
const name = node.source.value
if (!depToNodes.has(name)) {
depToNodes.set(name, [])
}
depToNodes.get(name).push(node)
return node.source.value
}))
const stub = {
dependencies,
_dep2url: null,
url: null,
injectDeps(_mapping) {
if (this.url != null) {
throw TypeError('Cannot inject dependencies to a resolved module stub.')
}
const mapping = (_mapping instanceof Map) ? _mapping : new Map(Object.entries(_mapping))
this._dep2url = mapping
},
toObjectURL() {
if (this.url != null) {
return this.url
}
const unresolvedNames = []

for (const [name, nodes] of depToNodes) {
if (!this._dep2url.has(name) || !this._dep2url.get(name)) {
unresolvedNames.push(name)
continue;
}

const lit = JSON.stringify(this._dep2url.get(name))

nodes.forEach(node => {
node.source.raw = lit
})
}

if (unresolvedNames.length) {
const err = TypeError(`Cannot resolve following imports from the provided mapping object: [${unresolvedNames.join(', ')}]`)
err.unresolvedNames = unresolvedNames
throw err
}

// TODO: generate source map
const source = astring.generate(ast)

const blob = new Blob([source], {
type: 'text/javascript'
})
const blobUrl = URL.createObjectURL(blob)

return this.url = blobUrl
},
}
return stub
}
Insert cell
registry = new Map()
Insert cell
importRecursive = async (rootModuleUrl, resolver) => {
async function _recursive(_path, base) {
// TODO: url normalization &/or dedupe
let path = resolver(_path, base)
if (!path) {
path = _path
}
let promiseToStub
if (registry.has(path)) {
promiseToStub = registry.get(path)
} else {
promiseToStub = fetchText(path).then(toModuleStub)
registry.set(path, promiseToStub)
}
const stub = await promiseToStub
if (stub.url != null) {
return stub.url
}
// micro optimization to prevent creating unnesseary blob urls
if (!stub.dependencies.size) {
return path
}
// critical section...
// bad smell... things would have been a lot easier if there were mutexes
if (!stub._resolving) {
stub._resolving = (async () => {
const depsPromises = Array.from(stub.dependencies).map(async d => [d, await _recursive(d, path)])
const depsMapping = await Promise.all(depsPromises)
// we are all set; get the final URL
stub.injectDeps(new Map(depsMapping))
return stub.toObjectURL()
})()
}
return await stub._resolving
}
return import(await _recursive(rootModuleUrl, rootModuleUrl))
}
Insert cell
adhocResolverFromUrls = (knownUrlsMap, suffix) => {
return (path, base) => {
if (knownUrlsMap.has(path)) {
return knownUrlsMap.get(path)
}
if (path.startsWith('.')) {
// relative path trick
return new URL(path + suffix, base).href
}
if (path.indexOf('//') <= 0) {
// maybe it is a module name on npm
return resolve(path + '?module')
}
return null
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more