npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

esm-analyzer

v0.3.6

Published

The scanner and analyzer of ESM.

Downloads

4

Readme

esm-analyzer

The scanner and analyzer of ESM.

Installation

pnpm i esm-analyzer

Scanner

The scanner uses @babel/parser to parse the source code and find the import and export statements.

import { scan } from 'esm-analyzer'

const { imports, exports } = scan(sourceCode, lang)

lang

The lang parameter is used to specify the language of the source code.

It can be one of the following values:

  • js
  • jsx
  • ts
  • tsx

imports scanner

cases

  • ✅ import default, e.g. import foo from 'bar'
  • ✅ import namespace, e.g. import * as foo from 'bar'
  • ✅ import named, e.g. import { foo } from 'bar'
  • ✅ import named with alias, e.g. import { foo as bar } from 'bar'
  • ✅ import type named, e.g. import type { foo } from 'bar' or import { type foo } from 'bar'

type definition

The imports type is defined as follows:

interface ScanResultBase {
  source: string
  loc: ASTNodeLocation
}

// import a from 'a'
interface ScanImportResultDefault extends ScanResultBase {
  type: 'default'
  local: string // a
}

// import * as a from 'a'
interface ScanImportResultNamespace extends ScanResultBase {
  type: 'namespace'
  local: string // a
}

// import { a as b } from 'a'
interface ScanImportResultImport extends ScanResultBase {
  type: 'import'
  subType: 'id' | 'string' // id: `import { a } from 'a'`; string: `import { 'a' } from 'a'`
  isType: boolean // `import type { a } from 'a'` or `import { type a } from 'a'`
  local: string // b
  imported: string // a
}

type ScanImportResultItem = ScanImportResultDefault | ScanImportResultNamespace | ScanImportResultImport

The imports is an array of ScanImportResultItem.

examples

The basic example:

const code = 'import foo from "bar"'
scan(code, 'js').imports

will be:

[
  {
    loc: {
      end: {
        column: 10,
        index: 10,
        line: 1,
      },
      start: {
        column: 7,
        index: 7,
        line: 1,
      },
    },
    local: 'foo',
    source: 'bar',
    type: 'default',
  },
]

the standalone API

Also, you can use the standalone import scanner API(with loadScanner helper):

import { loadScanner } from 'esm-analyzer'

const importResults = loadScanner(sourceCode, lang, node => scanImport(node))

config

The scanImport function accepts a config object as the second parameter:

interface ScanImportConfig {
  includeSource?: string[] // the source list to be included
  excludeSource?: string[] // the source list to be excluded
  skipType?: boolean // whether to skip the type import
}

const defaultConfig: Required<ScanImportConfig> = {
  includeSource: [],
  excludeSource: [],
  skipType: false,
}

variable declarations scanner

The variable declarations is an array of ScanVariableDeclarationResult.

cases

  • ❌ deferred init
  • primitive declaration
    • StringLiteral
    • NumericLiteral
    • BooleanLiteral
    • NullLiteral
  • ✅ reference declaration
  • complex declaration
    • ObjectExpression
    • ArrayExpression
    • CallExpression
    • ❗ Others are not supported yet

type definition

The ScanVariableDeclarationResult is defined as follows:

export interface ScanVariableDeclarationResult {
  loc: ASTNodeLocation
  kind: t.VariableDeclaration['kind']
  name: string
  init: ResolveVariableDeclaration
}

examples

The basic example:

const code = 'const foo = "bar"'
scan(code, 'js').variables

The output will be:

[
  {
    init: {
      type: 'StringLiteral',
      value: 'bar',
    },
    kind: 'const',
    loc: {
      end: {
        column: 17,
        index: 17,
        line: 1,
      },
      start: {
        column: 6,
        index: 6,
        line: 1,
      },
    },
    name: 'foo',
  },
]

the standalone API

Also, you can use the standalone variable scanner API(with loadScanner helper):

import { loadScanner } from 'esm-analyzer'

const importResults = loadScanner(sourceCode, lang, node => scanVariableDeclaration(node))

config

The scanVariableDeclaration function accepts a config object as the second parameter:

export type VariableType =
  | 'StringLiteral'
  | 'NumericLiteral'
  | 'BooleanLiteral'
  | 'NullLiteral'
  | 'ObjectExpression'
  | 'ArrayExpression'
  | 'CallExpression'

interface ScanVariableDeclarationConfig {
  includeType?: VariableType[]
  excludeType?: VariableType[]
}

export scanner

cases

  • ✅ export default, e.g. export default foo
  • ✅ export named, e.g. export { foo }
    • only support Identifier and primitive
    • functions are not supported yet
  • ✅ export named with alias, e.g. export { foo as bar }
  • ✅ export all, e.g. export * from 'foo'
  • ❌ export type, e.g. export type { foo } from 'bar'
  • ❌ export type named, e.g. export { type foo } from 'bar'

type definition

The exports type is defined as follows, will return ScanExportResult[]

