@tamagui/static 1.14.8 → 1.15.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 (39) hide show
  1. package/dist/cjs/extractor/bundleConfig.js +343 -0
  2. package/dist/cjs/extractor/bundleConfig.js.map +7 -0
  3. package/dist/cjs/extractor/createExtractor.js.map +2 -2
  4. package/dist/cjs/extractor/generateTamaguiConfig.js +83 -0
  5. package/dist/cjs/extractor/generateTamaguiConfig.js.map +7 -0
  6. package/dist/cjs/extractor/loadTamagui.js +76 -292
  7. package/dist/cjs/extractor/loadTamagui.js.map +3 -3
  8. package/dist/cjs/types.js.map +1 -1
  9. package/dist/esm/extractor/bundleConfig.js +307 -0
  10. package/dist/esm/extractor/bundleConfig.js.map +7 -0
  11. package/dist/esm/extractor/bundleConfig.mjs +307 -0
  12. package/dist/esm/extractor/bundleConfig.mjs.map +7 -0
  13. package/dist/esm/extractor/createExtractor.js.map +2 -2
  14. package/dist/esm/extractor/createExtractor.mjs.map +2 -2
  15. package/dist/esm/extractor/generateTamaguiConfig.js +49 -0
  16. package/dist/esm/extractor/generateTamaguiConfig.js.map +7 -0
  17. package/dist/esm/extractor/generateTamaguiConfig.mjs +49 -0
  18. package/dist/esm/extractor/generateTamaguiConfig.mjs.map +7 -0
  19. package/dist/esm/extractor/loadTamagui.js +76 -290
  20. package/dist/esm/extractor/loadTamagui.js.map +2 -2
  21. package/dist/esm/extractor/loadTamagui.mjs +76 -290
  22. package/dist/esm/extractor/loadTamagui.mjs.map +2 -2
  23. package/package.json +16 -16
  24. package/src/extractor/bundleConfig.ts +389 -0
  25. package/src/extractor/createExtractor.ts +3 -3
  26. package/src/extractor/generateTamaguiConfig.ts +65 -0
  27. package/src/extractor/loadTamagui.ts +81 -370
  28. package/src/types.ts +2 -3
  29. package/types/extractor/bundleConfig.d.ts +35 -0
  30. package/types/extractor/bundleConfig.d.ts.map +1 -0
  31. package/types/extractor/createExtractor.d.ts +2 -2
  32. package/types/extractor/createExtractor.d.ts.map +1 -1
  33. package/types/extractor/extractHelpers.d.ts +2 -2
  34. package/types/extractor/generateTamaguiConfig.d.ts +3 -0
  35. package/types/extractor/generateTamaguiConfig.d.ts.map +1 -0
  36. package/types/extractor/loadTamagui.d.ts +6 -21
  37. package/types/extractor/loadTamagui.d.ts.map +1 -1
  38. package/types/types.d.ts +1 -1
  39. package/types/types.d.ts.map +1 -1
