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

@colinhacks/pkgroll

v1.0.0

Published

Zero-config rollup bundler

Downloads

3

Readme

pkgroll is a JavaScript package bundler powered by Rollup that automatically builds your package from entry-points defined in package.json. No config necessary!

Write your code in TypeScript/ESM and run pkgroll to get ESM/CommonJS/.d.ts outputs!

Features

  • package.json#exports to define entry-points
  • ✅ Dependency externalization
  • ✅ Minification
  • ✅ TypeScript support + .d.ts bundling
  • ✅ Watch mode
  • ✅ CLI outputs (auto hashbang insertion)

Install

npm install --save-dev pkgroll

Quick setup

  1. Setup your project with source files in src and output in dist (configurable).

  2. Define package entry-files in package.json.

    These configurations are for Node.js to determine how to import the package.

    Pkgroll leverages the same configuration to determine how to build the package.

    {
        "name": "my-package",
    
        // Set "module" or "commonjs" (https://nodejs.org/api/packages.html#type)
        // "type": "module",
    
        // Define the output files
        "main": "./dist/index.cjs",
        "module": "./dist/index.mjs",
        "types": "./dist/index.d.cts",
    
        // Define output files for Node.js export maps (https://nodejs.org/api/packages.html#exports)
        "exports": {
            "require": {
                "types": "./dist/index.d.cts",
                "default": "./dist/index.cjs"
            },
            "import": {
                "types": "./dist/index.d.mts",
                "default": "./dist/index.mjs"
            }
        },
    
        // bin files will be compiled to be executable with the Node.js hashbang
        "bin": "./dist/cli.js",
    
        // (Optional) Add a build script referencing `pkgroll`
        "scripts": {
            "build": "pkgroll"
        }
    
        // ...
    }

    Paths that start with ./dist/ are automatically mapped to files in the ./src/ directory.

  3. Package roll!

    npm run build # or npx pkgroll

Usage

Entry-points

Pkgroll parses package entry-points from package.json by reading properties main, module, types, and exports.

The paths in ./dist are mapped to paths in ./src (configurable with --src and --dist flags) to determine bundle entry-points.

Output formats

Pkgroll detects the format for each entry-point based on the file extension or the package.json property it's placed in, using the same lookup logic as Node.js.

| package.json property | Output format | | - | - | | main | Auto-detect | | module | ESMNote: This unofficial property is not supported by Node.js and is mainly used by bundlers. | | types | TypeScript declaration | | exports | Auto-detect | | exports.require | CommonJS | | exports.import | Auto-detect | | exports.types | TypeScript declaration | | bin | Auto-detectAlso patched to be executable with the Node.js hashbang. |

Auto-detect infers the type by extension or package.json#type:

| Extension | Output format | | - | - | | .cjs | CommonJS | | .mjs | ECMAScript Modules | | .js | Determined by package.json#type, defaulting to CommonJS |

Dependency bundling & externalization

Packages to externalize are detected by reading dependency types in package.json. Only dependencies listed in devDependencies are bundled in.

When generating type declarations (.d.ts files), this also bundles and tree-shakes type dependencies declared in devDependencies as well.

// package.json
{
    // ...

    "peerDependencies": {
        // Externalized
    },
    "dependencies": {
        // Externalized
    },
    "optionalDependencies": {
        // Externalized
    },
    "devDependencies": {
        // Bundled
    },
}

Aliases

Aliases can be configured in the import map, defined in package.json#imports.

For native Node.js import mapping, all entries must be prefixed with # to indicate an internal subpath import. Pkgroll takes advantage of this behavior to define entries that are not prefixed with # as an alias.

Native Node.js import mapping supports conditional imports (eg. resolving different paths for Node.js and browser), but Pkgroll does not.

⚠️ Aliases are not supported in type declaration generation. If you need type support, do not use aliases.

