Skip to content

Commit

Permalink
feat: export analyzes with findExports (#8)
Browse files Browse the repository at this point in the history
Co-authored-by: Pooya Parsa <pyapar@gmail.com>
  • Loading branch information
antfu and pi0 committed Oct 14, 2021
1 parent 58e0652 commit 2eebbd5
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 8 deletions.
41 changes: 33 additions & 8 deletions README.md
Expand Up @@ -40,12 +40,13 @@ While ESM Modules are evolving in Node.js ecosystem, there are still many requir
- Stack-trace support
- `.json` loader
- Multiple composable module utils exposed
- Static import analyzes
- Static import and export analyzes
- Super fast Regex based implementation
- Handle most of edge cases
- Find all static ESM imports
- Find all dynamic ESM imports
- Parse static import statement
- Find all named, declared and default exports


## CommonJS Context
Expand Down Expand Up @@ -250,17 +251,41 @@ const foo = await import('bar')
`))
```
### `findExports`
**Note:** API Of this function might be broken in a breaking change for code matcher
```js
import { findExports } from 'mlly'

console.log(findExports(`
export const foo = 'bar'
export { bar, baz }
export default something
`))
```
Outputs:
```js
[
{
type: 'dynamic',
expression: "'bar'",
code: "import('bar')",
start: 19,
end: 32
}
type: 'declaration',
declaration: 'const',
name: 'foo',
code: 'export const foo',
start: 1,
end: 17
},
{
type: 'named',
exports: ' bar, baz ',
code: 'export { bar, baz }',
start: 26,
end: 45,
names: [ 'bar', 'baz' ]
},
{ type: 'default', code: 'export default ', start: 46, end: 61 }
]
```
Expand Down Expand Up @@ -323,7 +348,7 @@ Return the default export of a module at the top-level, alongside any other name
```js
// Assuming the shape { default: { foo: 'bar' }, baz: 'qux' }
import myModule from 'my-module'

// Returns { foo: 'bar', baz: 'qux' }
console.log(interopDefault(myModule))
```
Expand Down
1 change: 1 addition & 0 deletions lib/index.d.ts
Expand Up @@ -51,6 +51,7 @@ export interface DynamicImport extends ESMImport {

export function findStaticImports (code: string) : StaticImport[]
export function findDynamicImports (code: string) : DynamicImport[]
export function findNamedExports (code:string): string[]
export function parseStaticImport (staticImport: StaticImport) : ParsedStaticImport

// Evaluate
Expand Down
17 changes: 17 additions & 0 deletions lib/index.mjs
Expand Up @@ -201,6 +201,10 @@ export async function resolveImports (code, opts) {
export const ESM_STATIC_IMPORT_RE = /^(?<=\s*)import\s*(["'\s]*(?<imports>[\w*${}\n\r\t, /]+)from\s*)?["']\s*(?<specifier>.*[@\w_-]+)\s*["'][^\n]*$/gm
export const DYNAMIC_IMPORT_RE = /import\s*\((?<expression>(?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gm

const EXPORT_DECAL_RE = /\bexport\s+(?<declaration>(function|let|const|var|class))\s+(?<name>[\w$_]+)/g
const EXPORT_NAMED_RE = /\bexport\s+{(?<exports>[^}]+)}/g
const EXPORT_DEFAULT_RE = /\bexport\s+default\s+/g

function _matchAll (regex, string, addition) {
const matches = []
for (const match of string.matchAll(regex)) {
Expand Down Expand Up @@ -247,6 +251,19 @@ export function parseStaticImport (matched) {
}
}

export function findExports (code) {
const declaredExports = _matchAll(EXPORT_DECAL_RE, code, { type: 'declaration' })

const namedExports = _matchAll(EXPORT_NAMED_RE, code, { type: 'named' })
for (const namedExport of namedExports) {
namedExport.names = namedExport.exports.split(/\s*,\s*/g).map(name => name.replace(/^.*?\sas\s/, '').trim())
}

const defaultExport = _matchAll(EXPORT_DEFAULT_RE, code, { type: 'default' })

return [].concat(declaredExports, namedExports, defaultExport)
}

// Utils

export function fileURLToPath (id) {
Expand Down
30 changes: 30 additions & 0 deletions test/exports.test.mjs
@@ -0,0 +1,30 @@
import { expect } from 'chai'
import { findExports } from '../lib/index.mjs'

describe('findExports', () => {
const tests = {
'export function useA () { return \'a\' }': { name: 'useA', type: 'declaration' },
'export const useD = () => { return \'d\' }': { name: 'useD', type: 'declaration' },
'export { useB, _useC as useC }': { names: ['useB', 'useC'], type: 'named' },
'export default foo': { type: 'default' }
}

describe('findExports', () => {
for (const [input, test] of Object.entries(tests)) {
it(input.replace(/\n/g, '\\n'), () => {
const matches = findExports(input)
expect(matches.length).to.equal(1)
const match = matches[0]
if (test.type) {
expect(match.type).to.eql(test.type)
}
if (test.name) {
expect(match.name).to.eql(test.name)
}
if (test.names) {
expect(match.names).to.deep.eql(test.names)
}
})
}
})
})

0 comments on commit 2eebbd5

Please sign in to comment.