toModuleStub = source => {
const ast = acorn.Parser.parse(source, {
sourceType: 'module'
})
const depDefs = []
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
}
const source = astring.generate(ast)
const blob = new Blob([source], {
type: 'text/javascript'
})
const blobUrl = URL.createObjectURL(blob)
return this.url = blobUrl
},
}
return stub
}