@tamagui/static 2.0.0-rc.8 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/dist/check-dep-versions.cjs +201 -96
  2. package/dist/checkDeps.cjs +250 -46
  3. package/dist/constants.cjs +32 -30
  4. package/dist/exports.cjs +20 -13
  5. package/dist/extractor/accessSafe.cjs +25 -23
  6. package/dist/extractor/babelParse.cjs +30 -28
  7. package/dist/extractor/buildClassName.cjs +59 -35
  8. package/dist/extractor/bundle.cjs +187 -101
  9. package/dist/extractor/bundleConfig.cjs +568 -168
  10. package/dist/extractor/concatClassName.cjs +73 -32
  11. package/dist/extractor/createEvaluator.cjs +54 -41
  12. package/dist/extractor/createExtractor.cjs +1405 -574
  13. package/dist/extractor/createLogger.cjs +30 -25
  14. package/dist/extractor/detectModuleFormat.cjs +55 -0
  15. package/dist/extractor/ensureImportingConcat.cjs +31 -25
  16. package/dist/extractor/errors.cjs +12 -10
  17. package/dist/extractor/esbuildAliasPlugin.cjs +28 -16
  18. package/dist/extractor/esbuildTsconfigPaths.cjs +60 -36
  19. package/dist/extractor/evaluateAstNode.cjs +104 -59
  20. package/dist/extractor/extractHelpers.cjs +130 -67
  21. package/dist/extractor/extractMediaStyle.cjs +110 -69
  22. package/dist/extractor/extractToClassNames.cjs +337 -229
  23. package/dist/extractor/extractToNative.cjs +248 -154
  24. package/dist/extractor/findTopmostFunction.cjs +22 -13
  25. package/dist/extractor/generatedUid.cjs +39 -28
  26. package/dist/extractor/getPrefixLogs.cjs +12 -10
  27. package/dist/extractor/getPropValueFromAttributes.cjs +52 -34
  28. package/dist/extractor/getSourceModule.cjs +73 -37
  29. package/dist/extractor/getStaticBindingsForScope.cjs +131 -68
  30. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.cjs +20 -14
  31. package/dist/extractor/hasTopLevelAwait.cjs +62 -0
  32. package/dist/extractor/hoistClassNames.cjs +46 -32
  33. package/dist/extractor/literalToAst.cjs +67 -42
  34. package/dist/extractor/loadFile.cjs +9 -3
  35. package/dist/extractor/loadTamagui.cjs +151 -105
  36. package/dist/extractor/logLines.cjs +27 -19
  37. package/dist/extractor/normalizeTernaries.cjs +66 -44
  38. package/dist/extractor/propsToFontFamilyCache.cjs +15 -11
  39. package/dist/extractor/regenerateConfig.cjs +109 -81
  40. package/dist/extractor/removeUnusedHooks.cjs +73 -41
  41. package/dist/extractor/timer.cjs +23 -14
  42. package/dist/extractor/validHTMLAttributes.cjs +61 -59
  43. package/dist/extractor/watchTamaguiConfig.cjs +35 -23
  44. package/dist/getPragmaOptions.cjs +34 -19
  45. package/dist/helpers/memoize.cjs +24 -16
  46. package/dist/helpers/requireTamaguiCore.cjs +22 -15
  47. package/dist/index.cjs +26 -24
  48. package/dist/registerRequire.cjs +168 -65
  49. package/dist/server.cjs +35 -28
  50. package/dist/types.cjs +7 -5
  51. package/dist/worker.cjs +62 -40
  52. package/package.json +27 -24
  53. package/src/checkDeps.ts +305 -68
  54. package/src/exports.ts +2 -0
  55. package/src/extractor/babelParse.ts +1 -0
  56. package/src/extractor/bundle.ts +172 -40
  57. package/src/extractor/bundleConfig.ts +459 -65
  58. package/src/extractor/concatClassName.ts +37 -20
  59. package/src/extractor/createExtractor.ts +300 -30
  60. package/src/extractor/detectModuleFormat.ts +42 -0
  61. package/src/extractor/esbuildTsconfigPaths.ts +6 -1
  62. package/src/extractor/extractToClassNames.ts +15 -9
  63. package/src/extractor/extractToNative.ts +66 -33
  64. package/src/extractor/hasTopLevelAwait.ts +28 -0
  65. package/src/extractor/loadTamagui.ts +5 -4
  66. package/src/registerRequire.ts +102 -9
  67. package/types/checkDeps.d.ts.map +1 -1
  68. package/types/exports.d.ts +2 -0
  69. package/types/exports.d.ts.map +1 -1
  70. package/types/extractor/babelParse.d.ts.map +1 -1
  71. package/types/extractor/bundle.d.ts +83 -1
  72. package/types/extractor/bundle.d.ts.map +1 -1
  73. package/types/extractor/bundleConfig.d.ts +15 -2
  74. package/types/extractor/bundleConfig.d.ts.map +1 -1
  75. package/types/extractor/createExtractor.d.ts.map +1 -1
  76. package/types/extractor/detectModuleFormat.d.ts +5 -0
  77. package/types/extractor/detectModuleFormat.d.ts.map +1 -0
  78. package/types/extractor/esbuildTsconfigPaths.d.ts +8 -0
  79. package/types/extractor/esbuildTsconfigPaths.d.ts.map +1 -1
  80. package/types/extractor/extractToClassNames.d.ts.map +1 -1
  81. package/types/extractor/extractToNative.d.ts.map +1 -1
  82. package/types/extractor/hasTopLevelAwait.d.ts +2 -0
  83. package/types/extractor/hasTopLevelAwait.d.ts.map +1 -0
  84. package/types/extractor/loadTamagui.d.ts +1 -1
  85. package/types/extractor/loadTamagui.d.ts.map +1 -1
  86. package/types/registerRequire.d.ts.map +1 -1
  87. package/dist/check-dep-versions.js +0 -389
  88. package/dist/check-dep-versions.js.map +0 -6
  89. package/dist/checkDeps.js +0 -60
  90. package/dist/checkDeps.js.map +0 -6
  91. package/dist/constants.js +0 -34
  92. package/dist/constants.js.map +0 -6
  93. package/dist/exports.js +0 -34
  94. package/dist/exports.js.map +0 -6
  95. package/dist/extractor/accessSafe.js +0 -47
  96. package/dist/extractor/accessSafe.js.map +0 -6
  97. package/dist/extractor/babelParse.js +0 -59
  98. package/dist/extractor/babelParse.js.map +0 -6
  99. package/dist/extractor/buildClassName.js +0 -72
  100. package/dist/extractor/buildClassName.js.map +0 -6
  101. package/dist/extractor/bundle.js +0 -135
  102. package/dist/extractor/bundle.js.map +0 -6
  103. package/dist/extractor/bundleConfig.js +0 -352
  104. package/dist/extractor/bundleConfig.js.map +0 -6
  105. package/dist/extractor/concatClassName.js +0 -69
  106. package/dist/extractor/concatClassName.js.map +0 -6
  107. package/dist/extractor/createEvaluator.js +0 -66
  108. package/dist/extractor/createEvaluator.js.map +0 -6
  109. package/dist/extractor/createExtractor.js +0 -1212
  110. package/dist/extractor/createExtractor.js.map +0 -6
  111. package/dist/extractor/createLogger.js +0 -32
  112. package/dist/extractor/createLogger.js.map +0 -6
  113. package/dist/extractor/ensureImportingConcat.js +0 -50
  114. package/dist/extractor/ensureImportingConcat.js.map +0 -6
  115. package/dist/extractor/errors.js +0 -22
  116. package/dist/extractor/errors.js.map +0 -6
  117. package/dist/extractor/esbuildAliasPlugin.js +0 -36
  118. package/dist/extractor/esbuildAliasPlugin.js.map +0 -6
  119. package/dist/extractor/esbuildTsconfigPaths.js +0 -79
  120. package/dist/extractor/esbuildTsconfigPaths.js.map +0 -6
  121. package/dist/extractor/evaluateAstNode.js +0 -99
  122. package/dist/extractor/evaluateAstNode.js.map +0 -6
  123. package/dist/extractor/extractHelpers.js +0 -108
  124. package/dist/extractor/extractHelpers.js.map +0 -6
  125. package/dist/extractor/extractMediaStyle.js +0 -123
  126. package/dist/extractor/extractMediaStyle.js.map +0 -6
  127. package/dist/extractor/extractToClassNames.js +0 -351
  128. package/dist/extractor/extractToClassNames.js.map +0 -6
  129. package/dist/extractor/extractToNative.js +0 -286
  130. package/dist/extractor/extractToNative.js.map +0 -6
  131. package/dist/extractor/findTopmostFunction.js +0 -32
  132. package/dist/extractor/findTopmostFunction.js.map +0 -6
  133. package/dist/extractor/generatedUid.js +0 -42
  134. package/dist/extractor/generatedUid.js.map +0 -6
  135. package/dist/extractor/getPrefixLogs.js +0 -24
  136. package/dist/extractor/getPrefixLogs.js.map +0 -6
  137. package/dist/extractor/getPropValueFromAttributes.js +0 -65
  138. package/dist/extractor/getPropValueFromAttributes.js.map +0 -6
  139. package/dist/extractor/getSourceModule.js +0 -62
  140. package/dist/extractor/getSourceModule.js.map +0 -6
  141. package/dist/extractor/getStaticBindingsForScope.js +0 -145
  142. package/dist/extractor/getStaticBindingsForScope.js.map +0 -6
  143. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js +0 -32
  144. package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js.map +0 -6
  145. package/dist/extractor/hoistClassNames.js +0 -63
  146. package/dist/extractor/hoistClassNames.js.map +0 -6
  147. package/dist/extractor/literalToAst.js +0 -90
  148. package/dist/extractor/literalToAst.js.map +0 -6
  149. package/dist/extractor/loadFile.js +0 -14
  150. package/dist/extractor/loadFile.js.map +0 -6
  151. package/dist/extractor/loadTamagui.js +0 -306
  152. package/dist/extractor/loadTamagui.js.map +0 -6
  153. package/dist/extractor/logLines.js +0 -30
  154. package/dist/extractor/logLines.js.map +0 -6
  155. package/dist/extractor/normalizeTernaries.js +0 -61
  156. package/dist/extractor/normalizeTernaries.js.map +0 -6
  157. package/dist/extractor/propsToFontFamilyCache.js +0 -33
  158. package/dist/extractor/propsToFontFamilyCache.js.map +0 -6
  159. package/dist/extractor/regenerateConfig.js +0 -123
  160. package/dist/extractor/regenerateConfig.js.map +0 -6
  161. package/dist/extractor/removeUnusedHooks.js +0 -72
  162. package/dist/extractor/removeUnusedHooks.js.map +0 -6
  163. package/dist/extractor/timer.js +0 -38
  164. package/dist/extractor/timer.js.map +0 -6
  165. package/dist/extractor/validHTMLAttributes.js +0 -72
  166. package/dist/extractor/validHTMLAttributes.js.map +0 -6
  167. package/dist/extractor/watchTamaguiConfig.js +0 -57
  168. package/dist/extractor/watchTamaguiConfig.js.map +0 -6
  169. package/dist/getPragmaOptions.js +0 -50
  170. package/dist/getPragmaOptions.js.map +0 -6
  171. package/dist/helpers/memoize.js +0 -33
  172. package/dist/helpers/memoize.js.map +0 -6
  173. package/dist/helpers/requireTamaguiCore.js +0 -30
  174. package/dist/helpers/requireTamaguiCore.js.map +0 -6
  175. package/dist/index.js +0 -30
  176. package/dist/index.js.map +0 -6
  177. package/dist/registerRequire.js +0 -100
  178. package/dist/registerRequire.js.map +0 -6
  179. package/dist/server.js +0 -58
  180. package/dist/server.js.map +0 -6
  181. package/dist/setup.js +0 -1
  182. package/dist/setup.js.map +0 -6
  183. package/dist/types.js +0 -14
  184. package/dist/types.js.map +0 -6
  185. package/dist/worker.js +0 -72
  186. package/dist/worker.js.map +0 -6
