Skip to content

[cli] Migrate build output from CommonJS to ESM#14566

Merged
tknickman merged 15 commits intomainfrom
tknickman/cli-esm-migration
Jan 12, 2026
Merged

[cli] Migrate build output from CommonJS to ESM#14566
tknickman merged 15 commits intomainfrom
tknickman/cli-esm-migration

Conversation

@tknickman
Copy link
Copy Markdown
Member

Summary

This PR migrates the Vercel CLI build output from CommonJS to ESM (ECMAScript Modules), enabling the use of modern ESM-only packages while maintaining full backwards compatibility.

Key Changes

  • Build Configuration: Added "type": "module" to package.json, updated esbuild to output ESM format with a CJS compatibility shim for require, __filename, and __dirname
  • Worker Files: Renamed standalone worker files (builder-worker.js, get-latest-worker.js) to .cjs extension since they use CommonJS syntax
  • Template Compilation: Updated doT.js template compilation to handle ESM package context
  • TypeScript Config: Updated module settings to esnext with node16 resolution
  • Jest Config: Renamed to .cjs for CommonJS compatibility in ESM package

Why ESM?

  • Enables use of modern npm packages that are ESM-only (e.g., Ink 5+/6+ for terminal UI)
  • Better tree-shaking and static analysis capabilities
  • Aligns with the Node.js ecosystem direction
  • Prerequisite for future CLI enhancements

Backwards Compatibility

This change is fully backwards compatible:

  • CLI works identically for end users
  • All existing functionality preserved
  • Works with both Node.js and Bun runtimes
  • No changes to public API or CLI interface

Test plan

  • pnpm build --filter=vercel succeeds
  • vercel --version works correctly
  • vercel whoami works correctly
  • vercel dev --help loads without errors
  • Unit tests pass (1,582 passing)
  • Works with Node.js runtime
  • Works with Bun runtime

🤖 Generated with Claude Code

@tknickman tknickman requested a review from a team as a code owner January 9, 2026 15:06
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 9, 2026

🦋 Changeset detected

Latest commit: 50c773f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
vercel Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 9, 2026

🧪 Test Strategy

Comparing: 835679950c773f (view diff)

Strategy: Code changed outside of a package - running ALL tests

⚠️ All tests will run because global code changes could impact all packages.

Affected packages - 16 (40%)
  1. @vercel-internals/get-package-json
  2. @vercel/next
  3. vercel
  4. @vercel/build-utils
  5. @vercel/client
  6. @vercel/firewall
  7. @vercel/fs-detectors
  8. @vercel/go
  9. @vercel/hydrogen
  10. @vercel/node
  11. @vercel/python
  12. @vercel/remix-builder
  13. @vercel/ruby
  14. @vercel/rust
  15. @vercel/static-build
  16. examples
Unaffected packages - 24 (60%)
  1. @vercel/backends
  2. @vercel/cervel
  3. @vercel/cli-auth
  4. @vercel/config
  5. @vercel/detect-agent
  6. @vercel/edge
  7. @vercel/elysia
  8. @vercel/error-utils
  9. @vercel/express
  10. @vercel/fastify
  11. @vercel/frameworks
  12. @vercel/functions
  13. @vercel/gatsby-plugin-vercel-builder
  14. @vercel/h3
  15. @vercel/hono
  16. @vercel/introspection
  17. @vercel/nestjs
  18. @vercel/oidc
  19. @vercel/oidc-aws-credentials-provider
  20. @vercel/python-analysis
  21. @vercel/redwood
  22. @vercel/related-projects
  23. @vercel/routing-utils
  24. @vercel/static-config

Results

  • Unit tests: All affected packages will run unit tests
  • E2E tests: All e2e tests will run
  • Type checks: All affected packages will run type checks

This comment is automatically generated based on the affected testing strategy

Comment thread packages/cli/scripts/start.js Outdated
@TooTallNate
Copy link
Copy Markdown
Member

:nice-90s-bounce:

Comment thread packages/cli/tsconfig.json
Comment thread packages/cli/scripts/build.mjs
Copy link
Copy Markdown
Contributor

@mehulkar mehulkar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, let me know if you want to do more testing (not even sure how that happens)

Comment thread .changeset/cli-esm-migration.md
Comment thread packages/cli/scripts/start.js
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jan 12, 2026

📦 CLI Tarball Ready

The Vercel CLI tarball for this PR is now available!

Quick Test

You can test this PR's CLI directly by running:

npx https://vercel-aayd39b0d.vercel.sh/tarballs/vercel.tgz --help

Use in vercel.json

To use this CLI version in your project builds, add to your vercel.json:

