Last active
October 25, 2017 05:36
-
-
Save bdwain/47a0f5df3c6eb4712f8bd4aa07955f77 to your computer and use it in GitHub Desktop.
Reorganize exports rollup plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { parse } = require('acorn'); | |
const MagicString = require('magic-string'); | |
//this parses the returned source code and removes all functions from the main export {...}; | |
//it then changes those functions from `function foo(){...}` to `export function foo(){...}` | |
//if any variables in any file are named the same as an exported function, this will error out. | |
//A full solution would need to be included as part of rollup. https://github.com/rollup/rollup/issues/1682 | |
function plugin(){ | |
return { | |
transformBundle: function(code){ | |
let ast = parse(code, { | |
ecmaVersion: 6, | |
sourceType: 'module' | |
}); | |
let namedExports = [], namedExportsStart, namedExportsEnd; | |
//map of all functions declared at the top-level scope of the file (as opposed to other types of variables). | |
//value will includes start and end position of each, so that "export " can be prepended | |
let allFunctionDeclarations = {}; | |
ast.body.forEach(node => { | |
if(node.type === 'FunctionDeclaration'){ | |
allFunctionDeclarations[node.id.name] = { | |
start: node.start, | |
end: node.end | |
}; | |
return; | |
} | |
else if (node.type !== 'ExportNamedDeclaration') { | |
return; | |
} | |
if (node.declaration) { //?? should never happen | |
console.warn('extra named export found'); | |
return; | |
} | |
//node now represents the statement export {...}; | |
//there should only be one of these in the rollup generated bundle | |
namedExportsStart = node.start; | |
namedExportsEnd = node.end; | |
namedExports = node.specifiers.map(s => ({local: s.local.name, exported: s.exported.name})); | |
}); | |
const functionNames = Object.keys(allFunctionDeclarations); | |
let remainingExports = namedExports.filter(x => !functionNames.includes(x.local)); | |
let functionExports = namedExports.filter(x => functionNames.includes(x.local)); | |
let magicStr = new MagicString(code.toString()); | |
//recreate the main export statement without the functions in it | |
let newNamedExportStr = 'export {'; | |
remainingExports.forEach(x => { | |
if(x.local === x.exported){ | |
newNamedExportStr += `${x.local}, ` | |
} | |
else{ | |
newNamedExportStr += `${x.local} as ${x.exported}, `; | |
} | |
}); | |
//remove the trailing comma and close the statement | |
newNamedExportStr = newNamedExportStr.substring(0, newNamedExportStr.length - 2) + '};'; | |
magicStr.overwrite(namedExportsStart, namedExportsEnd, newNamedExportStr); | |
//add the exports for each function | |
functionExports.forEach(x => { | |
if(x.exported !== x.local){ //if function foo was exported like "bar as foo", there was a conflict, which is not supported | |
throw new Error(`ERROR with ${x.exported}: exported function names must be unique across the entire library, | |
even in other files (not tests though). Their declarations must also match their eventual export names. Please rename local variables | |
named ${x.exported}. See https://github.com/rollup/rollup/issues/1682 for more info.`); | |
} | |
magicStr.prependLeft(allFunctionDeclarations[x.local].start, 'export '); | |
}); | |
return {code: magicStr.toString()}; //ignoring source maps for this | |
} | |
}; | |
} | |
module.exports = plugin; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment