@tamagui/vite-plugin 2.7.7 → 3.0.0-beta.1097.1

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.
@@ -1,94 +1,288 @@
1
- import * as StaticWorker from '@tamagui/static-worker'
1
+ import Static from '@tamagui/static'
2
+ import type { TamaguiProjectInfo } from '@tamagui/static'
2
3
  import type { TamaguiOptions } from '@tamagui/types'
4
+ import { createRequire } from 'node:module'
5
+ import path from 'node:path'
6
+ import type { RunnableDevEnvironment } from 'vite'
3
7
 
4
- // use globalThis to share state across vite environments (SSR, client, etc.)
5
- const LOAD_STATE_KEY = '__tamagui_load_state__'
8
+ export const TAMAGUI_EVALUATION_ENVIRONMENT = 'tamagui'
6
9
 
7
- type LoadState = {
8
- loadPromise: Promise<TamaguiOptions> | null
9
- loadedOptions: TamaguiOptions | null
10
- fullConfigLoaded: boolean
11
- fullConfigLoadPromise: Promise<void> | null
12
- }
10
+ const requireFromLoader = createRequire(
11
+ typeof __filename === 'string' ? __filename : import.meta.url
12
+ )
13
13
 
14
- function getLoadState(): LoadState {
15
- if (!(globalThis as any)[LOAD_STATE_KEY]) {
16
- ;(globalThis as any)[LOAD_STATE_KEY] = {
17
- loadPromise: null,
18
- loadedOptions: null,
19
- fullConfigLoaded: false,
20
- fullConfigLoadPromise: null,
21
- }
22
- }
23
- return (globalThis as any)[LOAD_STATE_KEY]
14
+ // upgrading the plugin must invalidate cached plans even when the project did
15
+ // not change
16
+ const vitePluginVersions = [
17
+ `@tamagui/vite-plugin@${(requireFromLoader('@tamagui/vite-plugin/package.json') as { version: string }).version}`,
18
+ ]
19
+
20
+ type ResolvedEvaluationModule = {
21
+ moduleName: string
22
+ id: string
23
+ module: Record<string, unknown>
24
24
  }
25
25
 
26
- export function getTamaguiOptions(): TamaguiOptions | null {
27
- return getLoadState().loadedOptions
26
+ type EvaluatedProjectModules = {
27
+ config: ResolvedEvaluationModule
28
+ components: ResolvedEvaluationModule[]
28
29
  }
29
30
 
30
- export function getLoadPromise(): Promise<TamaguiOptions> | null {
31
- return getLoadState().loadPromise
31
+ export type ViteTamaguiLoader = {
32
+ getEnvironment(): RunnableDevEnvironment | null
33
+ getGeneration(): number
34
+ getLoadPromise(): Promise<TamaguiOptions> | null
35
+ getTamaguiOptions(): TamaguiOptions | null
36
+ getTamaguiConfig(): Promise<TamaguiProjectInfo['tamaguiConfig']>
37
+ getCompilerProject(): Promise<Static.CompilerProject>
38
+ getEvaluationDependencies(): string[]
39
+ isEvaluationDependency(id: string): boolean
40
+ evaluateProjectModules(options: TamaguiOptions): Promise<EvaluatedProjectModules>
41
+ /**
42
+ * Evaluate one host-resolved module in the evaluation environment for the
43
+ * compiler's component discovery. Null when the environment is not ready or
44
+ * the module cannot run in node; the compiler then leaves its elements alone.
45
+ */
46
+ evaluateModule(id: string): Promise<Record<string, unknown> | null>
47
+ loadTamaguiBuildConfig(): Promise<TamaguiOptions>
48
+ setEnvironment(next: RunnableDevEnvironment, options?: { owned?: boolean }): void
49
+ invalidate(file?: string): void
50
+ ensureFullConfigLoaded(): Promise<string[]>
51
+ cleanup(): Promise<void>
32
52
  }
33
53
 
34
- /**
35
- * Load just the tamagui.build.ts config (lightweight)
36
- * This doesn't bundle the full tamagui config - call ensureFullConfigLoaded() for that
37
- */
38
- export async function loadTamaguiBuildConfig(
39
- optionsIn?: Partial<TamaguiOptions>
40
- ): Promise<TamaguiOptions> {
41
- const state = getLoadState()
42
- if (state.loadedOptions) return state.loadedOptions
43
- if (state.loadPromise) return state.loadPromise
44
-
45
- state.loadPromise = (async () => {
46
- const options = await StaticWorker.loadTamaguiBuildConfig({
54
+ export function createViteTamaguiLoader(
55
+ optionsIn: Partial<TamaguiOptions> = {}
56
+ ): ViteTamaguiLoader {
57
+ let environment: RunnableDevEnvironment | null = null
58
+ let ownsEnvironment = false
59
+ let loadPromise: Promise<TamaguiOptions> | null = null
60
+ let loadedOptions: TamaguiOptions | null = null
61
+ let projectPromise: Promise<Static.CompilerProject> | null = null
62
+ const evaluationDependencies = new Set<string>()
63
+ const stampSources = new Set<string>()
64
+ let generation = 0
65
+
66
+ const normalizeDependency = (id: string) => id.split('?')[0]
67
+
68
+ const captureEvaluationDependencies = (modules: ResolvedEvaluationModule[]) => {
69
+ stampSources.clear()
70
+ for (const { id } of modules) {
71
+ const dependency = normalizeDependency(id)
72
+ if (path.isAbsolute(dependency)) {
73
+ evaluationDependencies.add(dependency)
74
+ stampSources.add(dependency)
75
+ }
76
+ }
77
+ if (environment) {
78
+ for (const module of environment.runner.evaluatedModules.urlToIdModuleMap.values()) {
79
+ const dependency = normalizeDependency(module.file)
80
+ if (!path.isAbsolute(dependency)) continue
81
+ // watching node_modules is pointless, but the compile cache stamp has to
82
+ // see them: a component package defines every staticConfig the compiler
83
+ // lowers against, so bumping one in place must invalidate cached plans
84
+ stampSources.add(dependency)
85
+ if (!dependency.includes('/node_modules/')) {
86
+ evaluationDependencies.add(dependency)
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ const loadTamaguiBuildConfig = async (): Promise<TamaguiOptions> => {
93
+ if (loadedOptions) return loadedOptions
94
+ if (loadPromise) return loadPromise
95
+
96
+ loadPromise = Static.loadTamaguiBuildConfigAsync({
47
97
  ...optionsIn,
48
98
  platform: 'web',
99
+ }).then((options) => {
100
+ loadedOptions = options
101
+ return options
49
102
  })
50
103
 
51
- state.loadedOptions = options
52
- return options
53
- })()
104
+ return loadPromise
105
+ }
54
106
 
55
- return state.loadPromise
56
- }
107
+ const resolveAndImport = async (
108
+ moduleName: string,
109
+ root: string,
110
+ kind: 'config' | 'component'
111
+ ): Promise<ResolvedEvaluationModule> => {
112
+ if (!environment) {
113
+ throw new Error(
114
+ `The Tamagui Vite evaluation environment is not ready. Config and component evaluation requires Vite's ModuleRunner.`
115
+ )
116
+ }
57
117
 
58
- /**
59
- * Ensure the full tamagui config is loaded (heavy - bundles config + components)
60
- * Call this lazily when transform/extraction is actually needed
61
- */
62
- export async function ensureFullConfigLoaded(): Promise<void> {
63
- const state = getLoadState()
64
-
65
- if (state.fullConfigLoaded) return
66
- if (state.fullConfigLoadPromise) return state.fullConfigLoadPromise
67
-
68
- // set promise immediately to prevent race conditions
69
- // (don't await loadTamaguiBuildConfig before setting this)
70
- state.fullConfigLoadPromise = (async () => {
71
- const options = await loadTamaguiBuildConfig()
72
-
73
- // load full tamagui config in worker (asynchronous)
74
- if (!options.disableWatchTamaguiConfig && !options.disable) {
75
- await StaticWorker.loadTamagui({
76
- components: ['tamagui'],
77
- platform: 'web',
78
- ...options,
79
- })
118
+ const source = path.isAbsolute(moduleName)
119
+ ? moduleName
120
+ : kind === 'config' || moduleName.startsWith('.')
121
+ ? path.resolve(root, moduleName)
122
+ : moduleName
123
+ let environmentResolution = await environment.pluginContainer.resolveId(source)
124
+
125
+ // Config paths are app-root relative by default, but package/alias config
126
+ // entries remain supported when no root-relative file resolves.
127
+ if (!environmentResolution && kind === 'config' && source !== moduleName) {
128
+ environmentResolution = await environment.pluginContainer.resolveId(moduleName)
80
129
  }
81
- state.fullConfigLoaded = true
82
- })()
83
130
 
84
- return state.fullConfigLoadPromise
85
- }
131
+ const resolvedId = environmentResolution?.id
86
132
 
87
- export async function cleanup() {
88
- await StaticWorker.destroyPool()
89
- const state = getLoadState()
90
- state.loadPromise = null
91
- state.loadedOptions = null
92
- state.fullConfigLoaded = false
93
- state.fullConfigLoadPromise = null
133
+ if (!resolvedId) {
134
+ throw new Error(
135
+ `Unable to resolve ${moduleName} in the Tamagui Vite environment (plugins: ${environment.plugins.map((plugin) => plugin.name).join(', ')})`
136
+ )
137
+ }
138
+
139
+ return {
140
+ moduleName,
141
+ id: resolvedId,
142
+ module: (await environment.runner.import(resolvedId)) as Record<string, unknown>,
143
+ }
144
+ }
145
+
146
+ const evaluateProjectModules = async (
147
+ options: TamaguiOptions
148
+ ): Promise<EvaluatedProjectModules> => {
149
+ if (!environment) {
150
+ throw new Error(
151
+ `Cannot evaluate Tamagui without the ${TAMAGUI_EVALUATION_ENVIRONMENT} Vite environment`
152
+ )
153
+ }
154
+
155
+ const root = environment.config.root
156
+ const config = await resolveAndImport(
157
+ options.config || 'tamagui.config.ts',
158
+ root,
159
+ 'config'
160
+ )
161
+ const components = await Promise.all(
162
+ (options.components || []).map((name) => resolveAndImport(name, root, 'component'))
163
+ )
164
+
165
+ captureEvaluationDependencies([config, ...components])
166
+
167
+ return { config, components }
168
+ }
169
+
170
+ const loadProject = async (
171
+ options: TamaguiOptions
172
+ ): Promise<Static.CompilerProject> => {
173
+ if (projectPromise) return projectPromise
174
+
175
+ projectPromise = (async () => {
176
+ if (!environment) {
177
+ throw new Error(
178
+ `Cannot load Tamagui without the ${TAMAGUI_EVALUATION_ENVIRONMENT} Vite environment`
179
+ )
180
+ }
181
+
182
+ let evaluated: EvaluatedProjectModules | null = null
183
+ return Static.loadCompilerProject({
184
+ root: environment.config.root,
185
+ target: 'web',
186
+ options,
187
+ generation: `vite:${generation}`,
188
+ hostVersions: vitePluginVersions,
189
+ async load(normalizedOptions) {
190
+ evaluated = await evaluateProjectModules(normalizedOptions)
191
+ return Static.loadTamaguiFromModules(normalizedOptions, {
192
+ config: evaluated.config.module,
193
+ components: evaluated.components.map(({ moduleName, module }) => ({
194
+ moduleName,
195
+ module,
196
+ })),
197
+ stampSources: [...stampSources],
198
+ })
199
+ },
200
+ async resolveComponents(moduleNames) {
201
+ if (!evaluated) {
202
+ throw new Error('The Tamagui compiler project modules were not evaluated')
203
+ }
204
+ const byName = new Map(
205
+ evaluated.components.map(({ moduleName, id }) => [moduleName, id])
206
+ )
207
+ return moduleNames.map((moduleName) => {
208
+ const id = byName.get(moduleName)
209
+ if (!id) throw new Error(`Unable to resolve compiler component ${moduleName}`)
210
+ return { moduleName, id }
211
+ })
212
+ },
213
+ })
214
+ })()
215
+
216
+ return projectPromise
217
+ }
218
+
219
+ return {
220
+ getEnvironment: () => environment,
221
+ getGeneration: () => generation,
222
+ getLoadPromise: () => loadPromise,
223
+ getTamaguiOptions: () => loadedOptions,
224
+ async getTamaguiConfig() {
225
+ const options = await loadTamaguiBuildConfig()
226
+ if (options.disable) return null
227
+ return (await loadProject(options)).projectInfo.tamaguiConfig
228
+ },
229
+ async getCompilerProject() {
230
+ const options = await loadTamaguiBuildConfig()
231
+ return loadProject(options)
232
+ },
233
+ getEvaluationDependencies: () => [...evaluationDependencies],
234
+ isEvaluationDependency: (id: string) =>
235
+ evaluationDependencies.has(normalizeDependency(id)),
236
+ evaluateProjectModules,
237
+ async evaluateModule(id) {
238
+ if (!environment) return null
239
+ try {
240
+ return (await environment.runner.import(id)) as Record<string, unknown>
241
+ } catch (error) {
242
+ if (process.env.DEBUG === 'tamagui') {
243
+ console.info(`[tamagui] component discovery skipped ${id}:`, error)
244
+ }
245
+ return null
246
+ }
247
+ },
248
+ loadTamaguiBuildConfig,
249
+
250
+ setEnvironment(next: RunnableDevEnvironment, options?: { owned?: boolean }) {
251
+ if (environment === next) return
252
+ environment = next
253
+ ownsEnvironment = options?.owned === true
254
+ generation++
255
+ projectPromise = null
256
+ },
257
+
258
+ invalidate(file?: string) {
259
+ if (file && environment) {
260
+ environment.runner.clearCache()
261
+ }
262
+ generation++
263
+ projectPromise = null
264
+ },
265
+
266
+ async ensureFullConfigLoaded() {
267
+ const options = await loadTamaguiBuildConfig()
268
+ if (!options.disable) {
269
+ await loadProject(options)
270
+ }
271
+ return [...evaluationDependencies]
272
+ },
273
+
274
+ async cleanup() {
275
+ try {
276
+ if (ownsEnvironment && environment) {
277
+ await environment.close()
278
+ }
279
+ } finally {
280
+ environment = null
281
+ ownsEnvironment = false
282
+ loadPromise = null
283
+ loadedOptions = null
284
+ projectPromise = null
285
+ }
286
+ },
287
+ }
94
288
  }