Files

106 lines
2.9 KiB
JavaScript

const {transformSync} = require('@babel/core')
const declare = require('@babel/helper-plugin-utils').declare
const syntaxJsxPlugin = require('@babel/plugin-syntax-jsx')
const proposalObjectRestSpreadPlugin = require('@babel/plugin-proposal-object-rest-spread')
class BabelPluginExtractImportsAndExports {
constructor() {
const nodes = []
this.state = {nodes}
this.plugin = declare(api => {
api.assertVersion(7)
return {
visitor: {
ExportDefaultDeclaration(path) {
const {start} = path.node
nodes.push({type: 'export', start, default: true})
},
ExportNamedDeclaration(path) {
const {start} = path.node
nodes.push({type: 'export', start})
},
ExportAllDeclaration(path) {
const {start} = path.node
nodes.push({type: 'export', start})
},
ImportDeclaration(path) {
const {start} = path.node
// Imports that are used in exports can end up as
// ImportDeclarations with no start/end metadata,
// these can be ignored
if (start === undefined) {
return
}
nodes.push({type: 'import', start})
}
}
}
})
}
}
const partitionString = (str, indices) =>
indices.map((val, i) => {
return str.slice(val, indices[i + 1])
})
module.exports = (value, vfile) => {
const instance = new BabelPluginExtractImportsAndExports()
transformSync(value, {
plugins: [syntaxJsxPlugin, proposalObjectRestSpreadPlugin, instance.plugin],
filename: vfile.path,
configFile: false,
babelrc: false
})
const sortedNodes = instance.state.nodes.sort((a, b) => a.start - b.start)
const nodeStarts = sortedNodes.map(n => n.start)
const values = partitionString(value, nodeStarts)
const allNodes = sortedNodes.map(({start: _, ...node}, i) => {
const value = values[i]
return {...node, value}
})
// Group adjacent nodes of the same type so that they can be combined
// into a single node later, this also ensures that order is preserved
let currType = allNodes[0].type
const groupedNodes = allNodes.reduce(
(acc, curr) => {
// Default export nodes shouldn't be grouped with other exports
// because they're handled specially by MDX
if (curr.default) {
currType = 'default'
return [...acc, [curr]]
}
if (curr.type === currType) {
const lastNodes = acc.pop()
return [...acc, [...lastNodes, curr]]
}
currType = curr.type
return [...acc, [curr]]
},
[[]]
)
// Combine adjacent nodes into a single node
return groupedNodes
.filter(a => a.length)
.reduce((acc, curr) => {
const node = curr.reduce((acc, curr) => ({
...acc,
value: acc.value + curr.value
}))
return [...acc, node]
}, [])
}