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

@knighted/specifier

v1.0.1

Published

Node.js tool for updating your ESM and CJS specifiers.

Downloads

272

Readme

@knighted/specifier

CI codecov NPM version

Node.js tool for updating your ESM and CJS specifiers.

Supports:

  • Latest ECMAScript
  • TypeScript
  • JSX

Example

The following script will update all specifiers in dist/index.js with an AST node.type of StringLiteral, and that also end with a .js extension, to end with an .mjs extension instead. Finally, it writes the updated source to dist/index.mjs.

script.js

import { writeFile } from 'node:fs/promises'
import { specifier } from '@knighted/specifier'

const { code, error } = await specifier.update('dist/index.js', ({ type, value }) => {
  if (type === 'StringLiteral') {
    return value.replace(/([^.]+)\.js$/, '$1.mjs')
  }
})

if (code && !error) {
  await writeFile('dist/index.mjs', code)
}

You can also provide an object where the keys are regular expressions and the values are the replacements to make when the regular expression matches.

The following does the same as before.

const { code, error } = await specifier.mapper('dist/index.js', {
  '([^.]+)\\.js$': '$1.mjs'
})

The order of the keys in the map object matters, the first match found will be used, so the most specific rule should be defined first. Additionally, you can substitute captured regex groups using numbered backreferences.

You can also check out the reference implementation.

async specifier.update(filename, callback)

Updates specifiers in filename using the values returned from callback, and returns the updated file content. The callback is called for each specifier found, and the returned value is used to update the related specifier value. If the callback returns anything other than a string, the return value will be ignored and the specifier not updated.

Signature

(filename: string, callback: Callback) => Promise<Update>

Where the other types are defined as such.

type Callback = (spec: Spec) => string | undefined
interface Spec {
  type: 'StringLiteral' | 'TemplateLiteral' | 'BinaryExpression' | 'NewExpression'
  start: number
  end: number
  value: string
  loc: SourceLocation
}
interface Position {
  line: number
  column: number
}
interface SourceLocation {
  start: Position
  end: Position
}
interface Update {
  code?: string
  map?: SourceMap | null
  error?: UpdateError
}
interface UpdateError {
  error: boolean
  msg: string
  filename?: string
  syntaxError?: {
    code: string
    reasonCode: string
  }
}

The Spec.value will not include any surrounding quotes or backticks when the type is StringLiteral or TemplateLiteral, and the return value from callback is not expected to include them either.

async specifier.mapper(filename, regexMap)

Updates specifiers in filename using the provided regexMap object and returns the updated file content. The value of the first key to match in regexMap is used, so more specific rules should be defined first. Numbered backreferences of captured groups can be used.

Signature

(filename: string, regexMap: {[regex: string]: string}) => Promise<Update>

Where Update is the same from specifier.update.

async specifier.updateSrc(code, callbackOrMap, opts)

Updates specifiers in source code using the values defined from callbackOrMap, and returns an Update that includes a map if opts.sourceMap was passed. If the provided source code is from a TypeScript declaration file you should pass true for opts.dts to support parsing of ambient contexts.

Signature

(code: string, callbackOrMap: Callback | RegexMap, opts?: Opts) => Promise<Update>
interface Opts {
  dts?: boolean;
  sourceMap?: boolean;
}
interface RegexMap {
  [regex: string]: string
}

Where the other types have the same definition from specifier.update.