parse-imports-exports
parse-imports-exports scans a JavaScript or TypeScript source string and returns a compact inventory of its module boundary. One function identifies static imports, dynamic imports with literal paths, require calls, reexports, ESM declarations, TypeScript type-only forms, interfaces, namespaces, and CommonJS assignments. Results are grouped by syntax kind and module specifier, with source offsets and optional line-column positions. It is designed for dependency analysis and code tooling that does not need a complete syntax tree or evaluation.
A useful specialized scanner when input is valid and the exact import/export categories match your tooling job. Choose a full parser when malformed code, transformations, resolution, or unfamiliar syntax must be handled correctly.
Use it if
- You need a quick dependency inventory from already valid, consistently formatted JavaScript or TypeScript
- Your tool must distinguish value imports, type-only imports, dynamic imports, require calls, reexports, and CommonJS assignments
- You need source ranges for module statements but do not want the memory and traversal cost of a full AST
- You ship for both ESM and CommonJS and want included TypeScript declarations from one small API
- You must accept arbitrary or half-edited source: the README explicitly says the parser works on syntactically correct, well-formatted code such as Prettier output
- You need a full AST for transformations, scope analysis, comments outside module statements, or expression semantics: this API returns only categorized import and export metadata
- You need module resolution: results contain raw specifiers such as ./foo or react, but the package does not apply Node exports rules, aliases, tsconfig paths, extensions, or filesystem lookup
- You need a settled public contract: the current release is 0.2.4, and the result type exposes many syntax-specific optional properties whose shape can still change before 1.0
- You cannot restrict source syntax to ES2018-capable runtimes: the README says the implementation uses named RegExp capture groups, and it only recognizes dynamic import targets that are string literals rather than computed expressions
Setup reality
Install the package and import the single parseImportsExports function. Version 0.2.4 declares ESM internally but publishes conditional import and require entry points, so both import {parseImportsExports} from 'parse-imports-exports' and const {parseImportsExports} = require('parse-imports-exports') are supported. Types are included. There is one runtime dependency, parse-statements, pinned exactly to 1.0.11. The difficult part is not installation but consuming the result correctly. The function does not return one flat dependency list. It returns separate optional maps for namedImports, namespaceImports, dynamicImports, requires, typeNamedImports, typeNamespaceImports, typeDynamicImports, six reexport groups, and several local export groups. A missing group is undefined, not an empty object. Each module path maps to a non-empty array because the same specifier can occur more than once. Aliases are keyed by the local or exported name, with by holding the original name. Source start and end offsets are always present; includeLineColumn adds strings such as 3:12 at extra processing cost. The parser records syntax problems in errors rather than promising a complete result from malformed input, so decide whether any error invalidates your analysis. Its own README requires syntactically correct, well-formatted input, making a Prettier or compiler pass a sensible upstream boundary. Options can skip CommonJS exports, dynamic imports, regular-expression literals, require calls, or string-literal shielding for speed. The last two ignore options can create false positives if source text contains import-like tokens inside skipped literals, so enable them only when the input grammar makes that safe. This tool also stops at syntax discovery: feed raw specifiers into a separate resolver if you need actual files, package export conditions, tsconfig paths, or a dependency graph.
Patterns
Parse imports and exportsparse-module-boundary
import {parseImportsExports} from 'parse-imports-exports'
const result = parseImportsExports(`
import {readFile as read} from 'node:fs/promises'
export const load = (path) => read(path, 'utf8')
`)
console.log(result.namedImports, result.declarationExports)Every result group is optional and absent groups are undefined. Treat result.errors as a failed or partial analysis according to your tool's policy.
List static import specifierslist-static-dependencies
const parsed = parseImportsExports(source)
const staticPaths = new Set([
...Object.keys(parsed.namedImports ?? {}),
...Object.keys(parsed.namespaceImports ?? {}),
...Object.keys(parsed.typeNamedImports ?? {}),
...Object.keys(parsed.typeNamespaceImports ?? {})
])
console.log([...staticPaths])This includes type-only dependencies. Side-effect imports are represented in namedImports with position data but no default or names fields.
Read named imports and aliasesinspect-import-aliases
const parsed = parseImportsExports(
"import {readFile as read, writeFile} from 'node:fs/promises'"
)
const entry = parsed.namedImports?.['node:fs/promises']?.[0]
for (const [localName, details] of Object.entries(entry?.names ?? {})) {
console.log({localName, importedName: details.by ?? localName})
}The names object is keyed by the local binding. An alias stores its original imported name in by.
Inspect default and namespace importsinspect-default-namespace
const parsed = parseImportsExports(
"import React, * as ReactNS from 'react'"
)
const entry = parsed.namespaceImports?.react?.[0]
console.log(entry?.default, entry?.namespace)Imports containing * as are placed in namespaceImports. A plain default import without a namespace is placed in namedImports.
Find dynamic imports and require callslist-runtime-loads
const parsed = parseImportsExports(source)
const lazyEsm = Object.keys(parsed.dynamicImports ?? {})
const commonJs = Object.keys(parsed.requires ?? {})
console.log({lazyEsm, commonJs})The scanner records literal module paths. Computed expressions such as import(prefix + name) cannot become a concrete path entry.
Collect all reexport sourceslist-reexports
const parsed = parseImportsExports(source)
const groups = [
parsed.namedReexports, parsed.namespaceReexports, parsed.starReexports,
parsed.typeNamedReexports, parsed.typeNamespaceReexports, parsed.typeStarReexports
]
const sources = new Set(groups.flatMap((group) => Object.keys(group ?? {})))
console.log([...sources])Reexports are separate from imports because they affect a package's public surface even when no local binding is created.
List exported runtime declarationslist-declaration-exports
const parsed = parseImportsExports(`
export const port = 3000
export async function start() {}
export class Server {}
`)
for (const [name, item] of Object.entries(parsed.declarationExports ?? {})) {
console.log(name, item.kind)
}kind distinguishes const, class, async function, generators, enums, destructuring, declare forms, and other supported declaration shapes.
Separate TypeScript type exportsinspect-type-exports
const parsed = parseImportsExports(`
export type ID = string
export interface User { id: ID }
export namespace Models {}
`)
console.log({
aliases: Object.keys(parsed.typeExports ?? {}),
interfaces: Object.keys(parsed.interfaceExports ?? {}),
namespaces: Object.keys(parsed.namespaceExports ?? {})
})These groups describe declarations only. The package does not parse members or resolve relationships between the declared types.
Detect CommonJS assignmentsinspect-commonjs-exports
const parsed = parseImportsExports(`
module.exports = createApp()
module.exports.version = '1.0.0'
`)
console.log(parsed.commonJsNamespaceExport)
console.log(parsed.commonJsExports?.version)module.exports = value and property assignments use separate fields. This is syntax detection, not Node runtime evaluation.
Add line and column positionsinclude-line-columns
const parsed = parseImportsExports(source, {includeLineColumn: true})
const first = parsed.namedImports?.react?.[0]
console.log(first?.startLineColumn, first?.endLineColumn)Line-column strings are optional and requested explicitly. Numeric start and end offsets remain available on every recognized statement.
Recover the original statement textslice-source-statement
const parsed = parseImportsExports(source)
const item = parsed.namedImports?.react?.[0]
if (item) {
console.log(source.slice(item.start, item.end))
}Keep the original source beside the result. Positions are metadata; the parser does not copy full statement text into each entry.
Skip syntax categories you do not needdisable-unneeded-scans
const parsed = parseImportsExports(source, {
ignoreCommonJsExports: true,
ignoreDynamicImports: true,
ignoreRequires: true
})These flags can reduce work and intentionally remove result groups. Be cautious with ignoreStringLiterals or ignoreRegexpLiterals because import-like text inside skipped literals may be misread.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| es-module-lexer | npm | You need a widely used low-level ESM lexer with exact import slices and are happy to handle a terser output format |
| @babel/parser | npm | You need a complete JavaScript or TypeScript AST for transformations, syntax recovery, scopes, and plugin syntax |
| acorn | npm | You want a standards-focused JavaScript parser with a full ESTree-compatible AST and a plugin ecosystem |
| cjs-module-lexer | npm | Your main problem is fast CommonJS export and reexport detection rather than combined TypeScript and ESM reporting |