unbundle
Version:
import/export & node_modules in the browser, without the bundling
51 lines (50 loc) • 2.22 kB
JavaScript
const { readFileSync } = require('fs')
const { dirname, relative, resolve } = require('path')
const { parse, visit, types, print } = require('recast')
const { sync: resolveJS } = require('resolve')
const babelParser = require('recast/parsers/babel')
const sourcemapRegex = /\/\/[#@] sourceMappingURL=(?!\w+:)([^\s]+)$/
module.exports = (entry, { recurse = true, root = '/', verbose = false } = {}) => {
return process(resolve(entry), new Map())
function relocate (importPath, source) {
const dependency = resolveJS(importPath, {
extensions: ['.mjs', '.js'],
basedir: dirname(source),
packageFilter: (pkg) => ({
main: pkg.module || pkg['jsnext:main'] || pkg.main
})
})
const relocated = /^(\.|\/|https?:\/\/)/.test(importPath)
? './' + relative(dirname(source), dependency) // File or HTTP
: root + dependency.substr(dependency.indexOf('node_modules')) // NPM
return { dependency, relocated }
}
function process (source, processed) {
if (verbose) console.log(`Processing: ${relative('', source)}`)
if (processed.has(source)) return processed
else processed.set(source)
const to = source.includes('node_modules')
? source.substr(source.indexOf('node_modules'))
: relative(dirname(entry), source)
const raw = readFileSync(source)
const map = sourcemapRegex.test(raw) && decodeURI(RegExp.$1)
if (verbose && map) console.log(`Source map: ${map}`)
const ast = parse(raw, { parser: babelParser })
visit(ast, {
visitImport (path) { visitor.call(this, path, path.parent.node.arguments, 0) },
visitImportDeclaration: visitor,
visitExportNamedDeclaration: visitor,
visitExportAllDeclaration: visitor
})
function visitor (path, parent = path.node, node = 'source') {
if (parent[node] && parent[node].type === 'StringLiteral') {
const { dependency, relocated } = relocate(parent[node].value, source)
parent[node] = types.builders.literal(relocated)
if (recurse) process(dependency, processed)
}
this.traverse(path)
}
const { code } = print(ast, { quote: 'single' })
return processed.set(source, { code, map, to, from: source })
}
}