@@ -1,15 +1,13 @@
1
1
  import generate from '@babel/generator'
2
2
  import traverse from '@babel/traverse'
3
3
  import * as t from '@babel/types'
4
- import { existsSync, readFileSync } from 'node:fs'
4
+ import { createHash } from 'node:crypto'
5
+ import { existsSync, readFileSync, unlinkSync } from 'node:fs'
5
6
  import { basename, dirname, extname, join, relative, sep } from 'node:path'
7
+ import { pathToFileURL } from 'node:url'
6
8
  // @ts-ignore why
7
9
  import { Color, colorLog } from '@tamagui/cli-color'
8
- import {
9
- createTamagui,
10
- type StaticConfig,
11
- type TamaguiInternalConfig,
12
- } from '@tamagui/web'
10
+ import { type StaticConfig, type TamaguiInternalConfig } from '@tamagui/web'
13
11
  import esbuild from 'esbuild'
14
12
  import * as FS from 'fs-extra'
15
13
  import { readFile } from 'node:fs/promises'
@@ -18,7 +16,74 @@ import type { TamaguiOptions } from '../types'
18
16
  import { babelParse } from './babelParse'
19
17
  import { esbuildLoaderConfig, esbundleTamaguiConfig } from './bundle'
20
18
  import { getTamaguiConfigPathFromOptionsConfig } from './getTamaguiConfigPathFromOptionsConfig'