@@ -0,0 +1,389 @@
1
+ import { readFileSync } from 'fs'
2
+ import path, { basename, dirname, extname, join, relative, sep } from 'path'
3
+
4
+ import generate from '@babel/generator'
5
+ import traverse from '@babel/traverse'
6
+ import * as t from '@babel/types'
7
+ import { Color, colorLog } from '@tamagui/cli-color'
8
+ import type { StaticConfigParsed, TamaguiInternalConfig } from '@tamagui/web'
9
+ import esbuild from 'esbuild'
10
+ import { ensureDir, removeSync, writeFileSync } from 'fs-extra'
11
+
12
+ import { babelParse } from './babelParse.js'
13
+ import { bundle } from './bundle.js'
14
+
15
+ let loggedOutputInfo = false
16
+
17
+ type NameToPaths = {
18
+ [key: string]: Set<string>
19
+ }
20
+
21
+ export type LoadedComponents = {
22
+ moduleName: string
23
+ nameToInfo: Record<
24
+ string,
25
+ {
26
+ staticConfig: StaticConfigParsed
27
+ }
28
+ >
29
+ }
30
+
31
+ export type TamaguiProjectInfo = {
32
+ components: LoadedComponents[]
33
+ tamaguiConfig: TamaguiInternalConfig
34
+ nameToPaths: NameToPaths
35
+ }
36
+
37
+ export type Props = {
38
+ components: string[]
39
+ config?: string
40
+ forceExports?: boolean
41
+ }
42
+
43
+ const external = [
44
+ '@tamagui/core',
45
+ '@tamagui/web',
46
+ '@tamagui/core-node',
47
+ 'react',
48
+ 'react-dom',
49
+ 'react-native-svg',
50
+ ]
51
+
52
+ export const esbuildOptions = {
53
+ loader: 'tsx',
54
+ target: 'es2018',
55
+ format: 'cjs',
56
+ jsx: 'transform',
57
+ platform: 'node',
58
+ } as const
59
+
60
+ export async function bundleConfig(props: Props) {
61
+ const configEntry = props.config ? join(process.cwd(), props.config) : ''
62
+ const tmpDir = join(process.cwd(), '.tamagui')
63
+ const configOutPath = join(tmpDir, `tamagui.config.cjs`)
64
+ const baseComponents = props.components.filter((x) => x !== '@tamagui/core')
65
+ const componentOutPaths = baseComponents.map((componentModule) =>
66
+ join(
67
+ tmpDir,
68
+ `${componentModule
69
+ .split(sep)
70
+ .join('-')
71
+ .replace(/[^a-z0-9]+/gi, '')}-components.config.cjs`
72
+ )
73
+ )
74
+
75
+ if (
76
+ process.env.NODE_ENV === 'development' &&
77
+ process.env.DEBUG?.startsWith('tamagui')
78
+ ) {
79
+ console.log(`Building config entry`, configEntry)
80
+ }
81
+
82
+ // build them to node-compat versions
83
+ try {
84
+ await ensureDir(tmpDir)
85
+ } catch {
86
+ //
87
+ }
88
+
89
+ if (!loggedOutputInfo) {
90
+ loggedOutputInfo = true
91
+ colorLog(
92
+ Color.FgYellow,
93
+ `
94
+ Tamagui built config and components:`
95
+ )
96
+ colorLog(
97
+ Color.Dim,
98
+ `
99
+ Config .${sep}${relative(process.cwd(), configOutPath)}
100
+ Components ${[
101
+ ...componentOutPaths.map((p) => `.${sep}${relative(process.cwd(), p)}`),
102
+ ].join('\n ')}
103
+ `
104
+ )
105
+ }
106
+
107
+ await Promise.all([
108
+ props.config
109
+ ? bundle({
110
+ entryPoints: [configEntry],
111
+ external,
112
+ outfile: configOutPath,
113
+ })
114
+ : null,
115
+ ...baseComponents.map((componentModule, i) => {
116
+ return bundle({
117
+ entryPoints: [componentModule],
118
+ resolvePlatformSpecificEntries: true,
119
+ external,
120
+ outfile: componentOutPaths[i],
121
+ })
122
+ }),
123
+ ])
124
+
125
+ // get around node.js's module cache to get the new config...
126
+ delete require.cache[path.resolve(configOutPath)]
127
+ const out = require(configOutPath)
128
+ const config = out.default || out
129
+ if (!config) {
130
+ throw new Error(`No config: ${config}`)
131
+ }
132
+
133
+ let components = loadComponents({
134
+ ...props,
135
+ components: componentOutPaths,
136
+ })
137
+
138
+ if (!components) {
139
+ throw new Error(`No components found: ${componentOutPaths.join(', ')}`)
140
+ }
141
+
142
+ // map from built back to original module names
143
+ for (const component of components) {
144
+ component.moduleName = baseComponents[componentOutPaths.indexOf(component.moduleName)]
145
+
146
+ // if (!component.moduleName) {
147
+ // throw new Error(`Tamagui internal err`)
148
+ // }
149
+ }
150
+
151
+ // always load core so we can optimize if directly importing
152
+ const coreComponents = loadComponents({
153
+ ...props,
154
+ components: ['@tamagui/core-node'],
155
+ })
156
+ if (coreComponents) {
157
+ coreComponents[0].moduleName = '@tamagui/core'
158
+ components = [...components, ...coreComponents]
159
+ }
160
+
161
+ if (
162
+ process.env.NODE_ENV === 'development' &&
163
+ process.env.DEBUG?.startsWith('tamagui')
164
+ ) {
165
+ console.log('Loaded components', components)
166
+ }
167
+ return {
168
+ components,
169
+ nameToPaths: {},
170
+ tamaguiConfig: config,
171
+ }
172
+ }
173
+
174
+ export function loadComponents(props: Props): null | LoadedComponents[] {
175
+ const componentsModules = props.components
176
+ const key = componentsModules.join('')
177
+ if (cacheComponents[key]) {
178
+ return cacheComponents[key]
179
+ }
180
+ try {
181
+ const info: LoadedComponents[] = componentsModules.flatMap((name) => {
182
+ const extension = extname(name)
183
+ const isLocal = Boolean(extension)
184
+ // during props.config pass we are passing in pre-bundled stuff
185
+ const isDynamic = isLocal && !props.config
186
+
187
+ if (isDynamic && !process.env.TAMAGUI_ENABLE_DYNAMIC_LOAD) {
188
+ return []
189
+ }
190
+
191
+ const fileContents = isDynamic ? readFileSync(name, 'utf-8') : ''
192
+ const loadModule = isDynamic
193
+ ? join(dirname(name), `.tamagui-dynamic-eval-${basename(name)}.tsx`)
194
+ : name
195
+ let writtenContents = fileContents
196
+ let didBabel = false
197
+
198
+ function attemptLoad({ forceExports = false } = {}) {
199
+ // need to write to tsx to enable reading it properly (:/ esbuild-register)
200
+ if (isDynamic) {
201
+ writtenContents = forceExports
202
+ ? esbuildit(
203
+ transformAddExports(babelParse(esbuildit(fileContents, 'modern')))
204
+ )
205
+ : esbuildit(fileContents)
206
+
207
+ writeFileSync(loadModule, writtenContents)
208
+ }
209
+
210
+ if (process.env.DEBUG === 'tamagui') {
211
+ console.log(`loadModule`, loadModule, require.resolve(loadModule))
212
+ }
213
+
214
+ return {
215
+ moduleName: name,
216
+ nameToInfo: getComponentStaticConfigByName(
217
+ name,
218
+ interopDefaultExport(require(loadModule))
219
+ ),
220
+ }
221
+ }
222
+
223
+ const dispose = () => {
224
+ isDynamic && removeSync(loadModule)
225
+ }
226
+
227
+ try {
228
+ const res = attemptLoad({
229
+ forceExports: true,
230
+ })
231
+ didBabel = true
232
+ return res
233
+ } catch (err) {
234
+ console.log('babel err', err, writtenContents)
235
+ // ok
236
+ writtenContents = fileContents
237
+ if (process.env.DEBUG?.startsWith('tamagui')) {
238
+ console.log(`Error parsing babel likely`, err)
239
+ }
240
+ } finally {
241
+ dispose()
242
+ }
243
+
244
+ try {
245
+ return attemptLoad({
246
+ forceExports: false,
247
+ })
248
+ } catch (err) {
249
+ if (!process.env.TAMAGUI_DISABLE_WARN_DYNAMIC_LOAD) {
250
+ console.log(`
251
+
252
+ Tamagui attempted but failed to dynamically load components in:
253
+ ${name}
254
+
255
+ This will leave some styled() tags unoptimized.
256
+ Disable this file (or dynamic loading altogether):
257
+
258
+ disableExtractFoundComponents: ['${name}'] | true
259
+
260
+ Quiet this warning with environment variable:
261
+
262
+ TAMAGUI_DISABLE_WARN_DYNAMIC_LOAD=1
263
+
264
+ `)
265
+ console.log(err)
266
+ console.log(
267
+ `At: ${loadModule}`,
268
+ `\ndidBabel: ${didBabel}`,
269
+ `\nIn:`,
270
+ writtenContents,
271
+ `\nisDynamic: `,
272
+ isDynamic
273
+ )
274
+ }
275
+ return []
276
+ } finally {
277
+ dispose()
278
+ }
279
+ })
280
+ cacheComponents[key] = info
281
+ return info
282
+ } catch (err: any) {
283
+ console.log(`Tamagui error bundling components`, err.message, err.stack)
284
+ return null
285
+ }
286
+ }
287
+
288
+ const esbuildit = (src: string, target?: 'modern') => {
289
+ return esbuild.transformSync(src, {
290
+ ...esbuildOptions,
291
+ ...(target === 'modern' && {
292
+ target: 'es2022',
293
+ jsx: 'transform',
294
+ loader: 'tsx',
295
+ platform: 'neutral',
296
+ format: 'esm',
297
+ }),
298
+ }).code
299
+ }
300
+
301
+ function getComponentStaticConfigByName(name: string, exported: any) {
302
+ const components: Record<string, { staticConfig: StaticConfigParsed }> = {}
303
+ try {
304
+ if (!exported || typeof exported !== 'object' || Array.isArray(exported)) {
305
+ throw new Error(`Invalid export from package ${name}: ${typeof exported}`)
306
+ }
307
+ for (const key in exported) {
308
+ const found = getTamaguiComponent(key, exported[key])
309
+ if (found) {
310
+ // remove non-stringifyable
311
+ const { Component, ...sc } = found.staticConfig
312
+ components[key] = { staticConfig: sc }
313
+ }
314
+ }
315
+ } catch (err) {
316
+ if (process.env.TAMAGUI_DISABLE_WARN_DYNAMIC_LOAD !== '1') {
317
+ console.error(
318
+ `Tamagui failed getting from ${name} (Disable error by setting environment variable TAMAGUI_DISABLE_WARN_DYNAMIC_LOAD=1)`
319
+ )
320
+ console.error(err)
321
+ }
322
+ }
323
+ return components
324
+ }
325
+
326
+ function getTamaguiComponent(
327
+ name: string,
328
+ Component: any
329
+ ): undefined | { staticConfig: StaticConfigParsed } {
330
+ if (name[0].toUpperCase() !== name[0]) {
331
+ return
332
+ }
333
+ const staticConfig = Component?.staticConfig as StaticConfigParsed | undefined
334
+ if (staticConfig) {
335
+ return Component
336
+ }
337
+ }
338
+
339
+ function interopDefaultExport(mod: any) {
340
+ return mod?.default ?? mod
341
+ }
342
+
343
+ const cacheComponents: Record<string, LoadedComponents[]> = {}
344
+
345
+ function transformAddExports(ast: t.File) {
346
+ const usedNames = new Set<string>()
347
+
348
+ // avoid clobbering
349
+ // @ts-ignore
350
+ traverse(ast, {
351
+ ExportNamedDeclaration(nodePath) {
352
+ if (nodePath.node.specifiers) {
353
+ for (const spec of nodePath.node.specifiers) {
354
+ usedNames.add(
355
+ t.isIdentifier(spec.exported) ? spec.exported.name : spec.exported.value
356
+ )
357
+ }
358
+ }
359
+ },
360
+ })
361
+
362
+ // @ts-ignore
363
+ traverse(ast, {
364
+ VariableDeclaration(nodePath) {
365
+ // top level only
366
+ if (!t.isProgram(nodePath.parent)) return
367
+ const decs = nodePath.node.declarations
368
+ if (decs.length > 1) return
369
+ const [dec] = decs
370
+ if (!t.isIdentifier(dec.id)) return
371
+ if (!dec.init) return
372
+ if (usedNames.has(dec.id.name)) return
373
+ usedNames.add(dec.id.name)
374
+ nodePath.replaceWith(
375
+ t.exportNamedDeclaration(t.variableDeclaration('let', [dec]), [
376
+ t.exportSpecifier(t.identifier(dec.id.name), t.identifier(dec.id.name)),
377
+ ])
378
+ )
379
+ },
380
+ })
381
+
382
+ // @ts-ignore
383
+ return generate(ast as any, {
384
+ concise: false,
385
+ filename: 'test.tsx',
386
+ retainLines: false,
387
+ sourceMaps: false,
388
+ }).code
389
+ }
@@ -4,14 +4,13 @@ import { basename, relative } from 'path'
4
4
  import traverse, { NodePath, TraverseOptions } from '@babel/traverse'
