Is there relatively simple way to find all exported names from JavaScript text

217 views Asked by At

So let's say we have some JavaScript es module as text

const ESMText = "export const answer = 42;"

I checked ECMAScript documents, and logic for export is quite complicated, is there kinda simple way to finde out all exports without executing it or implementing almost full spec lexical analysis stuff (tokenizer, etc...) ?


It should be done in browser using JavaScript (maybe wasm)


Should be mentioned: obviously parse file using existing parser is the right answer, but whole point of question is to find kinda tricky way to do it


it could be easy done with regex, but there is two edge cases that i can't handle:

// destructuring assignment
export const { name1, name2: bar } = o;
export const [ name1, name2 ] = array;

//simultaneous declaration and assignment
export const name1 = 1, name2 = 2/*, … */;

may be someone will figure out something of it

3

There are 3 answers

0
vanilla On BEST ANSWER

at the end, i did it with acorn (inside worker):

self.importScripts("https://cdnjs.cloudflare.com/ajax/libs/acorn/8.8.2/acorn.min.js");
const exp = new RegExp("export", "g");

const ESMText = "export const answer = 42; const innerVar = 'str'"

for (const match of ESMText.matchAll(exp)) {
    const parser = new acorn.Parser({
        ecmaVersion: 2020,
        sourceType: "module"
    }, text, match.index);
    parser.nextToken();
    const statement = parser.parseStatement(true, true, {});
    console.log(statement);
}

i checked acorn docs and its implementation, after that i find out how to solve my problem with pretty god performance.
in the code abow parsed only export statements

2
Josh Kelley On

There are many JavaScript parsers: acorn, esprima, Babel, etc. Your best bet is probably to use one of them.

https://astexplorer.net/ lets you experiment with different parsers to see the abstract syntax trees that they generate.

9
Ali Safari On

Yes, you may use a js parser like esprima and extract the exported names.

like the example below:

const esprima = require('esprima');

function getExportedNames(jsCode) {
  const parsedCode = esprima.parseScript(jsCode, { sourceType: 'module' });

  const exportedNames = parsedCode.body.filter(node => node.type === 'ExportNamedDeclaration')
    .map(exportNode => exportNode.specifiers.map(spec => spec.exported.name))
    .reduce((acc, curr) => acc.concat(curr), []);

  return exportedNames;
}

const ESMText = "export const answer = 42;export function foo() {}"
const exportedNames = getExportedNames(ESMText);
console.log(exportedNames); // output:  ['answer', 'foo']

Update: referring to the discussion below you may use flatMap(), instead of using reduce() method.

const flattened = arr.flatMap(x => x);

instead of:

const flattened = arr.reduce((acc, curr) => acc.concat(curr), []);