19
+ import { hasTopLevelAwait } from './hasTopLevelAwait'
21
20
  import { requireTamaguiCore } from '../helpers/requireTamaguiCore'
21
+ import { detectModuleFormat } from './detectModuleFormat'
22
+
23
+ // track temp files for cleanup on exit
24
+ const activeTempFiles = new Set<string>()
25
+
26
+ function getDynamicEvalOutfile(name: string, format: 'esm' | 'cjs', contents: string) {
27
+ const ext = format === 'esm' ? 'mjs' : 'cjs'
28
+ const hash = createHash('sha1')
29
+ .update(name)
30
+ .update('\0')
31
+ .update(format)
32
+ .update('\0')
33
+ .update(contents)
34
+ .digest('hex')
35
+ .slice(0, 10)
36
+ return join(process.cwd(), '.tamagui', `dynamic-eval-${hash}-${basename(name)}.${ext}`)
37
+ }
38
+
39
+ function getEsbuildStdinLoader(filePath: string): esbuild.Loader {
40
+ if (filePath.endsWith('.tsx')) return 'tsx'
41
+ if (filePath.endsWith('.ts')) return 'ts'
42
+ if (filePath.endsWith('.jsx')) return 'jsx'
43
+ return 'js'
44
+ }
45
+
46
+ function resolvePackageEntry(packageName: string, format: 'esm' | 'cjs') {
47
+ if (format === 'cjs') {
48
+ return require.resolve(packageName)
49
+ }
50
+
51
+ const packageJsonPath = require.resolve(`${packageName}/package.json`)
52
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
53
+ const packageRoot = dirname(packageJsonPath)
54
+ const exportEntry = packageJson.exports?.['.']
55
+
56
+ const esmEntry =
57
+ exportEntry?.import ||
58
+ exportEntry?.module ||
59
+ exportEntry?.browser ||
60
+ packageJson.module
61
+
62
+ if (typeof esmEntry === 'string') {
63
+ return join(packageRoot, esmEntry)
64
+ }
65
+
66
+ return require.resolve(packageName)
67
+ }
68
+
69
+ function cleanupTempFiles() {
70
+ for (const f of activeTempFiles) {
71
+ try {
72
+ unlinkSync(f)
73
+ } catch {}
74
+ }
75
+ activeTempFiles.clear()
76
+ }
77
+
78
+ process.on('exit', cleanupTempFiles)
79
+ process.on('SIGINT', () => {
80
+ cleanupTempFiles()
81
+ process.exit()
82
+ })
83
+ process.on('SIGTERM', () => {
84
+ cleanupTempFiles()
85
+ process.exit()
86
+ })
22
87
 