{
  "build": {
    "env": {
      "VERCEL_CLI_VERSION": "vercel@https://vercel-aayd39b0d.vercel.sh/tarballs/vercel.tgz"
    }
  }
}

tknickman and others added 13 commits January 12, 2026 13:23
This change updates the CLI build process to output ESM (ECMAScript Modules) instead of CommonJS, while maintaining full backwards compatibility.

- Add `"type": "module"` to package.json
- Update esbuild to output `format: 'esm'`
- Add banner shim for CommonJS globals (`require`, `__filename`, `__dirname`)
- Update tsconfig.json with `module: 'esnext'` and `moduleResolution: 'node16'`

- Rename `builder-worker.js` → `builder-worker.cjs`
- Rename `get-latest-worker.js` → `get-latest-worker.cjs`
- These standalone worker files use CommonJS and need explicit `.cjs` extension

- Update `compile-templates.mjs` to handle doT.js output in ESM context
- Rename generated files to `.cjs` before requiring them

- Rename `jest.config.js` → `jest.config.cjs` for CommonJS compatibility

- Add `.gitignore` entries to prevent test-generated artifacts from being tracked

- Enables use of modern npm packages that are ESM-only
- Better tree-shaking and static analysis
- Aligns with Node.js ecosystem direction
- Prerequisite for future enhancements (e.g., Ink for terminal UI)

This change is fully backwards compatible:
- CLI works identically for end users
- All existing functionality preserved
- Works with both Node.js and Bun runtimes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The start.js script used require() which fails with "type": "module".
Convert to dynamic import() to fix test failures.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The module and moduleResolution settings are not needed since esbuild
handles module resolution during bundling. The node16 moduleResolution
was causing tsc --noEmit to fail because it requires explicit .js
extensions in import paths.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
In ESM, callSite.getFileName() returns a file:// URL instead of a
regular file path. This caused path.join to malform the path and
the package.json lookup to fail.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Without await, the script could exit before the imported module
finishes executing.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
enableCompileCache was added in Node.js 22.1.0. The static import
fails on Node 20 because the export doesn't exist. Using dynamic
import with try-catch allows graceful degradation on older versions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Test fixtures were being treated as ES modules because Node.js was
walking up the directory tree and finding the CLI's package.json
with type: module. This caused Lambda functions to fail with
require is not defined in ES module scope errors.

The fix adds type: commonjs to:
- packages/cli/test/dev/fixtures/package.json (new file)
- packages/cli/test/fixtures/package.json (existing file)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add tests to validate ESM migration behavior:
- CJS shim globals (require, __filename, __dirname)
- enableCompileCache backward compatibility pattern
- Worker file resolution and CommonJS syntax
- file:// URL handling for ESM stack traces
- Test fixture isolation from ESM inheritance

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use platform-specific paths instead of hardcoded Unix paths.
fileURLToPath behaves differently on Windows (adds drive letter).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Set module to es2022 to enable import.meta support for ESM unit tests
while maintaining compatibility with existing code that doesnt use
explicit file extensions.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Explain why we rename to .cjs (ESM package needs explicit CJS extension)
and why we delete instead of rename back (generating TypeScript instead).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace createRequire/require() with await import() which works with
both ESM and CJS modules natively, simplifying the code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Comment thread packages/cli/scripts/compile-templates.mjs
Comment thread packages/cli/src/vc.js Outdated
Co-authored-by: Steven <steven@ceriously.com>
Comment thread packages/cli/test/fixtures/unit/monorepo-link/.gitignore
Comment thread packages/cli/tsconfig.json
@tknickman tknickman merged commit e959b59 into main Jan 12, 2026
346 of 348 checks passed
@tknickman tknickman deleted the tknickman/cli-esm-migration branch January 12, 2026 19:56
tknickman pushed a commit that referenced this pull request Jan 12, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and the packages will
be published to npm automatically. If you're not ready to do a release
yet, that's fine, whenever you add more changesets to main, this PR will
be updated.


# Releases
## vercel@50.3.0

### Minor Changes

- Add upgrade command
([#14584](#14584))

- Support typo detection for commands as well as frameworks
([#14586](#14586))

### Patch Changes

- Migrate CLI build output from CommonJS to ESM
([#14566](#14566))

    -   Change CLI package to use ESM format (`"type": "module"`)
- Add ESM shim banner for CommonJS compatibility (`require`,
`__filename`, `__dirname`)
- Rename worker files to `.cjs` extension for explicit CommonJS handling
- Fix `getPackageJSON` to handle ESM `file://` URLs from stack traces
    -   Convert `scripts/start.js` to use dynamic import

## @vercel/python-analysis@0.1.1

### Patch Changes

-   Fix release ([#14591](#14591))

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants