expo / expo

An open-source framework for making universal native apps with React. Expo runs on Android, iOS, and the web.
https://docs.expo.dev
MIT License
34.06k stars 5.45k forks source link

Expo API Router Vercel Deployment fails with ESM #30423

Open ProchaLu opened 3 months ago

ProchaLu commented 3 months ago

Minimal reproducible example

https://github.com/ProchaLu/expo-api-router-deployment

Which package manager are you using? (Yarn is recommended)

pnpm

If the issue is web-related, please select the bundler (web.bundler in the app.json)

None

Summary

While the Expo API Router documentation states that ESM (ECMAScript Modules) is not supported, all other ESM-based TypeScript configurations in my project have been working seamlessly. I would like to maintain consistency by using ESM throughout the entire project.

Error

When attempting to deploy the Expo API Router on Vercel, I encounter the following error message:

Unhandled Rejection: Error [ERR_REQUIRE_ESM]: require() of ES Module /var/task/dist/server/_expo/functions/hello+api.js from /var/task/node_modules/@expo/server/build/index.js not supported.
hello+api.js is treated as an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which declares all .js files in that package scope as ES modules.
Instead either rename hello+api.js to end in .cjs, change the requiring code to use dynamic import() which is available in all CommonJS modules, or change "type": "module" to "type": "commonjs" in /var/task/package.json to treat all .js files as CommonJS (using .mjs for all ES modules instead).

    at /opt/rust/nodejs.js:1:11506
    at Function.Xt (/opt/rust/nodejs.js:1:11878)
    at Z.e.<computed>.X._load (/opt/rust/nodejs.js:1:11476)
    at getApiRoute (/var/task/node_modules/@expo/server/build/index.js:66:16)
    at handler (/var/task/node_modules/@expo/server/build/index.js:142:32) {
  code: 'ERR_REQUIRE_ESM'
}
Node.js process exited with exit status: 128. The logs above can help with debugging the issue.

This is my app/index.js file

import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequestHandler } from '@expo/server/adapter/vercel.js';

export default createRequestHandler({
  build: join(dirname(fileURLToPath(import.meta.url)), '../dist/server'),
});

Switching to TypeScript throws a exports not defined error

ReferenceError: exports is not defined in ES module scope
This file is being treated as an ES module because it has a '.js' file extension and '/var/task/package.json' contains "type": "module". To treat it as a CommonJS script, rename it to use the '.cjs' file extension.
    at file:///var/task/api/index.js:2:23
    at ModuleJob.run (node:internal/modules/esm/module_job:222:25)
    at ModuleLoader.import (node:internal/modules/esm/loader:316:24)
    at c (/opt/rust/nodejs.js:8:13647)
Node.js process exited with exit status: 1. The logs above can help with debugging the issue.
INIT_REPORT Init Duration: 215.36 ms    Phase: invoke   Status: error   Error Type: Runtime.ExitError

Environment

expo-env-info 1.2.0 environment info: System: OS: macOS 14.5 Shell: 5.9 - /bin/zsh Binaries: Node: 20.14.0 - /opt/homebrew/opt/node@20/bin/node npm: 10.7.0 - /opt/homebrew/opt/node@20/bin/npm Managers: CocoaPods: 1.15.2 - /opt/homebrew/bin/pod SDKs: iOS SDK: Platforms: DriverKit 23.5, iOS 17.5, macOS 14.5, tvOS 17.5, visionOS 1.2, watchOS 10.5 IDEs: Android Studio: 2022.1 AI-221.6008.13.2211.9619390 Xcode: 15.4/15F31d - /usr/bin/xcodebuild npmPackages: expo: ~51.0.20 => 51.0.20 expo-router: ^3.5.18 => 3.5.18 react: 18.2.0 => 18.2.0 react-dom: ^18.3.1 => 18.3.1 react-native: 0.74.3 => 0.74.3 react-native-web: ^0.19.12 => 0.19.12 npmGlobalPackages: expo-cli: 6.3.2 Expo Workflow: managed

ProchaLu commented 3 months ago

Workaround

To deploy to Vercel while using ESM, I had to modify the package.json to switch from "type": "module" to "type": "commonjs". This can be achieved inside the vercel.json file by specifying a custom buildCommand.

Here’s the solution:

"buildCommand": "expo export -p web && sed -i 's/\"type\": \"module\"/\"type\": \"commonjs\"/' package.json",
jancbeck commented 2 months ago

I tried the workaround but it would only run the sed command locally when running vercel build, not when actually deploying. Vercel had automatically activated build command override when it detected turborepo. Not sure it matters, since Expo/Metro uses CJS internally anyways.

I ended up using your patch for expo-server headers. Did you open an issue for that already?

ProchaLu commented 2 months ago

@jancbeck, thx for the follow-up! I didn't open an issue for that

jancbeck commented 2 months ago

@ProchaLu would be great if you could. This saved me quite a lot of time. In any case thanks!

karlhorky commented 2 months ago

I ended up using your patch for expo-server headers. Did you open an issue for that already?

@jancbeck, thx for the follow-up! I didn't open an issue for that

No need to open an issue, it's already reported and fixed (in June 2024):

Just hasn't been published yet. Probably the next version will be either @expo/server@0.4.5 or @expo/server@0.5.0 - you'll see the change in build/vendor/vercel.js at the links below once a new version is published (currently both of these links are broken, because they are not published yet):

If you want to use this fixed version without waiting, just change to the canary version that has been published:

cc @kitten @EvanBacon looks like users are having problems elsewhere that this June 2024 fix to @expo/server has not been published - in case there's a chance to get a new release published soon-ish.