Skip to content

Expo/RN: Cannot read property `defineProperty' of undefined #1256

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
svile opened this issue May 22, 2025 · 5 comments
Closed

Expo/RN: Cannot read property `defineProperty' of undefined #1256

svile opened this issue May 22, 2025 · 5 comments

Comments

@svile
Copy link

svile commented May 22, 2025

I just ran into this issue on a new default Expo project created using the latest [email protected]

Steps to reproduce:

  1. Create a new react native project: npx create-expo-app@latest
  2. Add in any ts(x) file an example schema definition (e.g. in app/(tabs)/index.tsx:
// ...

import { Type, Static } from "@sinclair/typebox";

// ...

export const TestSchema = Type.Object({
  test: Type.String({
    description: "Test string.",
  }),
});

// ...
  1. Run in e.g. an iOS or Android
  2. Throws: Uncaught error: Cannot read property 'defineProperty' of undefined
@sinclairzx81
Copy link
Owner

sinclairzx81 commented May 22, 2025

@svile Hi,

I just ran into this issue on a new default Expo project created using the latest [email protected]

Interesting. TypeBox doesn't call defineProperty internally, but assume this issue may be to do with how expo polyfills for the JS environment (I have observed issues like this before, usually related to how Babel polyfills JS module headers, usually where shadowing issues occur against TypeBox's Object function coming into conflict with globalThis.Object (because Babel doesn't qualify the Object call)). It's complicated.

TypeBox already follows the Babel recommendation by defining Object with var to prevent the shadowing the polyfill, so there isn't much else TypeBox can do, this of course if the problem relates to the Object function, which I assume it does as defineProperty is a method of globalThis.Object.

What would be helpful though is seeing a full stack trace + the line of code that faults. Keep in mind, I am not a Android or IOS developer, and I can't trivially reproduce this side without installing the tooling. It might be good to direct this issue to the Expo project. Also, if you can debug via making edits to TypeBox in node_modules, that might lend a bit of insight into the problem.

Related PR: #1055

Let me know if you can provide a stack trace.
Cheers
S

@svile
Copy link
Author

svile commented May 22, 2025

@sinclairzx81 hi and thank you for the quick response and useful information!

Here's the full stack trace for React Native Web, which is also experiencing the same issues as the native device bundles:

Metro error: Cannot read properties of undefined (reading 'defineProperty')

  TypeError: Cannot read properties of undefined (reading 'defineProperty')
    at factory (/Users/svile/my-app/node_modules/expo-router/node/render.js.bundle?platform=web&dev=true&hot=false&transform.engine=hermes&transform.routerRoot=app&resolver.environment=node&transform.environment=node&unstable_transformProfile=hermes-stable:98523:10)
    at loadModuleImplementation (/Users/svile/my-app/node_modules/metro-runtime/src/polyfills/require.js:277:5)
    at guardedLoadModule (/Users/svile/my-app/node_modules/metro-runtime/src/polyfills/require.js:184:12)
    at require (/Users/svile/my-app/node_modules/metro-runtime/src/polyfills/require.js:92:7)
    at factory (/Users/svile/my-app/node_modules/@sinclair/typebox/build/esm/type/object/index.mjs:1:1)
    at loadModuleImplementation (/Users/svile/my-app/node_modules/metro-runtime/src/polyfills/require.js:277:5)
    at guardedLoadModule (/Users/svile/my-app/node_modules/metro-runtime/src/polyfills/require.js:184:12)
    at require (/Users/svile/my-app/node_modules/metro-runtime/src/polyfills/require.js:92:7)
    at factory (/Users/svile/my-app/node_modules/@sinclair/typebox/build/esm/type/mapped/mapped.mjs:12:1)
    at loadModuleImplementation (/Users/svile/my-app/node_modules/metro-runtime/src/polyfills/require.js:277:5)
Web Bundled 5612ms node_modules/expo-router/entry.js (1360 modules)
Web Bundled 54ms node_modules/expo-router/_error.js (1 module)

I was also able to narrow it down to the esm build of TypeBox. If I switch to cjs with the following changes, it works in all web, iOS and android. I also get the types by keeping esm. This can be a temporary workaround, I'll add if I find anything else:

// ...

import { Static } from "@sinclair/typebox";
const { Type } = require('@sinclair/typebox');

// ...

export const TestSchema = Type.Object({
  test: Type.String({
    description: "Test string.",
  }),
});

// ...

@sinclairzx81
Copy link
Owner

sinclairzx81 commented May 22, 2025

@svile Hiya, thanks for the follow up!

Here's the full stack trace for React Native Web, which is also experiencing the same issues as the native device bundles:

at require (/Users/svile/my-app/node_modules/metro-runtime/src/polyfills/require.js:92:7)
at factory (/Users/svile/my-app/node_modules/@sinclair/typebox/build/esm/type/object/index.mjs:1:1)

Yeah, looking at these two lines, it certainly does look like a shadowing conflict related to how metro (or more likely babel) is polyfill-ing for ESM and and running into shadowing issues for Object. Here is the full ESM output for Object. As you can see I am already applying workarounds using _ and var (which helps to avoid shadowing)

import { CreateType } from '../create/type.mjs';
import { Kind } from '../symbols/index.mjs';
// ------------------------------------------------------------------
// TypeGuard
// ------------------------------------------------------------------
import { IsOptional } from '../guard/kind.mjs';
function RequiredKeys(properties) {
    const keys = [];
    for (let key in properties) {
        if (!IsOptional(properties[key]))
            keys.push(key);
    }
    return keys;
}
/** `[Json]` Creates an Object type */
function _Object(properties, options) {
    const required = RequiredKeys(properties);
    const schematic = required.length > 0 ? { [Kind]: 'Object', type: 'object', properties, required } : { [Kind]: 'Object', type: 'object', properties };
    return CreateType(schematic, options);
}
/** `[Json]` Creates an Object type */
export var Object = _Object;

There really isn't much else TypeBox can do here (afaik), but if the CJS output is successful, that might be the way to go.

In the past, users who have hit this problem usually end up visiting project after project up the dependency chain until they reach Babel, where the solution in Babel would be to fully qualify the call with globalThis.Object (perhaps if detecting shadowing conflicts which would be feasible as Babel has access to AST) but they don't appear very receptive to this and there hasn't really been anyone wanting to take ownership to the issue, so sort of at a impasse, which is a bit unfortunate.

From a library stand point though, TypeBox shouldn't really need to be bundler / transpiler aware, and the need for it to prefix with _ and use var is really unfortunate because it's bundler specific trivia appearing in the library. The above code is perfectly valid (even without the _ and var), and renaming Object to something else is infeasible, so there's just not that much room to move.


The only thing I can recommend is perhaps tinkering with Expo configurations, setting the ES target to 2020+ (maybe you can avoid all polyfills with a newer ES target), or submitting an issue to Expo -> Metro -> Babel. I vaguely recall Metro (or Expo) having a configuration workaround (I can't find the link but remember something), so it might be worth chasing up.

Let me know how you go. I am genuinely open to updates in TypeBox just so long as they don't result in API interface changes. If you do find anything, or can advise insights on things happening downstream in these tooling projects, let me know.

Cheers!
S

@svile
Copy link
Author

svile commented May 22, 2025

@sinclairzx81 thank you very much! I'll try your suggestions and will close this as I do agree with you + there is also currently a workaround. I'll likely re-open or comment if I find anything else useful to contribute.

@byCedric
Copy link

byCedric commented Jun 10, 2025

Hi @sinclairzx81! We just received an issue report related to this error: expo/expo#37171. I think this is just a quirk in Metro and how it transpiles the ESM modules. You can see a full explanation here - but the TL;DR; is:

  • In src/type/object/object.ts, the Object is exported as export var Object = _Object
  • Metro uses Babel, and prepends the module with Object.defineProperty(exports, "__esModule", { value: true });
  • Because var Object changes the reference to Object for the whole module, the .defineProperty method doesn't exist anymore
  • This results in the error we are seeing

One thing that could help for Metro specific, which technically is also valid ESM is using something like this instead:

/** `[Json]` Creates an Object type */
-export var Object = _Object
+export { _Object as Object };

Important

If you have multiple exports, this does overwrite all previous export statements. But because src/type/object/object.ts only has 1 single non-type export, this should be fine for this file.
If you want to be on the safe side, you could also add the types to this export object.

This would avoid changing the Object reference within the module to anything other than globalThis.Object, which would allow the Babel-injected Object.defineProperty(..) to work. By using var Object = _Object, the reference to Object is changed to _Object.

Hope this helps!

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

No branches or pull requests

3 participants