require-cjs-transformer

API documentation for the require-cjs-transformer plugin

require-cjs-transformer

Transform ESM imports of CommonJS-only packages to require() calls for better Node.js performance.

Installation

The plugin is included with @visulima/packem-rollup:

npm install @visulima/packem

Usage

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  rollup: {
    requireCJS: {
      builtinNodeModules: true
    }
  }
})

API Reference

requireCJSTransformerPlugin

function requireCJSTransformerPlugin(
  options?: RequireCJSPluginOptions,
  logger?: Pail
): Plugin

Creates a Rollup plugin that transforms ESM imports of CJS packages.

Parameters

  • options (RequireCJSPluginOptions?): Plugin configuration options
  • logger (Pail?): Logger instance for debug output

Returns

A Rollup plugin instance.

Options

RequireCJSPluginOptions

interface RequireCJSPluginOptions {
  /**
   * Transform Node.js built-in modules (fs, path, etc.) to use runtime helpers that check Node.js version compatibility
   * @default false
   */
  builtinNodeModules?: boolean

  /**
   * Custom working directory for module resolution
   * @default process.cwd()
   */
  cwd?: string

  /**
   * Additional packages to consider as CJS-only (force transformation)
   * @default []
   */
  additionalPackages?: string[]

  /**
   * Packages to exclude from transformation
   * @default []
   */
  exclude?: string[]

  /**
   * Include patterns for files to process
   * @default ['**/*.{js,jsx,ts,tsx,mjs,mts,cjs,cts}']
   */
  include?: string[]

  /**
   * Exclude patterns for files to skip
   * @default ['**/node_modules/**']
   */
  excludePatterns?: string[]
}

builtinNodeModules

Type: boolean
Default: false

When enabled, transforms Node.js built-in modules to use runtime helpers that check Node.js version compatibility and fallback to require() when needed.

Example:

// Input
import { readFileSync } from 'fs'
import { join } from 'path'

// Output (with builtinNodeModules: true)
const { readFileSync } = __cjs_getBuiltinModule("fs")
const { join } = __cjs_getBuiltinModule("path")

cwd

Type: string
Default: process.cwd()

Working directory for module resolution. Useful when the plugin runs in a different directory than the project root.

additionalPackages

Type: string[]
Default: []

Additional packages to force transformation for, even if they appear to be ESM-compatible.

Example:

requireCJS: {
  additionalPackages: ['my-cjs-package', '@myorg/cjs-lib']
}

exclude

Type: string[]
Default: []

Packages to exclude from transformation, even if they are detected as CJS-only.

Example:

requireCJS: {
  exclude: ['some-package', '@myorg/esm-package']
}

include

Type: string[]
Default: ['**/*.{js,jsx,ts,tsx,mjs,mts,cjs,cts}']

File patterns to include for transformation processing.

excludePatterns

Type: string[]
Default: ['**/node_modules/**']

File patterns to exclude from transformation processing.

Examples

Basic Configuration

import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'

export default defineConfig({
  transformer,
  rollup: {
    requireCJS: {
      builtinNodeModules: true
    }
  }
})

Advanced Configuration

export default defineConfig({
  transformer,
  rollup: {
    requireCJS: {
      builtinNodeModules: true,
      additionalPackages: ['my-cjs-lib'],
      exclude: ['some-esm-package'],
      include: ['src/**/*.{ts,tsx}'],
      excludePatterns: ['src/vendor/**']
    }
  }
})

Programmatic Usage

import { requireCJSTransformerPlugin } from '@visulima/packem-rollup'

const plugin = requireCJSTransformerPlugin({
  builtinNodeModules: true,
  additionalPackages: ['typescript']
})

Transformation Examples

Named Imports

// Input
import { parse } from '@babel/parser'
import { transpile } from 'typescript'

// Output
const { parse } = require("@babel/parser")
const { transpile } = require("typescript")

Default Imports

