Skip to content

Complete ESM conversion and improve typing throughout the codebase#18

Merged
robdimarco-atxp merged 40 commits intomainfrom
robdimarco/fix-vercel-functions-builds-conflict
Sep 10, 2025
Merged

Complete ESM conversion and improve typing throughout the codebase#18
robdimarco-atxp merged 40 commits intomainfrom
robdimarco/fix-vercel-functions-builds-conflict

Conversation

@robdimarco-atxp
Copy link
Contributor

@robdimarco-atxp robdimarco-atxp commented Sep 10, 2025

Summary

This PR completes the ESM (ES Module) conversion of the backend and improves typing throughout the codebase, along with several deployment and development experience improvements.

It was motivated by wanting to get things working on Vercel and Render

Backend Changes

  • Complete ESM conversion: Convert from CommonJS to native ES modules with "type": "module"
  • Static imports: Replace dynamic imports with static imports (confirmed working with Vercel)
  • Enhanced CORS handling: Improve CORS configuration for both development and production
  • Better static file serving: Enhanced path resolution for frontend builds across different environments
  • Vercel compatibility: Add proper serverless function export and deployment configuration

Frontend Changes

  • Smart environment detection: Replace hostname-based SSE connection logic with NODE_ENV detection
  • Universal development support: Works with any hostname in development (not just localhost)

Testing & Development

  • Improved test mocks: Fix test mocks to work properly with static imports using vi.mocked()
  • Better test coverage: Add back important constructor parameter assertions
  • ESM test configuration: Add vitest config for proper ESM testing

Deployment

  • Vercel optimization: New streamlined Vercel configuration with proper build pipeline
  • Multi-environment support: Enhanced path resolution for various deployment scenarios

Technical Details

ESM Conversion

  • Added "type": "module" to package.json for native ESM support
  • Updated TypeScript config to target ESNext modules
  • Added ESM __dirname polyfill using fileURLToPath and dirname
  • Updated all imports to use .js extensions for ESM compatibility
  • Replaced Function constructor workarounds with clean await import() syntax

Frontend Environment Detection

  • Before: window.location.hostname === 'localhost' (breaks on IP addresses, custom hostnames)
  • After: process.env.NODE_ENV === 'development' (universal development detection)

Benefits

  • Better typing: Proper ATXPAccount types instead of any throughout codebase
  • Cleaner code: Synchronous functions where appropriate, removing unnecessary async overhead
  • Universal development: Frontend works on localhost, IP addresses, and custom hostnames
  • Production ready: Confirmed working on Render and Vercel deployments
  • Type safety: Full TypeScript compilation without errors
  • Better testing: Comprehensive test coverage with proper mocking

Test Plan

  • All existing tests pass (30/30)
  • TypeScript compilation succeeds with no errors
  • Local development server works (npm run dev and npm start)
  • Frontend SSE connections work on localhost and IP addresses
  • Vercel deployment works with static imports
  • Render deployment confirmed working
  • Production build process works correctly

Background

The original async approach with dynamic imports was implemented to fix Vercel deployment issues. After completing the full ESM conversion, testing confirmed that static imports now work properly with Vercel, allowing us to revert to cleaner, properly-typed synchronous functions.

🤖 Generated with Claude Code

robdimarco-atxp and others added 30 commits September 10, 2025 11:42
**Problem**: Vercel deployment failed with error:
"The functions property cannot be used in conjunction with the builds property"

**Solution**:
- Remove legacy builds property from vercel.json
- Keep modern functions property for maxDuration configuration
- Maintain routes and installCommand for single-service architecture

Vercel now auto-detects the backend/server.ts file and builds it appropriately
without needing explicit builds configuration.
**Problem**: Vercel deployment failed with error:
"The pattern 'backend/server.ts' defined in functions doesn't match any
Serverless Functions inside the api directory"

**Root Cause**: Vercel expects functions to be in api/ directory, but we
were referencing backend/server.ts in the functions config.

**Solution**: Remove the functions config and let Vercel auto-detect
the serverless function. Vercel will automatically:
- Detect backend/server.ts as a Node.js serverless function
- Apply appropriate default configurations
- Handle the routing as specified in routes array

**Result**: Cleaner config that works with Vercel's auto-detection system.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**: Vercel deployment failed with error:
"No Output Directory named 'public' found after the Build completed"

**Root Cause**: Vercel expected static files in a public directory, but our
single-service architecture serves everything through Express serverless function.