23
88
  type NameToPaths = {
24
89
  [key: string]: Set<string>
@@ -55,18 +120,101 @@ const esbuildExtraOptions = {
55
120
  },
56
121
  }
57
122
 
58
- export const esbuildOptions = {
59
- target: 'es2018',
123
+ // plugin to handle ESM-only features when bundling to CJS
124
+ const handleEsmFeaturesPlugin: esbuild.Plugin = {
125
+ name: 'handle-esm-features',
126
+ setup(build) {
127
+ // only apply transforms for CJS output - ESM supports these natively
128
+ const isCjs = build.initialOptions.format === 'cjs' || !build.initialOptions.format
129
+
130
+ build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs)$/ }, (args) => {
131
+ // skip if ESM output - import.meta and top-level await work natively
132
+ if (!isCjs) {
133
+ return null
134
+ }
135
+
136
+ // skip most node_modules
137
+ if (args.path.includes('node_modules') && !args.path.includes('@tamagui')) {
138
+ return null
139
+ }
140
+
141
+ let contents = readFileSync(args.path, 'utf8')
142
+ let modified = false
143
+
144
+ // transform import.meta.env -> process.env (Vite-style env vars)
145
+ if (contents.includes('import.meta.env')) {
146
+ contents = contents.replace(/import\.meta\.env/g, 'process.env')
147
+ modified = true
148
+ }
149
+
150
+ // transform import.meta.url -> "" (not needed for static extraction)
151
+ if (contents.includes('import.meta.url')) {
152
+ contents = contents.replace(/import\.meta\.url/g, '""')
153
+ modified = true
154
+ }
155
+
156
+ // transform import.meta.main -> false
157
+ if (contents.includes('import.meta.main')) {
158
+ contents = contents.replace(/import\.meta\.main/g, 'false')
159
+ modified = true
160
+ }
161
+
162
+ // stub files with top-level await - they're typically runtime-only
163
+ if (hasTopLevelAwait(contents, args.path)) {
164
+ if (process.env.DEBUG?.startsWith('tamagui')) {
165
+ console.info(`[tamagui] stubbing file with top-level await: ${args.path}`)
166
+ }
167
+ return {
168
+ // Keep this as an ESM-shaped stub so esbuild doesn't inline a top-level
169
+ // `module.exports = {}` into the parent bundle and wipe its exports.
170
+ contents: `// stubbed - contains top-level await\nexport default {}`,
171
+ loader: 'js',
172
+ }
173
+ }
174
+
175
+ if (modified) {
176
+ return {
177
+ contents,
178
+ loader: args.path.endsWith('.tsx')
179
+ ? 'tsx'
180
+ : args.path.endsWith('.ts')
181
+ ? 'ts'
182
+ : args.path.endsWith('.jsx')
183
+ ? 'jsx'
184
+ : 'js',
185
+ }
186
+ }
187
+
188
+ return null
189
+ })
190
+ },
191
+ }
192
+
193
+ // base options for transformSync (no plugins)
194
+ const esbuildTransformOptions = {
195
+ target: 'es2022',
60
196
  format: 'cjs',
61
197
  jsx: 'automatic',
62
198
  platform: 'node',
63
199
  ...esbuildExtraOptions,
200
+ } satisfies esbuild.TransformOptions
201
+
202
+ // options for buildSync - NO plugins (buildSync doesn't support plugins)
203
+ export const esbuildOptions = {
204
+ ...esbuildTransformOptions,
205
+ } satisfies esbuild.BuildOptions
206
+
207
+ // options for async build (with plugins)
208
+ export const esbuildOptionsWithPlugins = {
209
+ ...esbuildTransformOptions,
210
+ plugins: [handleEsmFeaturesPlugin],
64
211
  } satisfies esbuild.BuildOptions
65
212
 
66
213
  export type BundledConfig = Exclude<Awaited<ReturnType<typeof bundleConfig>>, undefined>
67
214
 
68
215
  // will use cached one if watching
69
216
  let currentBundle: BundledConfig | null = null
217
+ let currentBundleKey = ''
70
218
  let isBundling = false
71
219
  let lastBundle: BundledConfig | null = null
72
220
  const waitForBundle = new Set<Function>()
@@ -83,22 +231,35 @@ let loadedConfig: TamaguiInternalConfig | null = null
83
231
 
84
232
  export const getLoadedConfig = () => loadedConfig
85
233
 
