require-cjs-transformer Plugin
Transform ESM imports of CJS-only packages to require calls for better performance
require-cjs-transformer Plugin
The require-cjs-transformer plugin automatically transforms ESM imports of CommonJS-only packages into require() calls. This optimization improves Node.js performance by skipping the expensive cjs-module-lexer overhead for packages that only provide CJS builds.
Why Use This Plugin?
The Problem
Many popular packages in the JavaScript ecosystem still only provide CommonJS (CJS) builds:
typescript@babel/parsereslintcjs-module-lexeresbuild- And many others...
When you import these packages using ESM syntax:
import { parse } from '@babel/parser'
import { transpile } from 'typescript'Node.js must use the cjs-module-lexer to analyze the CJS module and create ESM compatibility. This analysis is expensive and happens on every import, impacting startup performance.
The Solution
The require-cjs-transformer plugin automatically converts ESM imports of CJS-only packages to require() calls:
// Input (ESM import)
import { parse } from '@babel/parser'
import { transpile } from 'typescript'
// Output (transformed to require)
const { parse } = require('@babel/parser')
const { transpile } = require('typescript')This allows Node.js to skip the cjs-module-lexer analysis entirely, providing better performance.
Performance Impact
Important: Only use this plugin if the performance benefit is significant for your use case. The plugin adds complexity and maintenance overhead.
For applications that heavily use CJS-only packages, this plugin can provide measurable performance improvements. However, for most applications, the performance difference is negligible.
Configuration
Basic Setup
import { defineConfig } from '@visulima/packem/config'
import transformer from '@visulima/packem/transformer/esbuild'
export default defineConfig({
transformer,
rollup: {
requireCJS: {
builtinNodeModules: true
}
}
})Options
interface RequireCJSPluginOptions {
/**
* Transform Node.js built-in modules (fs, path, etc.)
* @default false
*/
builtinNodeModules?: boolean
/**
* Custom CWD for resolving modules
* @default process.cwd()
*/
cwd?: string
/**
* Additional packages to consider as CJS-only
* @default []
*/
additionalPackages?: string[]
/**
* Packages to exclude from transformation
* @default []
*/
exclude?: string[]
}Advanced Configuration
export default defineConfig({
transformer,
rollup: {
requireCJS: {
builtinNodeModules: true,
additionalPackages: ['my-cjs-package'],
exclude: ['some-package']
}
}
})How It Works
Automatic Detection
The plugin automatically detects which packages are CJS-only by:
- Checking if the package has
type: "commonjs"in its package.json - Analyzing the package's export conditions
- Verifying if only CJS entry points are available
Node.js Built-ins
When builtinNodeModules: true is set, the plugin generates runtime checks to automatically choose the most efficient approach for the executing environment:
// Input
import { readFileSync } from 'fs'
import { join } from 'path'
import { cwd } from 'process'
// Output (runtime-detected)
import { createRequire as __cjs_createRequire } from "node:module";
const __cjs_require = __cjs_createRequire(import.meta.url);
// Runtime capability helpers
const __cjs_getBuiltinModule = (module) => {
// Check if we're in Node.js and version supports getBuiltinModule
if (typeof process !== "undefined" && process.versions?.node) {
const [major, minor] = process.versions.node.split(".").map(Number);
// Node.js 20.16.0+ and 22.3.0+
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
return process.getBuiltinModule(module);
}
}
// Fallback to createRequire
return __cjs_require(module);
};
const __cjs_getProcess = typeof globalThis !== "undefined" && typeof globalThis.process !== "undefined" ? globalThis.process : process;
// Use helpers for clean module resolution
const { readFileSync } = __cjs_getBuiltinModule("fs");
const { join } = __cjs_getBuiltinModule("path");
const { cwd } = __cjs_getProcess;The generated code automatically detects the runtime environment and Node.js version at execution time, providing optimal performance in any supported environment.
Selective Transformation
The plugin only transforms imports that are determined to be CJS-only. ESM packages are left untouched:
// ESM package - NOT transformed
import { someFunction } from 'esm-package'
// CJS package - transformed to require
const { someFunction } = require('cjs-package')Usage Examples
Basic Usage
// src/index.ts
import { transpile } from 'typescript'
import { parse } from '@babel/parser'
import { readFileSync } from 'fs'
export function processCode(code: string) {
const ast = parse(code)
const js = transpile(code)
const file = readFileSync('file.txt')
return { ast, js, file }
}After transformation:
// dist/index.mjs
import { createRequire as __cjs_createRequire } from "node:module";
const __cjs_require = __cjs_createRequire(import.meta.url);
// Runtime capability helpers
const __cjs_getBuiltinModule = (module) => {
// Check if we're in Node.js and version supports getBuiltinModule
if (typeof process !== "undefined" && process.versions?.node) {
const [major, minor] = process.versions.node.split(".").map(Number);
if (major > 22 || (major === 22 && minor >= 3) || (major === 20 && minor >= 16)) {
return process.getBuiltinModule(module);
}
}
// Fallback to createRequire
return __cjs_require(module);
};
const { transpile } = __cjs_require("typescript")
const { parse } = __cjs_require("@babel/parser")
const { readFileSync } = __cjs_getBuiltinModule("fs")
export function processCode(code) {
const ast = parse(code)
const js = transpile(code)
const file = readFileSync('file.txt')
return { ast, js, file }
}React Component Library
// src/Button.tsx
import React from 'react'
import { transpile } from 'typescript'
export function Button({ children }) {
// Use typescript for runtime transpilation
const code = transpile('const x = 1')
return <button>{children}</button>
}CLI Tool
// src/cli.ts
import { Command } from 'commander'
import { readFileSync } from 'fs'
import { parse } from '@babel/parser'
const program = new Command()
program
.argument('<file>')
.action((file) => {
const content = readFileSync(file, 'utf8')
const ast = parse(content)
console.log(ast)
})
program.parse()Ecosystem Appeal
We encourage the JavaScript ecosystem to continue its transition toward ESM. If you maintain a package that is still CJS-only, please consider offering an ESM build or migrating fully to ESM.
The need for plugins like require-cjs-transformer highlights the importance of the ecosystem's transition to ESM. By providing ESM builds, packages can:
- Eliminate the cjs-module-lexer overhead
- Provide better tree-shaking capabilities
- Offer improved performance for all consumers
- Reduce complexity in build tools
When NOT to Use
Don't use this plugin if:
- Your application doesn't heavily use CJS-only packages
- Performance is not a critical concern
- You prefer to keep your build configuration simple
- You're building for environments where
require()is not available
Alternative Approaches
- Use ESM-compatible packages: Choose packages that provide ESM builds
- Runtime requires: Use dynamic
require()calls where needed
Troubleshooting
Plugin Not Transforming Imports
- Verify package detection: The plugin only transforms detected CJS packages
- Check exclude list: Make sure the package isn't in the
excludearray
Build Errors
- CommonJS compatibility: Ensure your environment supports
require() - Module resolution: Verify package paths are correct
- TypeScript types: Update type imports if needed
Performance Issues
If you experience performance issues:
- Disable the plugin: Remove the
requireCJSconfiguration - Selective transformation: Use
excludeto skip problematic packages - Alternative bundling: Consider using a different bundling strategy
Migration Guide
From Manual require() Calls
// Before
const { parse } = require('@babel/parser')
const { transpile } = require('typescript')
// After - plugin handles this automatically
import { parse } from '@babel/parser'
import { transpile } from 'typescript'From Dynamic Imports
// Before
const { parse } = await import('@babel/parser')
// After - plugin provides static transformation
import { parse } from '@babel/parser'See Also
- Performance Impact Study
- Node.js ESM Documentation
- cjs-module-lexer Repository
- ESM Migration Guide