**Solution**:
- Add explicit builds configuration with @vercel/node
- Specify backend/server.ts as the serverless function source
- Remove outputDirectory config (not needed for serverless functions)
- Keep installCommand to ensure proper build process

**Result**: Vercel will treat this as a pure serverless function deployment
without looking for static output directories.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
**Problem**: @atxp/client is an ES Module but TypeScript compiles to CommonJS,
causing "ERR_REQUIRE_ESM" errors in Vercel serverless functions.

**Solution**:
- Keep existing CommonJS TypeScript configuration (simpler, more compatible)
- Upgrade Vercel to Node.js 18.x runtime which better handles ESM/CommonJS interop
- Increase maxLambdaSize to handle larger dependencies
- Use builds configuration for explicit control

**Changes**:
- Reverted ES module experiments (package.json, tsconfig.json, server.ts)
- Updated vercel.json with nodejs18.x runtime and larger lambda size
- Maintains stable CommonJS build while fixing ES module import issues

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Updated from nodejs18.x to nodejs20.x for better ES module support
- Node.js 20 has improved CommonJS/ES module interoperability
- Provides better performance and security updates

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…e compatibility

- Convert static imports of @atxp/client and @atxp/common to dynamic imports in server.ts
- Update atxp-utils.ts functions to use async/dynamic imports for ATXPAccount
- Make findATXPAccount and validateATXPConnectionString async functions
- Change ATXPAccount type annotation to any to avoid static import dependency
- This resolves ERR_REQUIRE_ESM errors when deploying to Vercel serverless functions

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update atxp-utils.test.ts to use async/await for findATXPAccount and validateATXPConnectionString
- Update server.test.ts validation endpoint to handle async validateATXPConnectionString
- Remove static ATXPAccount import from test file since it's now dynamically imported
- All tests now pass with the new async implementation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ire() compilation

- Replace await import() with Function constructor approach to bypass TypeScript compilation
- This prevents TypeScript from converting dynamic imports to require() calls
- Should resolve ERR_REQUIRE_ESM errors in Vercel serverless environment
- Tests fail due to Function constructor bypassing vitest mocks, but should work in production
- Add type annotations for payment callback parameters

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add "type": "module" to package.json for native ESM support
- Update TypeScript config to target ESNext modules instead of CommonJS
- Add ESM __dirname polyfill using fileURLToPath and dirname
- Remove Function constructor workarounds for dynamic imports
- Use clean await import() syntax for @atxp/client and @atxp/common
- Update all local imports to use .js extensions for ESM compatibility
- Add vitest config for ESM test environment
- Update dev script to use ts-node/esm loader

