@tamagui/static 2.0.0-rc.3 → 2.0.0-rc.31
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.
- package/dist/checkDeps.cjs +164 -31
- package/dist/exports.cjs +3 -0
- package/dist/extractor/bundle.cjs +72 -35
- package/dist/extractor/bundleConfig.cjs +219 -35
- package/dist/extractor/createExtractor.cjs +170 -28
- package/dist/extractor/detectModuleFormat.cjs +49 -0
- package/dist/extractor/esbuildTsconfigPaths.cjs +3 -1
- package/dist/extractor/extractToClassNames.cjs +7 -5
- package/dist/extractor/extractToNative.cjs +7 -8
- package/dist/extractor/loadTamagui.cjs +1 -1
- package/dist/getPragmaOptions.cjs +7 -3
- package/dist/registerRequire.cjs +23 -14
- package/package.json +26 -22
- package/src/checkDeps.ts +305 -68
- package/src/exports.ts +1 -0
- package/src/extractor/bundle.ts +140 -37
- package/src/extractor/bundleConfig.ts +435 -61
- package/src/extractor/createExtractor.ts +261 -41
- package/src/extractor/detectModuleFormat.ts +42 -0
- package/src/extractor/esbuildTsconfigPaths.ts +6 -1
- package/src/extractor/extractToClassNames.ts +15 -9
- package/src/extractor/extractToNative.ts +32 -25
- package/src/extractor/loadTamagui.ts +2 -2
- package/src/getPragmaOptions.ts +6 -1
- package/src/registerRequire.ts +40 -8
- package/types/checkDeps.d.ts.map +1 -1
- package/types/exports.d.ts +1 -0
- package/types/exports.d.ts.map +1 -1
- package/types/extractor/bundle.d.ts +83 -1
- package/types/extractor/bundle.d.ts.map +1 -1
- package/types/extractor/bundleConfig.d.ts +15 -2
- package/types/extractor/bundleConfig.d.ts.map +1 -1
- package/types/extractor/createExtractor.d.ts.map +1 -1
- package/types/extractor/detectModuleFormat.d.ts +5 -0
- package/types/extractor/detectModuleFormat.d.ts.map +1 -0
- package/types/extractor/esbuildTsconfigPaths.d.ts +8 -0
- package/types/extractor/esbuildTsconfigPaths.d.ts.map +1 -1
- package/types/extractor/extractToClassNames.d.ts.map +1 -1
- package/types/extractor/extractToNative.d.ts.map +1 -1
- package/types/getPragmaOptions.d.ts.map +1 -1
- package/types/registerRequire.d.ts.map +1 -1
- package/dist/check-dep-versions.js +0 -389
- package/dist/check-dep-versions.js.map +0 -6
- package/dist/checkDeps.js +0 -60
- package/dist/checkDeps.js.map +0 -6
- package/dist/constants.js +0 -34
- package/dist/constants.js.map +0 -6
- package/dist/exports.js +0 -34
- package/dist/exports.js.map +0 -6
- package/dist/extractor/accessSafe.js +0 -47
- package/dist/extractor/accessSafe.js.map +0 -6
- package/dist/extractor/babelParse.js +0 -59
- package/dist/extractor/babelParse.js.map +0 -6
- package/dist/extractor/buildClassName.js +0 -72
- package/dist/extractor/buildClassName.js.map +0 -6
- package/dist/extractor/bundle.js +0 -135
- package/dist/extractor/bundle.js.map +0 -6
- package/dist/extractor/bundleConfig.js +0 -352
- package/dist/extractor/bundleConfig.js.map +0 -6
- package/dist/extractor/concatClassName.js +0 -69
- package/dist/extractor/concatClassName.js.map +0 -6
- package/dist/extractor/createEvaluator.js +0 -66
- package/dist/extractor/createEvaluator.js.map +0 -6
- package/dist/extractor/createExtractor.js +0 -1215
- package/dist/extractor/createExtractor.js.map +0 -6
- package/dist/extractor/createLogger.js +0 -32
- package/dist/extractor/createLogger.js.map +0 -6
- package/dist/extractor/ensureImportingConcat.js +0 -50
- package/dist/extractor/ensureImportingConcat.js.map +0 -6
- package/dist/extractor/errors.js +0 -22
- package/dist/extractor/errors.js.map +0 -6
- package/dist/extractor/esbuildAliasPlugin.js +0 -36
- package/dist/extractor/esbuildAliasPlugin.js.map +0 -6
- package/dist/extractor/esbuildTsconfigPaths.js +0 -79
- package/dist/extractor/esbuildTsconfigPaths.js.map +0 -6
- package/dist/extractor/evaluateAstNode.js +0 -99
- package/dist/extractor/evaluateAstNode.js.map +0 -6
- package/dist/extractor/extractHelpers.js +0 -108
- package/dist/extractor/extractHelpers.js.map +0 -6
- package/dist/extractor/extractMediaStyle.js +0 -123
- package/dist/extractor/extractMediaStyle.js.map +0 -6
- package/dist/extractor/extractToClassNames.js +0 -351
- package/dist/extractor/extractToClassNames.js.map +0 -6
- package/dist/extractor/extractToNative.js +0 -286
- package/dist/extractor/extractToNative.js.map +0 -6
- package/dist/extractor/findTopmostFunction.js +0 -32
- package/dist/extractor/findTopmostFunction.js.map +0 -6
- package/dist/extractor/generatedUid.js +0 -42
- package/dist/extractor/generatedUid.js.map +0 -6
- package/dist/extractor/getPrefixLogs.js +0 -24
- package/dist/extractor/getPrefixLogs.js.map +0 -6
- package/dist/extractor/getPropValueFromAttributes.js +0 -65
- package/dist/extractor/getPropValueFromAttributes.js.map +0 -6
- package/dist/extractor/getSourceModule.js +0 -62
- package/dist/extractor/getSourceModule.js.map +0 -6
- package/dist/extractor/getStaticBindingsForScope.js +0 -145
- package/dist/extractor/getStaticBindingsForScope.js.map +0 -6
- package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js +0 -32
- package/dist/extractor/getTamaguiConfigPathFromOptionsConfig.js.map +0 -6
- package/dist/extractor/hoistClassNames.js +0 -63
- package/dist/extractor/hoistClassNames.js.map +0 -6
- package/dist/extractor/literalToAst.js +0 -90
- package/dist/extractor/literalToAst.js.map +0 -6
- package/dist/extractor/loadFile.js +0 -14
- package/dist/extractor/loadFile.js.map +0 -6
- package/dist/extractor/loadTamagui.js +0 -306
- package/dist/extractor/loadTamagui.js.map +0 -6
- package/dist/extractor/logLines.js +0 -30
- package/dist/extractor/logLines.js.map +0 -6
- package/dist/extractor/normalizeTernaries.js +0 -61
- package/dist/extractor/normalizeTernaries.js.map +0 -6
- package/dist/extractor/propsToFontFamilyCache.js +0 -33
- package/dist/extractor/propsToFontFamilyCache.js.map +0 -6
- package/dist/extractor/regenerateConfig.js +0 -123
- package/dist/extractor/regenerateConfig.js.map +0 -6
- package/dist/extractor/removeUnusedHooks.js +0 -72
- package/dist/extractor/removeUnusedHooks.js.map +0 -6
- package/dist/extractor/timer.js +0 -38
- package/dist/extractor/timer.js.map +0 -6
- package/dist/extractor/validHTMLAttributes.js +0 -72
- package/dist/extractor/validHTMLAttributes.js.map +0 -6
- package/dist/extractor/watchTamaguiConfig.js +0 -57
- package/dist/extractor/watchTamaguiConfig.js.map +0 -6
- package/dist/getPragmaOptions.js +0 -46
- package/dist/getPragmaOptions.js.map +0 -6
- package/dist/helpers/memoize.js +0 -33
- package/dist/helpers/memoize.js.map +0 -6
- package/dist/helpers/requireTamaguiCore.js +0 -30
- package/dist/helpers/requireTamaguiCore.js.map +0 -6
- package/dist/index.js +0 -30
- package/dist/index.js.map +0 -6
- package/dist/registerRequire.js +0 -100
- package/dist/registerRequire.js.map +0 -6
- package/dist/server.js +0 -58
- package/dist/server.js.map +0 -6
- package/dist/setup.js +0 -1
- package/dist/setup.js.map +0 -6
- package/dist/types.js +0 -14
- package/dist/types.js.map +0 -6
- package/dist/worker.js +0 -72
- package/dist/worker.js.map +0 -6
package/src/checkDeps.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, realpathSync } from 'node:fs'
|
|
2
|
+
import { join, relative } from 'node:path'
|
|
3
3
|
import { CDVC } from './check-dep-versions'
|
|
4
4
|
|
|
5
5
|
export enum DEPENDENCY_TYPE {
|
|
@@ -21,113 +21,350 @@ export type Options = {
|
|
|
21
21
|
ignorePathPattern?: readonly string[]
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
devDependencies?: Record<string, string>
|
|
27
|
-
optionalDependencies?: Record<string, string>
|
|
28
|
-
}
|
|
24
|
+
// critical packages that must not be duplicated at runtime
|
|
25
|
+
const CRITICAL_PACKAGES = ['@tamagui/web', '@tamagui/core', 'tamagui']
|
|
29
26
|
|
|
30
27
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
28
|
+
* Walks node_modules to find duplicate physical copies of critical tamagui packages.
|
|
29
|
+
* Detects nested node_modules that would cause multiple runtime instances.
|
|
33
30
|
*/
|
|
34
|
-
function
|
|
35
|
-
const
|
|
36
|
-
if (!existsSync(
|
|
37
|
-
|
|
31
|
+
function checkDuplicateInstalls(root: string): string {
|
|
32
|
+
const nodeModules = join(root, 'node_modules')
|
|
33
|
+
if (!existsSync(nodeModules)) return ''
|
|
34
|
+
|
|
35
|
+
const duplicates = new Map<string, string[]>()
|
|
36
|
+
|
|
37
|
+
for (const pkg of CRITICAL_PACKAGES) {
|
|
38
|
+
const locations = findAllInstances(nodeModules, pkg)
|
|
39
|
+
if (locations.length > 1) {
|
|
40
|
+
// resolve symlinks to find truly distinct copies
|
|
41
|
+
const realPaths = new Set<string>()
|
|
42
|
+
const distinctLocations: string[] = []
|
|
43
|
+
for (const loc of locations) {
|
|
44
|
+
try {
|
|
45
|
+
const real = realpathSync(loc)
|
|
46
|
+
if (!realPaths.has(real)) {
|
|
47
|
+
realPaths.add(real)
|
|
48
|
+
distinctLocations.push(relative(root, loc))
|
|
49
|
+
}
|
|
50
|
+
} catch {
|
|
51
|
+
distinctLocations.push(relative(root, loc))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (distinctLocations.length > 1) {
|
|
55
|
+
duplicates.set(pkg, distinctLocations)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
38
58
|
}
|
|
39
59
|
|
|
40
|
-
|
|
60
|
+
if (duplicates.size === 0) return ''
|
|
41
61
|
|
|
42
|
-
|
|
43
|
-
|
|
62
|
+
const lines: string[] = [
|
|
63
|
+
'Found duplicate tamagui installations in node_modules:',
|
|
64
|
+
'',
|
|
65
|
+
'This causes multiple runtime instances, which breaks theme/config detection.',
|
|
66
|
+
'',
|
|
67
|
+
]
|
|
44
68
|
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
69
|
+
for (const [pkg, locations] of duplicates) {
|
|
70
|
+
// read versions from each location
|
|
71
|
+
lines.push(` ${pkg}:`)
|
|
72
|
+
for (const loc of locations) {
|
|
73
|
+
const pkgJsonPath = join(root, loc, 'package.json')
|
|
74
|
+
let version = '?'
|
|
75
|
+
try {
|
|
76
|
+
version = JSON.parse(readFileSync(pkgJsonPath, 'utf8')).version
|
|
77
|
+
} catch {}
|
|
78
|
+
lines.push(` ${version} at ${loc}`)
|
|
79
|
+
}
|
|
80
|
+
lines.push('')
|
|
49
81
|
}
|
|
50
82
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
83
|
+
lines.push("Fix: run your package manager's dedupe command:")
|
|
84
|
+
lines.push(' bun install (bun auto-dedupes)')
|
|
85
|
+
lines.push(' npx yarn-deduplicate && yarn install')
|
|
86
|
+
lines.push(' npm dedupe')
|
|
87
|
+
lines.push('')
|
|
88
|
+
lines.push("If that doesn't help, delete node_modules and lockfile, then reinstall.")
|
|
89
|
+
|
|
90
|
+
return lines.join('\n')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Recursively find all instances of a package in node_modules.
|
|
95
|
+
* Handles both scoped (@tamagui/web) and unscoped (tamagui) packages.
|
|
96
|
+
*/
|
|
97
|
+
function findAllInstances(
|
|
98
|
+
nodeModulesDir: string,
|
|
99
|
+
packageName: string,
|
|
100
|
+
found: string[] = [],
|
|
101
|
+
depth = 0
|
|
102
|
+
): string[] {
|
|
103
|
+
// don't go too deep, typical hoisting issues show up within a few levels
|
|
104
|
+
if (depth > 4 || !existsSync(nodeModulesDir)) return found
|
|
105
|
+
|
|
106
|
+
const pkgDir = join(nodeModulesDir, ...packageName.split('/'))
|
|
107
|
+
if (existsSync(join(pkgDir, 'package.json'))) {
|
|
108
|
+
found.push(pkgDir)
|
|
55
109
|
}
|
|
56
110
|
|
|
57
|
-
|
|
111
|
+
// scan nested node_modules inside direct children
|
|
112
|
+
try {
|
|
113
|
+
const entries = readdirSync(nodeModulesDir)
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
if (entry.startsWith('.')) continue
|
|
116
|
+
|
|
117
|
+
if (entry.startsWith('@')) {
|
|
118
|
+
// scoped packages have another level
|
|
119
|
+
const scopeDir = join(nodeModulesDir, entry)
|
|
120
|
+
try {
|
|
121
|
+
const scopeEntries = readdirSync(scopeDir)
|
|
122
|
+
for (const scopeEntry of scopeEntries) {
|
|
123
|
+
const nested = join(scopeDir, scopeEntry, 'node_modules')
|
|
124
|
+
if (existsSync(nested)) {
|
|
125
|
+
findAllInstances(nested, packageName, found, depth + 1)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch {}
|
|
129
|
+
} else {
|
|
130
|
+
const nested = join(nodeModulesDir, entry, 'node_modules')
|
|
131
|
+
if (existsSync(nested)) {
|
|
132
|
+
findAllInstances(nested, packageName, found, depth + 1)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch {}
|
|
137
|
+
|
|
138
|
+
return found
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Checks lockfile for multiple resolved versions of tamagui packages.
|
|
143
|
+
* Supports bun.lock, yarn.lock, and package-lock.json.
|
|
144
|
+
*/
|
|
145
|
+
function checkLockfileDuplicates(root: string): string {
|
|
146
|
+
const bunLock = join(root, 'bun.lock')
|
|
147
|
+
const yarnLock = join(root, 'yarn.lock')
|
|
148
|
+
const npmLock = join(root, 'package-lock.json')
|
|
149
|
+
|
|
150
|
+
if (existsSync(bunLock)) return checkBunLockDuplicates(bunLock)
|
|
151
|
+
if (existsSync(yarnLock)) return checkYarnLockDuplicates(yarnLock)
|
|
152
|
+
if (existsSync(npmLock)) return checkNpmLockDuplicates(npmLock)
|
|
153
|
+
|
|
154
|
+
return ''
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function checkBunLockDuplicates(lockPath: string): string {
|
|
158
|
+
try {
|
|
159
|
+
const content = readFileSync(lockPath, 'utf8')
|
|
160
|
+
const duplicates = new Map<string, Set<string>>()
|
|
161
|
+
const criticalSet = new Set(CRITICAL_PACKAGES)
|
|
162
|
+
|
|
163
|
+
// match patterns like "@tamagui/web@version" or "tamagui@version" in resolved entries
|
|
164
|
+
// bun.lock format: "package@version": ["resolved-url", ...]
|
|
165
|
+
const packagePattern = /["'](@tamagui\/[\w-]+|tamagui)@([^"'\s,]+)["']/g
|
|
166
|
+
let match: RegExpExecArray | null
|
|
167
|
+
while ((match = packagePattern.exec(content)) !== null) {
|
|
168
|
+
const name = match[1]
|
|
169
|
+
const version = match[2]
|
|
170
|
+
if (version.startsWith('workspace:')) continue
|
|
171
|
+
// only flag critical packages — leaf packages can safely differ
|
|
172
|
+
if (!criticalSet.has(name)) continue
|
|
173
|
+
if (!duplicates.has(name)) duplicates.set(name, new Set())
|
|
174
|
+
duplicates.get(name)!.add(version)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return formatLockfileDuplicates(duplicates, 'bun.lock')
|
|
178
|
+
} catch {
|
|
58
179
|
return ''
|
|
59
180
|
}
|
|
181
|
+
}
|
|
60
182
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
183
|
+
function checkYarnLockDuplicates(lockPath: string): string {
|
|
184
|
+
try {
|
|
185
|
+
const content = readFileSync(lockPath, 'utf8')
|
|
186
|
+
const duplicates = new Map<string, Set<string>>()
|
|
187
|
+
const criticalSet = new Set(CRITICAL_PACKAGES)
|
|
188
|
+
|
|
189
|
+
// yarn.lock format:
|
|
190
|
+
// "@tamagui/web@^1.0.0":
|
|
191
|
+
// version "1.0.1"
|
|
192
|
+
const entryPattern = /^"?(@tamagui\/[\w-]+|tamagui)@[^":\n]+[":]?\s*$/gm
|
|
193
|
+
const versionPattern = /^\s+version\s+"([^"]+)"/gm
|
|
194
|
+
|
|
195
|
+
let entryMatch: RegExpExecArray | null
|
|
196
|
+
while ((entryMatch = entryPattern.exec(content)) !== null) {
|
|
197
|
+
const name = entryMatch[1]
|
|
198
|
+
if (!criticalSet.has(name)) continue
|
|
199
|
+
versionPattern.lastIndex = entryMatch.index
|
|
200
|
+
const verMatch = versionPattern.exec(content)
|
|
201
|
+
if (verMatch) {
|
|
202
|
+
if (!duplicates.has(name)) duplicates.set(name, new Set())
|
|
203
|
+
duplicates.get(name)!.add(verMatch[1])
|
|
204
|
+
}
|
|
66
205
|
}
|
|
67
|
-
|
|
206
|
+
|
|
207
|
+
return formatLockfileDuplicates(duplicates, 'yarn.lock')
|
|
208
|
+
} catch {
|
|
209
|
+
return ''
|
|
68
210
|
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function checkNpmLockDuplicates(lockPath: string): string {
|
|
214
|
+
try {
|
|
215
|
+
const lock = JSON.parse(readFileSync(lockPath, 'utf8'))
|
|
216
|
+
const duplicates = new Map<string, Set<string>>()
|
|
217
|
+
const criticalSet = new Set(CRITICAL_PACKAGES)
|
|
69
218
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
219
|
+
// package-lock.json v2/v3 uses "packages" map with path keys
|
|
220
|
+
const packages = lock.packages || {}
|
|
221
|
+
for (const [path, info] of Object.entries(packages) as [string, any][]) {
|
|
222
|
+
if (!path) continue // skip root
|
|
223
|
+
const name = info.name || path.split('node_modules/').pop()
|
|
224
|
+
if (!name) continue
|
|
225
|
+
if (!criticalSet.has(name)) continue
|
|
226
|
+
const version = info.version
|
|
227
|
+
if (version) {
|
|
228
|
+
if (!duplicates.has(name)) duplicates.set(name, new Set())
|
|
229
|
+
duplicates.get(name)!.add(version)
|
|
230
|
+
}
|
|
76
231
|
}
|
|
77
|
-
versionGroups.get(normalized)!.push(`${dep.name}@${dep.version}`)
|
|
78
|
-
}
|
|
79
232
|
|
|
80
|
-
|
|
81
|
-
|
|
233
|
+
return formatLockfileDuplicates(duplicates, 'package-lock.json')
|
|
234
|
+
} catch {
|
|
82
235
|
return ''
|
|
83
236
|
}
|
|
237
|
+
}
|
|
84
238
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for (const [
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
lines.push(` - ${pkg}`)
|
|
239
|
+
function formatLockfileDuplicates(
|
|
240
|
+
duplicates: Map<string, Set<string>>,
|
|
241
|
+
lockfileName: string
|
|
242
|
+
): string {
|
|
243
|
+
// filter to only packages with multiple versions
|
|
244
|
+
const multiVersion = new Map<string, string[]>()
|
|
245
|
+
for (const [name, versions] of duplicates) {
|
|
246
|
+
if (versions.size > 1) {
|
|
247
|
+
multiVersion.set(name, [...versions].sort())
|
|
95
248
|
}
|
|
96
249
|
}
|
|
97
250
|
|
|
251
|
+
if (multiVersion.size === 0) return ''
|
|
252
|
+
|
|
253
|
+
const lines: string[] = [`Found multiple resolved versions in ${lockfileName}:`, '']
|
|
254
|
+
|
|
255
|
+
for (const [name, versions] of multiVersion) {
|
|
256
|
+
lines.push(` ${name}: ${versions.join(', ')}`)
|
|
257
|
+
}
|
|
258
|
+
|
|
98
259
|
lines.push('')
|
|
99
260
|
lines.push(
|
|
100
|
-
'
|
|
261
|
+
'Multiple versions cause duplicate runtime instances, breaking config/theme detection.'
|
|
101
262
|
)
|
|
102
|
-
lines.push('
|
|
263
|
+
lines.push('Fix: ensure all tamagui packages use the same version range, then dedupe.')
|
|
103
264
|
|
|
104
265
|
return lines.join('\n')
|
|
105
266
|
}
|
|
106
267
|
|
|
268
|
+
/**
|
|
269
|
+
* Checks that a tamagui config file exists in common locations.
|
|
270
|
+
*/
|
|
271
|
+
function checkConfigExists(root: string): string {
|
|
272
|
+
const configNames = [
|
|
273
|
+
'tamagui.config.ts',
|
|
274
|
+
'tamagui.config.tsx',
|
|
275
|
+
'tamagui.config.js',
|
|
276
|
+
'tamagui.config.mjs',
|
|
277
|
+
'tamagui.config.cjs',
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
const searchDirs = [root, join(root, 'src'), join(root, 'app'), join(root, 'config')]
|
|
281
|
+
|
|
282
|
+
for (const dir of searchDirs) {
|
|
283
|
+
for (const name of configNames) {
|
|
284
|
+
if (existsSync(join(dir, name))) {
|
|
285
|
+
return ''
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// check if tamagui.build.ts references a config path
|
|
291
|
+
const buildConfigNames = [
|
|
292
|
+
'tamagui.build.ts',
|
|
293
|
+
'tamagui.build.js',
|
|
294
|
+
'tamagui.build.mjs',
|
|
295
|
+
'tamagui.build.cjs',
|
|
296
|
+
]
|
|
297
|
+
for (const name of buildConfigNames) {
|
|
298
|
+
const buildPath = join(root, name)
|
|
299
|
+
if (existsSync(buildPath)) {
|
|
300
|
+
try {
|
|
301
|
+
const content = readFileSync(buildPath, 'utf8')
|
|
302
|
+
const match = content.match(/config\s*:\s*['"`]([^'"`]+)['"`]/)
|
|
303
|
+
if (match) {
|
|
304
|
+
const configPath = join(root, match[1])
|
|
305
|
+
if (existsSync(configPath)) return ''
|
|
306
|
+
}
|
|
307
|
+
} catch {}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// also check if there's a tamagui config referenced in package.json
|
|
312
|
+
const pkgJsonPath = join(root, 'package.json')
|
|
313
|
+
if (existsSync(pkgJsonPath)) {
|
|
314
|
+
try {
|
|
315
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
|
|
316
|
+
if (pkg.tamagui?.config) {
|
|
317
|
+
const configPath = join(root, pkg.tamagui.config)
|
|
318
|
+
if (existsSync(configPath)) return ''
|
|
319
|
+
}
|
|
320
|
+
} catch {}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// check if this is a monorepo root (has workspaces) - skip config check for root
|
|
324
|
+
if (existsSync(pkgJsonPath)) {
|
|
325
|
+
try {
|
|
326
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
|
|
327
|
+
if (pkg.workspaces) return ''
|
|
328
|
+
} catch {}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return [
|
|
332
|
+
'No tamagui.config file found.',
|
|
333
|
+
'',
|
|
334
|
+
'Tamagui requires a config file (e.g. tamagui.config.ts) that calls createTamagui().',
|
|
335
|
+
'Without it, components will throw "Can\'t find Tamagui configuration" at runtime.',
|
|
336
|
+
'',
|
|
337
|
+
'See: https://tamagui.dev/docs/core/configuration',
|
|
338
|
+
].join('\n')
|
|
339
|
+
}
|
|
340
|
+
|
|
107
341
|
export async function checkDeps(root: string) {
|
|
108
|
-
|
|
109
|
-
const tamaguiMismatchSummary = checkTamaguiPackageVersions(root)
|
|
342
|
+
const issues: string[] = []
|
|
110
343
|
|
|
111
|
-
//
|
|
344
|
+
// 1. check for dependency version mismatches across workspace packages
|
|
112
345
|
const workspaceMismatchSummary = new CDVC(root).toMismatchSummary()
|
|
346
|
+
if (workspaceMismatchSummary) issues.push(workspaceMismatchSummary)
|
|
347
|
+
|
|
348
|
+
// 2. check lockfile for duplicate resolved versions of critical packages
|
|
349
|
+
const lockfileSummary = checkLockfileDuplicates(root)
|
|
350
|
+
if (lockfileSummary) issues.push(lockfileSummary)
|
|
113
351
|
|
|
114
|
-
|
|
115
|
-
const
|
|
352
|
+
// 3. check for duplicate physical installations in node_modules
|
|
353
|
+
const duplicatesSummary = checkDuplicateInstalls(root)
|
|
354
|
+
if (duplicatesSummary) issues.push(duplicatesSummary)
|
|
116
355
|
|
|
117
|
-
|
|
356
|
+
// 4. check that a config file exists
|
|
357
|
+
const configSummary = checkConfigExists(root)
|
|
358
|
+
if (configSummary) issues.push(configSummary)
|
|
359
|
+
|
|
360
|
+
if (issues.length === 0) {
|
|
118
361
|
console.info(`Tamagui dependencies look good ✅`)
|
|
119
362
|
process.exit(0)
|
|
120
363
|
}
|
|
121
364
|
|
|
122
|
-
|
|
123
|
-
console.error(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (hasWorkspaceMismatch) {
|
|
127
|
-
if (hasTamaguiMismatch) {
|
|
128
|
-
console.error('') // Add spacing between error sections
|
|
129
|
-
}
|
|
130
|
-
console.error(workspaceMismatchSummary)
|
|
365
|
+
for (let i = 0; i < issues.length; i++) {
|
|
366
|
+
if (i > 0) console.error('')
|
|
367
|
+
console.error(issues[i])
|
|
131
368
|
}
|
|
132
369
|
|
|
133
370
|
process.exit(1)
|
package/src/exports.ts
CHANGED
|
@@ -11,4 +11,5 @@ export * from './extractor/loadTamagui'
|
|
|
11
11
|
export * from './extractor/watchTamaguiConfig'
|
|
12
12
|
export * from './extractor/createLogger'
|
|
13
13
|
export * from './registerRequire'
|
|
14
|
+
export { detectModuleFormat, clearFormatCache } from './extractor/detectModuleFormat'
|
|
14
15
|
export * from './getPragmaOptions'
|
package/src/extractor/bundle.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
2
|
import esbuild from 'esbuild'
|
|
3
3
|
import * as FS from 'fs-extra'
|
|
4
4
|
import type { TamaguiPlatform } from '../types'
|
|
5
|
+
import { detectModuleFormat } from './detectModuleFormat'
|
|
5
6
|
import { esbuildAliasPlugin } from './esbuildAliasPlugin'
|
|
6
7
|
import { resolveWebOrNativeSpecificEntry } from './loadTamagui'
|
|
7
8
|
import { TsconfigPathsPlugin } from './esbuildTsconfigPaths'
|
|
@@ -63,11 +64,23 @@ function getESBuildConfig(
|
|
|
63
64
|
? entryPoints
|
|
64
65
|
: entryPoints.map(resolveWebOrNativeSpecificEntry)
|
|
65
66
|
|
|
67
|
+
// detect format from entry points if not explicitly provided by caller
|
|
68
|
+
const detectedFormat = options.format || detectEntryFormat(resolvedEntryPoints[0])
|
|
69
|
+
|
|
66
70
|
const res: esbuild.BuildOptions = {
|
|
67
71
|
bundle: true,
|
|
68
72
|
entryPoints: resolvedEntryPoints,
|
|
69
|
-
format:
|
|
70
|
-
|
|
73
|
+
format: detectedFormat,
|
|
74
|
+
// for ESM: prefer "module" field for resolution, add require() shim for bundled CJS deps
|
|
75
|
+
...(detectedFormat === 'esm'
|
|
76
|
+
? {
|
|
77
|
+
mainFields: ['module', 'main'],
|
|
78
|
+
banner: {
|
|
79
|
+
js: 'import { createRequire as __cr } from "module"; const require = __cr(import.meta.url);',
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
: {}),
|
|
83
|
+
target: 'node24',
|
|
71
84
|
jsx: 'transform',
|
|
72
85
|
jsxFactory: 'react',
|
|
73
86
|
allowOverwrite: true,
|
|
@@ -92,38 +105,126 @@ function getESBuildConfig(
|
|
|
92
105
|
plugins: [
|
|
93
106
|
TsconfigPathsPlugin(),
|
|
94
107
|
|
|
108
|
+
// handle ESM-only features that can't be used with CJS output
|
|
109
|
+
{
|
|
110
|
+
name: 'handle-esm-features',
|
|
111
|
+
setup(build) {
|
|
112
|
+
// only apply transforms for CJS output - ESM supports these natively
|
|
113
|
+
const isCjs =
|
|
114
|
+
build.initialOptions.format === 'cjs' || !build.initialOptions.format
|
|
115
|
+
|
|
116
|
+
build.onLoad({ filter: /\.(ts|tsx|js|jsx|mjs)$/ }, (args) => {
|
|
117
|
+
// skip if ESM output - import.meta and top-level await work natively
|
|
118
|
+
if (!isCjs) {
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// skip most node_modules
|
|
123
|
+
if (args.path.includes('node_modules') && !args.path.includes('@tamagui')) {
|
|
124
|
+
return null
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let contents = readFileSync(args.path, 'utf8')
|
|
128
|
+
let modified = false
|
|
129
|
+
|
|
130
|
+
// transform import.meta.env -> process.env (Vite-style env vars)
|
|
131
|
+
if (contents.includes('import.meta.env')) {
|
|
132
|
+
contents = contents.replace(/import\.meta\.env/g, 'process.env')
|
|
133
|
+
modified = true
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// transform import.meta.url -> "" (not needed for static extraction)
|
|
137
|
+
if (contents.includes('import.meta.url')) {
|
|
138
|
+
contents = contents.replace(/import\.meta\.url/g, '""')
|
|
139
|
+
modified = true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// transform import.meta.main -> false
|
|
143
|
+
if (contents.includes('import.meta.main')) {
|
|
144
|
+
contents = contents.replace(/import\.meta\.main/g, 'false')
|
|
145
|
+
modified = true
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// stub files with top-level await - they're typically runtime-only
|
|
149
|
+
if (
|
|
150
|
+
/^\s*(?:const|let|var|export)\s+[^=]*=\s*await\b/m.test(contents) ||
|
|
151
|
+
/^await\s/m.test(contents)
|
|
152
|
+
) {
|
|
153
|
+
if (process.env.DEBUG?.startsWith('tamagui')) {
|
|
154
|
+
console.info(`[tamagui] stubbing file with top-level await: ${args.path}`)
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
contents: `// stubbed - contains top-level await\nmodule.exports = {}`,
|
|
158
|
+
loader: 'js',
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (modified) {
|
|
163
|
+
return {
|
|
164
|
+
contents,
|
|
165
|
+
loader: args.path.endsWith('.tsx')
|
|
166
|
+
? 'tsx'
|
|
167
|
+
: args.path.endsWith('.ts')
|
|
168
|
+
? 'ts'
|
|
169
|
+
: args.path.endsWith('.jsx')
|
|
170
|
+
? 'jsx'
|
|
171
|
+
: 'js',
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return null
|
|
176
|
+
})
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
|
|
95
180
|
{
|
|
96
181
|
name: 'external',
|
|
97
182
|
setup(build) {
|
|
98
|
-
|
|
183
|
+
const proxyWormPath = require.resolve('@tamagui/proxy-worm')
|
|
184
|
+
|
|
185
|
+
// only externalize @tamagui/core and @tamagui/web - these are provided at runtime
|
|
186
|
+
// other @tamagui/* packages (like @tamagui/config/v3) must be bundled in to avoid
|
|
187
|
+
// ESM race conditions when multiple threads require() them concurrently
|
|
188
|
+
build.onResolve({ filter: /^@tamagui\/(core|web)$/ }, (args) => {
|
|
189
|
+
if (args.kind === 'entry-point') {
|
|
190
|
+
return null
|
|
191
|
+
}
|
|
99
192
|
return {
|
|
100
|
-
path: platform === 'native' ? '@tamagui/core/native' :
|
|
193
|
+
path: platform === 'native' ? '@tamagui/core/native' : args.path,
|
|
101
194
|
external: true,
|
|
102
195
|
}
|
|
103
196
|
})
|
|
104
|
-
|
|
197
|
+
|
|
198
|
+
build.onResolve({ filter: /react-native\/package.json$/ }, () => {
|
|
105
199
|
return {
|
|
106
200
|
path: 'react-native/package.json',
|
|
107
201
|
external: true,
|
|
108
202
|
}
|
|
109
203
|
})
|
|
110
|
-
|
|
204
|
+
|
|
205
|
+
build.onResolve({ filter: /^(react-native|react-native\/.*)$/ }, () => {
|
|
111
206
|
return {
|
|
112
|
-
path:
|
|
207
|
+
path: '@tamagui/react-native-web-lite',
|
|
113
208
|
external: true,
|
|
114
209
|
}
|
|
115
210
|
})
|
|
116
211
|
|
|
117
|
-
build.onResolve({ filter: /^
|
|
212
|
+
build.onResolve({ filter: /^react-native-reanimated(?:\/.*)?$/ }, () => {
|
|
118
213
|
return {
|
|
119
|
-
path:
|
|
120
|
-
external: true,
|
|
214
|
+
path: proxyWormPath,
|
|
121
215
|
}
|
|
122
216
|
})
|
|
123
217
|
|
|
124
|
-
build.onResolve({ filter:
|
|
218
|
+
build.onResolve({ filter: /^react-native-worklets(?:\/.*)?$/ }, () => {
|
|
125
219
|
return {
|
|
126
|
-
path:
|
|
220
|
+
path: proxyWormPath,
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// externalize animation libraries - not needed for static extraction
|
|
225
|
+
build.onResolve({ filter: /^(framer-motion|motion)/ }, (args) => {
|
|
226
|
+
return {
|
|
227
|
+
path: args.path,
|
|
127
228
|
external: true,
|
|
128
229
|
}
|
|
129
230
|
})
|
|
@@ -139,36 +240,38 @@ function getESBuildConfig(
|
|
|
139
240
|
return res
|
|
140
241
|
}
|
|
141
242
|
|
|
243
|
+
function detectEntryFormat(entryPoint: string): esbuild.BuildOptions['format'] {
|
|
244
|
+
// file path - detect from file/package.json
|
|
245
|
+
if (entryPoint.startsWith('/') || entryPoint.startsWith('.')) {
|
|
246
|
+
return detectModuleFormat(entryPoint)
|
|
247
|
+
}
|
|
248
|
+
// bare module specifier - check package.json type field
|
|
249
|
+
try {
|
|
250
|
+
const pkgJsonPath = require.resolve(entryPoint + '/package.json')
|
|
251
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'))
|
|
252
|
+
return pkg.type === 'module' ? 'esm' : 'cjs'
|
|
253
|
+
} catch {
|
|
254
|
+
return 'cjs'
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
142
258
|
export async function esbundleTamaguiConfig(
|
|
143
259
|
props: Props,
|
|
144
260
|
platform: TamaguiPlatform,
|
|
145
261
|
aliases?: Record<string, string>
|
|
146
262
|
) {
|
|
147
|
-
await asyncLock(props)
|
|
148
263
|
const config = getESBuildConfig(props, platform, aliases)
|
|
149
|
-
return await esbuild.build(config)
|
|
150
|
-
}
|
|
151
264
|
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
const
|
|
156
|
-
|
|
265
|
+
// build to memory first, then write atomically (temp file + rename)
|
|
266
|
+
// to prevent other threads from reading partially-written files
|
|
267
|
+
const tmpFile = props.outfile + '.tmp.' + process.pid
|
|
268
|
+
const result = await esbuild.build({
|
|
269
|
+
...config,
|
|
270
|
+
outfile: tmpFile,
|
|
157
271
|
})
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
console.info(`Waiting for existing build`, props.entryPoints)
|
|
164
|
-
}
|
|
165
|
-
let tries = 5
|
|
166
|
-
while (tries--) {
|
|
167
|
-
if (await FS.pathExists(props.outfile)) {
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
await new Promise((res) => setTimeout(res, 50))
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
void FS.writeFile(lockFile, '')
|
|
272
|
+
|
|
273
|
+
// atomic rename prevents other threads from reading partial files
|
|
274
|
+
await FS.rename(tmpFile, props.outfile)
|
|
275
|
+
|
|
276
|
+
return result
|
|
174
277
|
}
|