export interface ScanExportNamedDeclarationResult {
  type: 'ExportNamedDeclaration'
  subType: 'VariableDeclaration'
  kind: t.VariableDeclaration['kind']
  declarations: {
    name: string
    init: ResolveVariableDeclaration
  }[]
}

export interface ScanExportNamedSpecifiersResult {
  type: 'ExportNamedDeclaration'
  subType: 'Specifiers'
  specifiers: {
    local: string
    exported: string
  }[]
  source: string | null
}

export interface ScanExportAllResult {
  type: 'ExportAllDeclaration'
  source: string
}

export interface ScanExportDefaultIdentifierResult {
  type: 'ExportDefaultDeclaration'
  subType: 'Identifier'
  id: string
}

export interface ScanExportDefaultObjectResult {
  type: 'ExportDefaultDeclaration'
  subType: 'ObjectExpression'
  properties: {
    key: string
    value: ResolveVariableDeclaration
  }[]
}

export type ScanExportResult = (
  | ScanExportNamedDeclarationResult
  | ScanExportNamedSpecifiersResult
  | ScanExportAllResult
  | ScanExportDefaultIdentifierResult
  | ScanExportDefaultObjectResult
) & {
  loc: ASTNodeLocation
}

examples

The basic example:

const code = 'export default { a: 1, b: 2 }'
scan(code, 'js').exports

The result will be:

[
  {
    loc: {
      end: {
        column: 7,
        index: 58,
        line: 5,
      },
      start: {
        column: 6,
        index: 7,
        line: 2,
      },
    },
    properties: [
      {
        key: 'a',
        value: {
          type: 'NumericLiteral',
          value: 1,
        },
      },
      {
        key: 'b',
        value: {
          type: 'NumericLiteral',
          value: 2,
        },
      },
    ],
    subType: 'ObjectExpression',
    type: 'ExportDefaultDeclaration',
  },
]

the standalone API

Also, you can use the standalone export scanner API(with loadScanner helper):

import { loadScanner } from 'esm-analyzer'

const importResults = loadScanner(sourceCode, lang, node => scanExport(node))

config

The scanExport function accepts a config object as the second parameter:

export type ScanExportType =
  | 'ExportNamedDeclaration'
  | 'ExportAllDeclaration'
  | 'ExportDefaultDeclaration'

interface ScanExportConfig {
  includeType?: ScanExportType[]
  excludeType?: ScanExportType[]
}

Analyzer

use analyze you can find all the variable declarations and their import statement and export statement

const code1 = {
  filename: '/src/bar.js',
  code: `
      export const bar = 'bar'
    `,
}
const code2 = {
  filename: '/src/foo.js',
  code: `
      import { bar, ref } from './bar'
      export const foo = bar
      const foo2 = ref(1)
    `,
}
const p = new Project('test')
p.addFile(code1.filename, code1.code)
p.addFile(code2.filename, code2.code)
await p.prepare()
const c = p.findAnalyzeResults(code2.filename)
expect(c).toMatchSnapshot()

The result is map, and it's entries is:

[
  [
    {
      init: {
        id: 'bar',
        type: 'Identifier',
      },
      kind: 'const',
      loc: {
        end: {
          column: 28,
          index: 68,
          line: 3,
        },
        start: {
          column: 19,
          index: 59,
          line: 3,
        },
      },
      name: 'foo',
    },
    {
      fromExport: {
        declarations: [
          {
            init: {
              type: 'StringLiteral',
              value: 'bar',
            },
            name: 'bar',
          },
        ],
        kind: 'const',
        loc: {
          end: {
            column: 30,
            index: 31,
            line: 2,
          },
          start: {
            column: 6,
            index: 7,
            line: 2,
          },
        },
        subType: 'VariableDeclaration',
        type: 'ExportNamedDeclaration',
      },
      fromImport: {
        imported: 'bar',
        isType: false,
        loc: {
          end: {
            column: 18,
            index: 19,
            line: 2,
          },
          start: {
            column: 15,
            index: 16,
            line: 2,
          },
        },
        local: 'bar',
        source: './bar',
        subType: 'id',
        type: 'import',
      },
      id: 'bar',
      importFile: '/src/bar.js',
      type: 'Identifier',
    },
  ],
  [
    {
      init: {
        arguments: [
          {
            type: 'NumericLiteral',
            value: 1,
          },
        ],
        callee: 'ref',
        type: 'CallExpression',
      },
      kind: 'const',
      loc: {
        end: {
          column: 25,
          index: 94,
          line: 4,
        },
        start: {
          column: 12,
          index: 81,
          line: 4,
        },
      },
      name: 'foo2',
    },
    {
      arguments: [
        {
          type: 'NumericLiteral',
          value: 1,
        },
      ],
      callee: 'ref',
      calleeFrom: {
        imported: 'ref',
        isType: false,
        loc: {
          end: {
            column: 23,
            index: 24,
            line: 2,
          },
          start: {
            column: 20,
            index: 21,
            line: 2,
          },
        },
        local: 'ref',
        source: './bar',
        subType: 'id',
        type: 'import',
      },
      type: 'CallExpression',
    },
  ],
]

License

MIT