This resolves ERR_REQUIRE_ESM errors by natively supporting ES modules
while maintaining clean, readable code without workarounds.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update vercel.json to explicitly include frontend/build/** files
- Add buildCommand to ensure both backend and frontend are built
- Add comprehensive debugging to getStaticPath() function with multiple candidate paths
- Include Vercel-specific paths like /var/task and /vercel/path0
- Add detailed logging to help debug Vercel deployment structure

This should resolve the "No frontend build directory found" error by:
1. Ensuring frontend is built during Vercel deployment
2. Including frontend build files in the serverless function
3. Checking multiple possible paths where build files might be located

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update vercel.json buildCommand to copy frontend/build to backend/ after building
- Update getStaticPath() to prioritize backend/build directory for Vercel
- Add enhanced debugging to check build directory contents
- Simplify candidate paths focusing on the copied build location

This ensures the frontend build files are included in the backend serverless function
deployment, resolving the "No frontend build directory found" error in Vercel.

Local testing confirms the server finds build files at the expected location.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Replace hardcoded localhost CORS origins with dynamic origin handling
- In production, allow any origin since frontend and API are served from same domain
- In development, maintain strict localhost-only CORS for security
- Update SSE endpoint CORS headers to use dynamic origin detection
- This resolves CORS errors when deploying to Vercel, Render, or other platforms

The CORS error was: "Access-Control-Allow-Origin header has a value
'http://localhost:3000' that is not equal to the supplied origin" because
the production frontend was served from Vercel domain, not localhost.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Use relative URL '/api/progress' in production instead of hardcoded localhost
- Maintain localhost URL for development where SSE needs direct backend connection
- Add environment-based URL detection with logging for debugging
- This resolves CORS errors: frontend was connecting to localhost:3001 from Vercel domain

The issue was that EventSource was hardcoded to http://localhost:3001/api/progress
even in production, causing cross-origin requests from Vercel domain to localhost.
Now it uses relative URLs in production since frontend/backend are same domain.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add robust production detection using hostname and protocol checks
- Add debug logging to understand environment detection in Vercel
- Set NODE_ENV=production during build command in vercel.json
- Use multiple fallback methods: NODE_ENV, hostname, and protocol

This should resolve SSE endpoint selection issues by using:
1. NODE_ENV === 'production' (primary)
2. !hostname.includes('localhost') (hostname-based)
3. protocol === 'https:' (protocol-based)

The debug logs will help identify which detection method works in Vercel.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Use simple hostname check: localhost = development, anything else = production
- Always use relative URLs for same-origin requests (deployed environments)
- Only use localhost URLs when specifically running on localhost
- Remove complex environment variable detection that wasn't working reliably
- Simplify Vercel build command without environment variable overrides

This approach is much more reliable:
- localhost/127.0.0.1 → http://localhost:3001/api/progress (development)
- vercel.app/render.com/etc → /api/progress (production, same-origin)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add cache busting strings to force new deployment
- Add version number to SSE connection setup logs
- This should help identify if Vercel is using old cached frontend builds
- New build hash: main.a7ee31e9.js (was main.1abc448d.js)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Remove builds array from vercel.json to resolve the warning:
"Due to builds existing in your configuration file, the Build and
Development Settings defined in your Project Settings will not apply"

This allows Vercel to properly handle frontend build deployment
and use the buildCommand to copy frontend/build to backend/.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Use @vercel/node instead of nodejs20.x for proper function runtime specification.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Use @vercel/node runtime with engines.node specification in package.json
instead of invalid nodejs22.x runtime format.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Remove functions configuration that was causing runtime version errors.
Use rewrites to route all traffic to the Express server.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Specify backend as output directory to resolve 'No Output Directory named public found' error.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Use relative path /dist/server.js instead of /backend/dist/server.js
since outputDirectory is already set to backend.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add api/index.js as Vercel function entry point
- Export Express app from server.ts for serverless deployment
- Update vercel.json to route all requests to /api serverless function
- Remove outputDirectory to let Vercel handle function detection

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Set outputDirectory to frontend/build to resolve build error.
Remove copying frontend build to backend since the serverless function
can access both directories during runtime.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Copy frontend/build to backend/public for cleaner organization
- Update static file path resolution to check public directory
- Set backend as outputDirectory in vercel.json

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Use CommonJS module.exports with dynamic import to handle ES module
compatibility in Vercel's serverless runtime environment.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Rename api/index.js to api/index.mjs to force ES module treatment
- Use proper ES module syntax with top-level await
- Remove CommonJS workaround since we've converted everything to ESM

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
robdimarco-atxp and others added 10 commits September 10, 2025 14:45
…s functions with static imports

- Change findATXPAccount from Promise<any> to ATXPAccount for better typing
- Revert validateATXPConnectionString from async to sync function
- Update all callers to use synchronous versions
- Fix test mocks to work with static imports using vi.mocked()

This reverts the dynamic import approach to test if static imports work with Vercel deployment.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Restore expect(ATXPAccount).toHaveBeenCalledWith() assertions to verify
that findATXPAccount calls the constructor with correct parameters.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
… detection

- Replace hostname-based detection (localhost/127.0.0.1) with NODE_ENV check
- Development mode (npm start): uses direct backend URL regardless of hostname
- Production mode (npm run build): uses relative URL for same-origin requests
- This fixes cases where development server runs on non-localhost hostnames

Tested and confirmed working on:
- Local development (npm run dev and npm start)
- Render deployment
- Vercel deployment

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Import ATXPAccount type from @atxp/client
- Fix account parameter in pollForTaskCompletion function signature
- Fix account variable declaration in /api/texts endpoint
- Maintains type safety throughout the codebase

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@robdimarco-atxp robdimarco-atxp changed the title Improve ATXP utilities typing by reverting to synchronous functions with static imports Complete ESM conversion and improve typing throughout the codebase Sep 10, 2025
@robdimarco-atxp robdimarco-atxp merged commit ee91edc into main Sep 10, 2025
1 check passed
@robdimarco-atxp robdimarco-atxp deleted the robdimarco/fix-vercel-functions-builds-conflict branch September 10, 2025 19:26
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.

1 participant