234
+ function getBundleKey(props: TamaguiOptions) {
235
+ return JSON.stringify({
236
+ components: props.components,
237
+ config: props.config,
238
+ platform: props.platform,
239
+ })
240
+ }
241
+
86
242
  export async function getBundledConfig(props: TamaguiOptions, rebuild = false) {
243
+ const bundleKey = getBundleKey(props)
87
244
  if (isBundling) {
88
245
  await new Promise((res) => {
89
246
  waitForBundle.add(res)
90
247
  })
91
- } else if (!currentBundle || rebuild) {
248
+ }
249
+
250
+ if (!currentBundle || currentBundleKey !== bundleKey || rebuild) {
92
251
  return await bundleConfig(props)
93
252
  }
253
+
94
254
  return currentBundle
95
255
  }
96
256
 
97
257
  global.tamaguiLastLoaded ||= 0
98
258
 
99
- function updateLastLoaded(config: any) {
259
+ function updateLastLoaded(config: any, bundleKey: string) {
100
260
  global.tamaguiLastLoaded = Date.now()
101
261
  global.tamaguiLastBundledConfig = config
262
+ global.tamaguiLastBundledConfigKey = bundleKey
102
263
  }
103
264
 
104
265
  let hasBundledOnce = false
@@ -109,8 +270,13 @@ let hasBundledOnce = false
109
270
  let hasLoggedBuild = false
110
271
 
111
272
  export async function bundleConfig(props: TamaguiOptions) {
273
+ const bundleKey = getBundleKey(props)
112
274
  // webpack is calling this a ton for no reason
113
- if (global.tamaguiLastBundledConfig && Date.now() - global.tamaguiLastLoaded < 3000) {
275
+ if (
276
+ global.tamaguiLastBundledConfig &&
277
+ global.tamaguiLastBundledConfigKey === bundleKey &&
278
+ Date.now() - global.tamaguiLastLoaded < 3000
279
+ ) {
114
280
  // just loaded recently
115
281
  return global.tamaguiLastBundledConfig
116
282
  }
@@ -122,17 +288,31 @@ export async function bundleConfig(props: TamaguiOptions) {
122
288
  ? getTamaguiConfigPathFromOptionsConfig(props.config)
123
289
  : ''
124
290
  const tmpDir = join(process.cwd(), '.tamagui')
125
- const configOutPath = join(tmpDir, `tamagui.config.cjs`)
291
+ // detect module format from config entry point
292
+ const configFormat = configEntry ? detectModuleFormat(configEntry) : 'cjs'
293
+ const configExt = configFormat === 'esm' ? '.mjs' : '.cjs'
294
+ const configOutPath = join(tmpDir, `tamagui.config${configExt}`)
126
295
  const baseComponents = (props.components || []).filter((x) => x !== '@tamagui/core')
127
- const componentOutPaths = baseComponents.map((componentModule) =>
128
- join(
296
+ // detect format per component module
297
+ const componentFormats: Array<'esm' | 'cjs'> = baseComponents.map((mod) => {
298
+ try {
299
+ const pkgJson = require.resolve(mod + '/package.json')
300
+ const pkg = JSON.parse(readFileSync(pkgJson, 'utf-8'))
301
+ return pkg.type === 'module' ? 'esm' : 'cjs'
302
+ } catch {
303
+ return 'cjs'
304
+ }
305
+ })
306
+ const componentOutPaths = baseComponents.map((componentModule, i) => {
307
+ const ext = componentFormats[i] === 'esm' ? '.mjs' : '.cjs'
308
+ return join(
129
309
  tmpDir,
130
310
  `${componentModule
131
311
  .split(sep)
132
312
  .join('-')
133
- .replace(/[^a-z0-9]+/gi, '')}-components.config.cjs`
313
+ .replace(/[^a-z0-9]+/gi, '')}-components.config${ext}`
134
314
  )
135
- )
315
+ })
136
316
 
137
317
  if (
138
318
  process.env.NODE_ENV === 'development' &&
@@ -141,7 +321,29 @@ export async function bundleConfig(props: TamaguiOptions) {
141
321
  console.info(`Building config entry`, configEntry)
142
322
  }
143
323
 
144
- if (!props.disableInitialBuild) {
324
+ // check if ALL output files (config + components) already exist and are recent
325
+ // (built by another worker) - this prevents duplicate builds across worker threads
326
+ // we must check ALL files, not just the config, to avoid a race where another
327
+ // worker has written the config but not yet finished writing component files
328
+ let shouldBuild = !props.disableInitialBuild
329
+ if (shouldBuild && props.config) {
330
+ const allOutFiles = [configOutPath, ...componentOutPaths]
331
+ try {
332
+ const stats = await Promise.all(
333
+ allOutFiles.map((f) => FS.stat(f).catch(() => null))
334
+ )
335
+ const allExistAndRecent = stats.every(
336
+ (s) => s !== null && Date.now() - s.mtimeMs < 3000
337
+ )
338
+ if (allExistAndRecent) {
339
+ shouldBuild = false
340
+ }
341
+ } catch {
342
+ // something went wrong checking files, just build
343
+ }
344
+ }
345
+
346
+ if (shouldBuild) {
145
347
  // build them to node-compat versions
146
348
  try {
147
349
  await FS.ensureDir(tmpDir)
@@ -158,7 +360,8 @@ export async function bundleConfig(props: TamaguiOptions) {
158
360
  entryPoints: [configEntry],
159
361
  external,
160
362
  outfile: configOutPath,
161
- target: 'node20',
363
+ target: 'node24',
364
+ format: configFormat,
162
365
  ...esbuildExtraOptions,
163
366
  },
164
367
  props.platform || 'web'
@@ -171,7 +374,8 @@ export async function bundleConfig(props: TamaguiOptions) {
171
374
  resolvePlatformSpecificEntries: true,
172
375
  external,
173
376
  outfile: componentOutPaths[i],
174
- target: 'node20',
377
+ target: 'node24',
378
+ format: componentFormats[i],
175
379
  ...esbuildExtraOptions,
176
380
  },
177
381
  props.platform || 'web'
@@ -201,25 +405,32 @@ export async function bundleConfig(props: TamaguiOptions) {
201
405
  }
202
406
  }
203
407
 
204
- let out
205
- const { unregister } = registerRequire(props.platform || 'web')
206
- try {
207
- if (hasBundledOnce) {
208
- // this did cause mini-css-extract plugin to freak out
209
- // clear cache to get new files
210
- for (const key in require.cache) {
211
- // avoid clearing core/web it seems to break things
212
- if (!/(core|web)[/\\]dist/.test(key)) {
213
- delete require.cache[key]
214
- }
408
+ // clear specific output file caches so we pick up the fresh (or newly discovered) build
409
+ // only clear the built output files - not all require.cache entries, since that breaks
410
+ // external requires like @tamagui/config/v3 that are externalized in the bundled CJS
411
+ if (hasBundledOnce) {
412
+ try {
413
+ delete require.cache[require.resolve(configOutPath)]
414
+ } catch {
415
+ // file may not exist yet
416
+ }
417
+ for (const p of componentOutPaths) {
418
+ try {
419
+ delete require.cache[require.resolve(p)]
420
+ } catch {
421
+ // file may not exist yet
215
422
  }
216
- } else {
217
- hasBundledOnce = true
218
423
  }
424
+ } else {
425
+ hasBundledOnce = true
426
+ }
219
427
 
428
+ let out: any
429
+ if (configFormat === 'esm') {
430
+ // use file:// URL for proper ESM resolution
431
+ out = await import(pathToFileURL(configOutPath).href)
432
+ } else {
220
433
  out = require(configOutPath)
221
- } finally {
222
- unregister()
223
434
  }
224
435
 
225
436
  // try and find .config, even if on .default
@@ -232,6 +443,13 @@ export async function bundleConfig(props: TamaguiOptions) {
232
443
  throw new Error(`No config: ${config}`)
233
444
  }
234
445
 
446
+ // check for ProxyWorm - indicates a module loading error
447
+ if (config._isProxyWorm) {
448
+ throw new Error(
449
+ `Got a proxied config - likely a module loading error. Set DEBUG=tamagui for details.`
450
+ )
451
+ }
452
+
235
453
  loadedConfig = config
236
454
 
237
455
  if (!config.parsed) {
@@ -244,7 +462,7 @@ export async function bundleConfig(props: TamaguiOptions) {
244
462
  await writeTamaguiCSS(props.outputCSS, config)
245
463
  }
246
464
 
247
- let components = loadComponents({
465
+ let components = await loadComponents({
248
466
  ...props,
249
467
  components: componentOutPaths,
250
468
  })
@@ -284,7 +502,8 @@ export async function bundleConfig(props: TamaguiOptions) {
284
502
  }
285
503
 
286
504
  currentBundle = res
287
- updateLastLoaded(res)
505
+ currentBundleKey = bundleKey
506
+ updateLastLoaded(res, bundleKey)
288
507
 
289
508
  return res
290
509
  } catch (err: any) {
@@ -322,14 +541,20 @@ export async function writeTamaguiCSS(outputCSS: string, config: TamaguiInternal
322
541
  }
323
542
  }
324
543
 
325
- export function loadComponents(props: TamaguiOptions, forceExports = false) {
326
- const coreComponents = getCoreComponents(props)
327
- const otherComponents = loadComponentsInner(props, forceExports)
544
+ export async function loadComponents(props: TamaguiOptions, forceExports = false) {
545
+ const coreComponents = getCoreComponentsSync(props)
546
+ const otherComponents = await loadComponentsInner(props, forceExports)
547
+ return [...coreComponents, ...(otherComponents || [])]
548
+ }
549
+
550
+ export function loadComponentsSync(props: TamaguiOptions, forceExports = false) {
551
+ const coreComponents = getCoreComponentsSync(props)
552
+ const otherComponents = loadComponentsInnerSync(props, forceExports)
328
553
  return [...coreComponents, ...(otherComponents || [])]
329
554
  }
330
555
 
331
- function getCoreComponents(props: TamaguiOptions) {
332
- const loaded = loadComponentsInner({
556
+ function getCoreComponentsSync(props: TamaguiOptions) {
557
+ const loaded = loadComponentsInnerSync({
333
558
  ...props,
334
559
  components: ['@tamagui/core'],
335
560
  })
@@ -347,13 +572,172 @@ function getCoreComponents(props: TamaguiOptions) {
347
572
  ]
348
573
  }
349
574
 
350
- export function loadComponentsInner(
575
+ export async function loadComponentsInner(
576
+ props: TamaguiOptions,
577
+ forceExports = false
578
+ ): Promise<null | LoadedComponents[]> {
579
+ const componentsModules = props.components || []
580
+
581
+ const key = componentsModules.join('\0')
582
+
583
+ if (!forceExports && cacheComponents[key]) {
584
+ return cacheComponents[key]
585
+ }
586
+
587
+ const { unregister } = registerRequire(props.platform || 'web', {
588
+ proxyWormImports: forceExports,
589
+ })
590
+
591
+ try {
592
+ const results: LoadedComponents[] = []
593
+
594
+ for (const name of componentsModules) {
595
+ const extension = extname(name)
596
+ const isLocal = Boolean(extension)
597
+ const isDynamic = isLocal && forceExports
598
+ const format = isLocal ? detectModuleFormat(name) : ('cjs' as const)
599
+
600
+ const fileContents = isDynamic ? readFileSync(name, 'utf-8') : ''
601
+ let loadModule = name
602
+ let writtenContents = fileContents
603
+ let didBabel = false
604
+
605
+ const attemptLoad = async ({ forceExports = false } = {}) => {
606
+ if (isDynamic) {
607
+ writtenContents = forceExports
608
+ ? transformAddExports(babelParse(esbuildit(fileContents, 'modern'), name))
609
+ : fileContents
610
+ loadModule = getDynamicEvalOutfile(name, format, writtenContents)
611
+
612
+ FS.ensureDirSync(dirname(loadModule))
613
+ activeTempFiles.add(loadModule)
614
+
615
+ await esbuild.build({
616
+ ...esbuildOptionsWithPlugins,
617
+ format,
618
+ outfile: loadModule,
619
+ stdin: {
620
+ contents: writtenContents,
621
+ resolveDir: dirname(name),
622
+ sourcefile: name,
623
+ loader: getEsbuildStdinLoader(name),
624
+ },
625
+ alias: {
626
+ 'react-native': resolvePackageEntry(
627
+ '@tamagui/react-native-web-lite',
628
+ format
629
+ ),
630
+ '@tamagui/react-native-web-lite': resolvePackageEntry(
631
+ '@tamagui/react-native-web-lite',
632
+ format
633
+ ),
634
+ '@tamagui/react-native-web-internals': resolvePackageEntry(
635
+ '@tamagui/react-native-web-internals',
636
+ format
637
+ ),
638
+ },
639
+ bundle: true,
640
+ packages: 'external',
641
+ allowOverwrite: true,
642
+ sourcemap: false,
643
+ loader: esbuildLoaderConfig,
644
+ })
645
+ }
646
+
647
+ if (process.env.DEBUG === 'tamagui') {
648
+ console.info(`loadModule`, loadModule, format)
649
+ }
650
+
651
+ let moduleResult: any
652
+ if (format === 'esm') {
653
+ // use file:// URL for proper ESM resolution
654
+ moduleResult = await import(pathToFileURL(loadModule).href)
655
+ } else {
656
+ moduleResult = require(loadModule)
657
+ }
658
+
659
+ if (!forceExports) {
660
+ setRequireResult(name, moduleResult)
661
+ }
662
+
663
+ const nameToInfo = getComponentStaticConfigByName(
664
+ name,
665
+ interopDefaultExport(moduleResult)
666
+ )
667
+
668
+ return {
669
+ moduleName: name,
670
+ nameToInfo,
671
+ }
672
+ }
673
+
674
+ const dispose = () => {
675
+ if (isDynamic) {
676
+ FS.removeSync(loadModule)
677
+ activeTempFiles.delete(loadModule)
678
+ }
679
+ }
680
+
681
+ let loaded: LoadedComponents | LoadedComponents[] | undefined
682
+
683
+ try {
684
+ loaded = await attemptLoad({ forceExports: true })
685
+ didBabel = true
686
+ } catch (err) {
687
+ console.info('babel err', err, writtenContents)
688
+ writtenContents = fileContents
689
+ if (process.env.DEBUG?.startsWith('tamagui')) {
690
+ console.info(`Error parsing babel likely`, err)
691
+ }
692
+
693
+ try {
694
+ loaded = await attemptLoad({ forceExports: false })
695
+ } catch (err2) {
696
+ if (process.env.TAMAGUI_ENABLE_WARN_DYNAMIC_LOAD) {
697
+ console.info(
698
+ `\nTamagui attempted but failed to dynamically optimize components in:\n ${name}\n`
699
+ )
700
+ console.info(err2)
701
+ console.info(
702
+ `At: ${loadModule}`,
703
+ `\ndidBabel: ${didBabel}`,
704
+ `\nIn:`,
705
+ writtenContents,
706
+ `\nisDynamic: `,
707
+ isDynamic
708
+ )
709
+ }
710
+ loaded = []
711
+ }
712
+ } finally {
713
+ dispose()
714
+ }
715
+
716
+ if (Array.isArray(loaded)) {
717
+ results.push(...loaded)
718
+ } else if (loaded) {
719
+ results.push(loaded)
720
+ }
721
+ }
722
+
723
+ cacheComponents[key] = results
724
+ return results
725
+ } catch (err: any) {
726
+ console.info(`Tamagui error bundling components`, err.message, err.stack)
727
+ return null
728
+ } finally {
729
+ unregister()
730
+ }
731
+ }
732
+
733
+ // sync version - uses cjs format for buildSync (no plugin support)
734
+ export function loadComponentsInnerSync(
351
735
  props: TamaguiOptions,
352
736
  forceExports = false
353
737
  ): null | LoadedComponents[] {
354
738
  const componentsModules = props.components || []
355
739
 
356
- const key = componentsModules.join('')
740
+ const key = componentsModules.join('\0')
357
741
 
358
742
  if (!forceExports && cacheComponents[key]) {
359
743
  return cacheComponents[key]
@@ -370,32 +754,46 @@ export function loadComponentsInner(
370
754
  const isDynamic = isLocal && forceExports
371
755
 
372
756
  const fileContents = isDynamic ? readFileSync(name, 'utf-8') : ''
373
- const loadModule = isDynamic
374
- ? join(dirname(name), `.tamagui-dynamic-eval-${basename(name)}.tsx`)
375
- : name
757
+ let loadModule = name
376
758
  let writtenContents = fileContents
377
759
  let didBabel = false
378
760
 
379
761
  function attemptLoad({ forceExports = false } = {}) {
380
- // need to write to tsx to enable reading it properly (:/ esbuild-register)
381
762
  if (isDynamic) {
382
763
  writtenContents = forceExports
383
764
  ? transformAddExports(babelParse(esbuildit(fileContents, 'modern'), name))
384
765
  : fileContents
766
+ loadModule = getDynamicEvalOutfile(name, 'cjs', writtenContents)
385
767
 
386
- FS.writeFileSync(loadModule, writtenContents)
768
+ FS.ensureDirSync(dirname(loadModule))
769
+ activeTempFiles.add(loadModule)
387
770
 
388
771
  esbuild.buildSync({
389
772
  ...esbuildOptions,
390
- entryPoints: [loadModule],
391
773
  outfile: loadModule,
774
+ stdin: {
775
+ contents: writtenContents,
776
+ resolveDir: dirname(name),
777
+ sourcefile: name,
778
+ loader: getEsbuildStdinLoader(name),
779
+ },
392
780
  alias: {
393
- 'react-native': require.resolve('@tamagui/react-native-web-lite'),
781
+ 'react-native': resolvePackageEntry(
782
+ '@tamagui/react-native-web-lite',
783
+ 'esm'
784
+ ),
785
+ '@tamagui/react-native-web-lite': resolvePackageEntry(
786
+ '@tamagui/react-native-web-lite',
787
+ 'esm'
788
+ ),
789
+ '@tamagui/react-native-web-internals': resolvePackageEntry(
790
+ '@tamagui/react-native-web-internals',
791
+ 'esm'
792
+ ),
394
793
  },
395
794
  bundle: true,
396
795
  packages: 'external',
397
796
  allowOverwrite: true,
398
- // logLevel: 'silent',
399
797
  sourcemap: false,
400
798
  loader: esbuildLoaderConfig,
401
799
  })
@@ -423,18 +821,18 @@ export function loadComponentsInner(
423
821
  }
424
822
 
425
823
  const dispose = () => {
426
- isDynamic && FS.removeSync(loadModule)
824
+ if (isDynamic) {
825
+ FS.removeSync(loadModule)
826
+ activeTempFiles.delete(loadModule)
827
+ }
427
828
  }
428
829
 
429
830
  try {
430
- const res = attemptLoad({
431
- forceExports: true,
432
- })
831
+ const res = attemptLoad({ forceExports: true })
433
832
  didBabel = true
434
833
  return res
435
834
  } catch (err) {
436
835
  console.info('babel err', err, writtenContents)
437
- // ok
438
836
  writtenContents = fileContents
439
837
  if (process.env.DEBUG?.startsWith('tamagui')) {
440
838
  console.info(`Error parsing babel likely`, err)
@@ -444,16 +842,12 @@ export function loadComponentsInner(
444
842
  }
445
843
 
446
844
  try {
447
- return attemptLoad({
448
- forceExports: false,
449
- })
845
+ return attemptLoad({ forceExports: false })
450
846
  } catch (err) {
451
847
  if (process.env.TAMAGUI_ENABLE_WARN_DYNAMIC_LOAD) {
452
- console.info(`
453
-
454
- Tamagui attempted but failed to dynamically optimize components in:
455
- ${name}
456
- `)
848
+ console.info(
849
+ `\nTamagui attempted but failed to dynamically optimize components in:\n ${name}\n`
850
+ )
457
851
  console.info(err)
458
852
  console.info(
459
853
  `At: ${loadModule}`,
@@ -481,7 +875,7 @@ Tamagui attempted but failed to dynamically optimize components in:
481
875
 
482
876
  const esbuildit = (src: string, target?: 'modern') => {
483
877
  return esbuild.transformSync(src, {
484
- ...esbuildOptions,
878
+ ...esbuildTransformOptions,
485
879
  ...(target === 'modern' && {
486
880
  target: 'es2022',
487
881
  jsx: 'automatic',