{
    // ...

    "imports": {
        // Mapping '~utils' to './src/utils.js'
        "~utils": "./src/utils.js",

        // Native Node.js import mapping (can't reference ./src)
        "#internal-package": "./vendors/package/index.js",
    }
}

Target

Pkgroll uses esbuild to handle TypeScript and JavaScript transformation and minification.

The target specifies the environments the output should support. Depending on how new the target is, it can generate less code using newer syntax. Read more about it in the esbuild docs.

By default, the target is set to the version of Node.js used. It can be overwritten with the --target flag:

pkgroll --target=es2020 --target=node14.18.0

It will also automatically detect and include the target specified in tsconfig.json#compilerOptions.

Strip node: protocol

Node.js builtin modules can be prefixed with the node: protocol for explicitness:

import fs from 'node:fs/promises'

This is a new feature and may not work in older versions of Node.js. While you can opt out of using it, your dependencies may still be using it (example package using node:: path-exists).

Pass in a Node.js target that that doesn't support it to strip the node: protocol from imports:

pkgroll --target=node12.19

Export condition

Similarly to the target, the export condition specifies which fields to read from when evaluating export and import maps.

For example, to simulate import resolutions in Node.js, pass in node as the export condition:

pkgroll --export-condition=node

ESM ⇄ CJS interoperability

Node.js ESM offers interoperability with CommonJS via static analysis. However, not all bundlers compile ESM to CJS syntax in a way that is statically analyzable.

Because pkgroll uses Rollup, it's able to produce CJS modules that are minimal and interoperable with Node.js ESM.

This means you can technically output in CommonJS to get ESM and CommonJS support.

require() in ESM

Sometimes it's useful to use require() or require.resolve() in ESM. ESM code that uses require() can be seamlessly compiled to CommonJS, but when compiling to ESM, Node.js will error because require doesn't exist in the module scope.

When compiling to ESM, Pkgroll detects require() usages and shims it with createRequire(import.meta.url).

Environment variables

Pass in compile-time environment variables with the --env flag.

This will replace all instances of process.env.NODE_ENV with 'production' and remove unused code:

pkgroll --env.NODE_ENV=production

Minification

Pass in the --minify flag to minify assets.

pkgroll --minify

Watch mode

Run the bundler in watch mode during development:

pkgroll --watch

Clean dist

Clean dist directory before bundling:

pkgroll --clean-dist

Source maps

Pass in the --sourcemap flag to emit a source map file:

pkgroll --sourcemap

Or to inline them in the distribution files:

pkgroll --sourcemap=inline

FAQ

Why bundle with Rollup?

Rollup has the best tree-shaking performance, outputs simpler code, and produces seamless CommonJS and ESM formats (minimal interop code). Notably, CJS outputs generated by Rollup supports named exports so it can be parsed by Node.js ESM. TypeScript & minification transformations are handled by esbuild for speed.

Why bundle Node.js packages?

  • ESM and CommonJS outputs

    As the Node.js ecosystem migrates to ESM, there will be both ESM and CommonJS users. A bundler helps accommodate both distribution types.

  • Dependency bundling yields smaller and faster installation.

    Tree-shaking only pulls in used code from dependencies, preventing unused code and unnecessary files (eg. README.md, package.json, etc.) from getting downloaded.

    Removing dependencies also eliminates dependency tree traversal, which is one of the biggest bottlenecks.

  • Inadvertent breaking changes

    Dependencies can introduce breaking changes due to a discrepancy in environment support criteria, by accident, or in rare circumstances, maliciously.

    Compiling dependencies will make sure new syntax & features are downgraded to support the same environments. And also prevent any unexpected changes from sneaking in during installation.

  • Type dependencies must be declared in the dependencies object in package.json, instead of devDependencies, to be resolved by the consumer.

    This may seem counterintuitive because types are a development enhancement. By bundling them in with your package, you remove the need for an external type dependency. Additionally, bundling only keeps the types that are actually used which helps minimize unnecessary bloat.

  • Minification strips dead-code, comments, white-space, and shortens variable names.

Sponsors