// Input
import typescript from 'typescript'
import parser from '@babel/parser'

// Output
const typescript = require("typescript")
const parser = require("@babel/parser")

Namespace Imports

// Input
import * as ts from 'typescript'
import * as babel from '@babel/core'

// Output
const ts = require("typescript")
const babel = require("@babel/core")

Node.js Built-ins

// Input (with builtinNodeModules: true)
import { readFileSync } from 'fs'
import { join, dirname } from 'path'

// Output
const { readFileSync } = __cjs_getBuiltinModule("fs")
const { join, dirname } = __cjs_getBuiltinModule("path")

Mixed Imports

// Input
import fs, { readFileSync } from 'fs'
import * as path from 'path'
import { join } from 'path'

// Output (with builtinNodeModules: true)
const fs = __cjs_getBuiltinModule("fs")
const { readFileSync } = __cjs_getBuiltinModule("fs")
const path = __cjs_getBuiltinModule("path")
const { join } = __cjs_getBuiltinModule("path")

Detection Logic

The plugin automatically detects CJS-only packages by:

  1. Package.json Analysis: Checks if type: "commonjs" is set
  2. Export Conditions: Analyzes available export conditions
  3. Entry Points: Verifies if only CommonJS entry points exist
  4. Additional Packages: Includes user-specified packages
  5. Exclusions: Respects user-defined exclusions

Automatic Detection Examples

CJS-only package (transformed):

{
  "name": "cjs-package",
  "main": "index.js",
  "type": "commonjs"
}

ESM package (not transformed):

{
  "name": "esm-package",
  "type": "module",
  "exports": {
    ".": {
      "import": "./index.js",
      "types": "./index.d.ts"
    }
  }
}

Dual package (not transformed - has ESM exports):

{
  "name": "dual-package",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs"
    }
  }
}

TypeScript Support

The plugin generates TypeScript declaration files that reflect the transformed imports:

// Input types (from package)
export declare function parse(code: string): AST

// Generated declaration (transformed)
declare const parse: (code: string) => AST
export { parse }

Rollup Integration

The plugin integrates with Rollup's plugin system and provides:

  • Transform Hook: Processes individual chunks during bundling
  • ResolveId Hook: Handles module resolution for transformed imports
  • Logger Integration: Uses Rollup's logging system for debug output

Plugin Hooks

interface Plugin {
  name: 'packem:require-cjs-transformer'
  version: string

  renderChunk: (code: string, chunk: RenderedChunk) => { code: string } | null
  resolveId: (id: string, importer?: string) => Promise<string | null>
}

Performance Considerations

When to Use

  • ✅ CLI tools with heavy CJS package usage
  • ✅ Build tools and bundlers
  • ✅ Applications with measurable startup time issues
  • ✅ Development servers with frequent restarts

When NOT to Use

  • ❌ Simple applications with few CJS imports
  • ❌ Libraries consumed by other packages
  • ❌ When performance gain is negligible
  • ❌ If you prefer simpler build configuration

Benchmarking

// Measure startup time impact
const start = Date.now()
await import('./dist/index.js')
const end = Date.now()
console.log(`Startup time: ${end - start}ms`)

Error Handling

Common Errors

"Cannot resolve module"

  • Ensure the package is installed
  • Check if the package name is spelled correctly
  • Verify the package exports the expected modules

"Plugin only works with ESM output"

  • Ensure your Rollup output format is ESM-compatible

"Module not found in exclude list"

  • Check your exclude configuration
  • Verify package names match exactly

Debug Logging

Enable debug logging to troubleshoot transformation issues:

export default defineConfig({
  // ... other config
  rollup: {
    requireCJS: {
      // ... options
    }
  }
})

The plugin logs transformation details with the prefix packem:plugin-require-cjs.

  • defineConfig - Main configuration function
  • PackemConfig - Configuration interface
  • isPureCJS - Utility for detecting CJS packages
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues