import { register } from 'esbuild-register/dist/node' import { esbuildIgnoreFilesRegex } from './extractor/bundle' import { requireTamaguiCore } from './helpers/requireTamaguiCore' import type { TamaguiPlatform } from './types' const nameToPaths = {} export const getNameToPaths = () => nameToPaths const Module = require('node:module') const proxyWorm = require('@tamagui/proxy-worm') let isRegistered = false let og: any const whitelisted = { react: true, } const compiled = {} export function setRequireResult(name: string, result: any) { compiled[name] = result } function getStaticExtractionStub(path: string) { switch (path) { case 'expo-constants': return { __esModule: true, default: { executionEnvironment: null, }, ExecutionEnvironment: { Bare: 'bare', Standalone: 'standalone', StoreClient: 'storeClient', }, } case 'expo-updates': return { __esModule: true, default: { isEnabled: false, isUsingEmbeddedAssets: true, }, checkForUpdateAsync: async () => ({ isAvailable: false }), fetchUpdateAsync: async () => ({ isNew: false }), reloadAsync: async () => {}, } default: return null } } export function registerRequire( platform: TamaguiPlatform, { proxyWormImports } = { proxyWormImports: false, } ) { // already registered if (isRegistered) { return { tamaguiRequire: require, unregister: () => {}, } } // capture original resolve BEFORE esbuild-register patches it // so we can use Node's native exports resolution for @tamagui packages const originalResolveFilename = Module._resolveFilename const { unregister } = register({ hookIgnoreNodeModules: false, // don't transform @tamagui packages - they have pre-built dist files hookMatcher: (filename) => { if ( filename.includes('@tamagui') || /\/tamagui\/code\/(core|ui|packages)\//.test(filename) ) { return false } return true }, }) // esbuild-register's registerTsconfigPaths replaces Module._resolveFilename // but tsconfig paths resolution bypasses Node's package exports // we need to restore Node's native resolution for @tamagui packages const tsconfigPatchedResolve = Module._resolveFilename Module._resolveFilename = function (request: string, ...args: any[]) { // for @tamagui packages, use Node's native resolution (respects exports) if (request.startsWith('@tamagui/')) { return originalResolveFilename.call(this, request, ...args) } // for everything else, use tsconfig-paths resolution return tsconfigPatchedResolve.call(this, request, ...args) } if (!og) { og = Module.prototype.require // capture esbuild require } isRegistered = true Module.prototype.require = tamaguiRequire function tamaguiRequire(this: any, path: string) { const staticExtractionStub = getStaticExtractionStub(path) if (staticExtractionStub) { return staticExtractionStub } if (path === 'tamagui' && platform === 'native') { return og.apply(this, ['tamagui/native']) } if (path === '@tamagui/core') { return requireTamaguiCore(platform, (path) => { return og.apply(this, [path]) }) } if ( path in knownIgnorableModules || path.startsWith('react-native-reanimated') || esbuildIgnoreFilesRegex.test(path) ) { return proxyWorm } if (path in compiled) { return compiled[path] } if (path === 'react-native-svg') { return og.apply(this, ['@tamagui/react-native-svg']) } if (path === 'react-native/package.json') { return og.apply(this, ['react-native-web/package.json']) } if ( path === '@tamagui/react-native-web-lite' || path === 'react-native' || path.startsWith('react-native/') ) { try { return og.apply('react-native') } catch { return og.apply(this, ['@tamagui/react-native-web-lite']) } } if (!whitelisted[path]) { if (proxyWormImports && !path.includes('.tamagui-dynamic-eval')) { // allow tamagui and its sub-packages through - they re-export components // with staticConfig needed for dynamic eval optimization. // also allow requires FROM within tamagui packages (relative imports like ./Separator.cjs) const callerFile = this?.filename || this?.id || '' const isFromTamaguiPkg = callerFile.includes('@tamagui') || callerFile.includes('node_modules/tamagui/') const isFromStaticLoader = !callerFile || callerFile === '.' || callerFile === '[eval]' || callerFile.endsWith('/[eval]') || callerFile.includes('/code/compiler/static/') || callerFile.includes('/.tamagui/') if ( path === 'tamagui' || path.startsWith('@tamagui/') || isFromTamaguiPkg || isFromStaticLoader ) { return og.apply(this, [path]) } return proxyWorm } } try { const out = og.apply(this, arguments) // only for studio disable for now // if (!nameToPaths[path]) { // if (out && typeof out === 'object') { // for (const key in out) { // try { // const conf = out[key]?.staticConfig as StaticConfig // if (conf) { // if (conf.componentName) { // nameToPaths[conf.componentName] ??= new Set() // const fullName = path.startsWith('.') // ? join(`${this.path.replace(/dist(\/cjs)?/, 'src')}`, path) // : path // nameToPaths[conf.componentName].add(fullName) // } else { // // console.log('no name component', path) // } // } // } catch { // // ok // } // } // } // } return out } catch (err: any) { if ( !process.env.TAMAGUI_ENABLE_WARN_DYNAMIC_LOAD && path.includes('tamagui-dynamic-eval') ) { // ok, dynamic eval fails return } if (allowedIgnores[path] || IGNORES === 'true') { // ignore } else if (!process.env.TAMAGUI_SHOW_FULL_BUNDLE_ERRORS && !process.env.DEBUG) { if (hasWarnedForModules.has(path)) { // ignore } else { hasWarnedForModules.add(path) } } else { /** * Allow errors to happen, we're just reading config and components but sometimes external modules cause problems * We can't fix every problem, so just swap them out with proxyWorm which is a sort of generic object that can be read. */ console.warn( ` [tamagui] skipped "${path}" (set TAMAGUI_IGNORE_BUNDLE_ERRORS="${path}" to silence)` ) } return proxyWorm } } return { tamaguiRequire, unregister: () => { if (hasWarnedForModules.size) { console.info( ` [tamagui] skipped loading ${hasWarnedForModules.size} module, see: https://tamagui.dev/docs/intro/errors#warning-001` ) hasWarnedForModules.clear() } unregister() isRegistered = false Module.prototype.require = og }, } } const IGNORES = process.env.TAMAGUI_IGNORE_BUNDLE_ERRORS const extraIgnores = IGNORES === 'true' ? [] : process.env.TAMAGUI_IGNORE_BUNDLE_ERRORS?.split(',') const knownIgnorableModules = { '@gorhom/bottom-sheet': true, 'expo-modules': true, solito: true, 'expo-linear-gradient': true, '@expo/vector-icons': true, 'tamagui/linear-gradient': true, // animation libraries not needed for static extraction '@emotion/is-prop-valid': true, 'framer-motion': true, motion: true, ...Object.fromEntries(extraIgnores?.map((k) => [k, true]) || []), } const hasWarnedForModules = new Set() const allowedIgnores = { 'expo-constants': true, './ExpoHaptics': true, './js/MaskedView': true, }