5
5
  import * as t from '@babel/types'
6
6
  import {
7
- PseudoStyles,
8
- StaticConfigParsed,
9
7
  expandStyles,
10
8
  getSplitStyles,
11
9
  mediaQueryConfig,
12
10
  proxyThemeVariables,
13
11
  pseudoDescriptors,
14
12
  } from '@tamagui/core-node'
13
+ import type { PseudoStyles, StaticConfigParsed } from '@tamagui/web'
15
14
  import type { ViewStyle } from 'react-native'
16
15
  import { createDOMProps } from 'react-native-web-internals'
17
16
 
@@ -26,6 +25,7 @@ import type {
26
25
  TamaguiOptionsWithFileInfo,
27
26
  Ternary,
28
27
  } from '../types.js'
28
+ import { TamaguiProjectInfo } from './bundleConfig.js'
29
29
  import { createEvaluator, createSafeEvaluator } from './createEvaluator.js'
30
30
  import { evaluateAstNode } from './evaluateAstNode.js'
31
31
  import {
@@ -43,7 +43,7 @@ import {
43
43
  getStaticBindingsForScope,
44
44
  } from './getStaticBindingsForScope.js'
45
45
  import { literalToAst } from './literalToAst.js'
46
- import { TamaguiProjectInfo, loadTamagui, loadTamaguiSync } from './loadTamagui.js'
46
+ import { loadTamagui, loadTamaguiSync } from './loadTamagui.js'
47
47
  import { logLines } from './logLines.js'
48
48
  import { normalizeTernaries } from './normalizeTernaries.js'
49
49
  import { removeUnusedHooks } from './removeUnusedHooks.js'
@@ -0,0 +1,65 @@
1
+ import { getVariableValue } from '@tamagui/core-node'
2
+ import { CLIResolvedOptions } from '@tamagui/types'
3
+ import fs, { ensureDir } from 'fs-extra'
4
+
5
+ import { bundleConfig } from './bundleConfig.js'
6
+
7
+ async function getTamaguiConfig(options: CLIResolvedOptions) {
8
+ return bundleConfig(options.tamaguiOptions)
9
+ }
10
+
11
+ export async function generateTamaguiConfig(options: CLIResolvedOptions) {
12
+ await ensureDir(options.paths.dotDir)
13
+ const config = await getTamaguiConfig(options)
14
+ const { components, nameToPaths } = config
15
+ const { themes, tokens } = config.tamaguiConfig
16
+
17
+ // reduce down to usable, smaller json
18
+
19
+ // slim themes, add name
20
+ for (const key in themes) {
21
+ const theme = themes[key]
22
+ // @ts-ignore
23
+ theme.id = key
24
+ for (const tkey in theme) {
25
+ theme[tkey] = getVariableValue(theme[tkey])
26
+ }
27
+ }
28
+
29
+ // flatten variables
30
+ for (const key in tokens) {
31
+ const token = tokens[key]
32
+ for (const tkey in token) {
33
+ token[tkey] = getVariableValue(token[tkey])
34
+ }
35
+ }
36
+
37
+ // remove bulky stuff in components
38
+ for (const component of components) {
39
+ for (const _ in component.nameToInfo) {
40
+ delete component.nameToInfo[_].staticConfig['validStyles']
41
+ delete component.nameToInfo[_].staticConfig['parentStaticConfig']
42
+ }
43
+ }
44
+
45
+ // set to array
46
+ for (const key in nameToPaths) {
47
+ // @ts-ignore
48
+ nameToPaths[key] = [...nameToPaths[key]]
49
+ }
50
+
51
+ // remove stuff we dont need to send
52
+ const { fontsParsed, getCSS, tokensParsed, themeConfig, ...cleanedConfig } =
53
+ config.tamaguiConfig
54
+
55
+ await fs.writeJSON(
56
+ options.paths.conf,
57
+ {
58
+ ...config,
59
+ tamaguiConfig: cleanedConfig,
60
+ },
61
+ {
62
+ spaces: 2,
63
+ }
64
+ )
65
+ }