@tamagui/vite-plugin 2.7.7 → 3.0.0-beta.643.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,271 @@
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
+ loadTamaguiBuildConfig(): Promise<TamaguiOptions>
42
+ setEnvironment(next: RunnableDevEnvironment, options?: { owned?: boolean }): void
43
+ invalidate(file?: string): void
44
+ ensureFullConfigLoaded(): Promise<string[]>
45
+ cleanup(): Promise<void>
32
46
  }
33
47
 
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({
48
+ export function createViteTamaguiLoader(
49
+ optionsIn: Partial<TamaguiOptions> = {}
50
+ ): ViteTamaguiLoader {
51
+ let environment: RunnableDevEnvironment | null = null
52
+ let ownsEnvironment = false
53
+ let loadPromise: Promise<TamaguiOptions> | null = null
54
+ let loadedOptions: TamaguiOptions | null = null
55
+ let projectPromise: Promise<Static.CompilerProject> | null = null
56
+ const evaluationDependencies = new Set<string>()
57
+ const stampSources = new Set<string>()
58
+ let generation = 0
59
+
60
+ const normalizeDependency = (id: string) => id.split('?')[0]
61
+
62
+ const captureEvaluationDependencies = (modules: ResolvedEvaluationModule[]) => {
63
+ stampSources.clear()
64
+ for (const { id } of modules) {
65
+ const dependency = normalizeDependency(id)
66
+ if (path.isAbsolute(dependency)) {
67
+ evaluationDependencies.add(dependency)
68
+ stampSources.add(dependency)
69
+ }
70
+ }
71
+ if (environment) {
72
+ for (const module of environment.runner.evaluatedModules.urlToIdModuleMap.values()) {
73
+ const dependency = normalizeDependency(module.file)
74
+ if (!path.isAbsolute(dependency)) continue
75
+ // watching node_modules is pointless, but the compile cache stamp has to
76
+ // see them: a component package defines every staticConfig the compiler
77
+ // lowers against, so bumping one in place must invalidate cached plans
78
+ stampSources.add(dependency)
79
+ if (!dependency.includes('/node_modules/')) {
80
+ evaluationDependencies.add(dependency)
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ const loadTamaguiBuildConfig = async (): Promise<TamaguiOptions> => {
87
+ if (loadedOptions) return loadedOptions
88
+ if (loadPromise) return loadPromise
89
+
90
+ loadPromise = Static.loadTamaguiBuildConfigAsync({
47
91
  ...optionsIn,
48
92
  platform: 'web',
93
+ }).then((options) => {
94
+ loadedOptions = options
95
+ return options
49
96
  })
50
97
 
51
- state.loadedOptions = options
52
- return options
53
- })()
98
+ return loadPromise
99
+ }
54
100
 
55
- return state.loadPromise
56
- }
101
+ const resolveAndImport = async (
102
+ moduleName: string,
103
+ root: string,
104
+ kind: 'config' | 'component'
105
+ ): Promise<ResolvedEvaluationModule> => {
106
+ if (!environment) {
107
+ throw new Error(
108
+ `The Tamagui Vite evaluation environment is not ready. Config and component evaluation requires Vite's ModuleRunner.`
109
+ )
110
+ }
57
111
 
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
- })
112
+ const source = path.isAbsolute(moduleName)
113
+ ? moduleName
114
+ : kind === 'config' || moduleName.startsWith('.')
115
+ ? path.resolve(root, moduleName)
116
+ : moduleName
117
+ let environmentResolution = await environment.pluginContainer.resolveId(source)
118
+
119
+ // Config paths are app-root relative by default, but package/alias config
120
+ // entries remain supported when no root-relative file resolves.
121
+ if (!environmentResolution && kind === 'config' && source !== moduleName) {
122
+ environmentResolution = await environment.pluginContainer.resolveId(moduleName)
80
123
  }
81
- state.fullConfigLoaded = true
82
- })()
83
124
 
84
- return state.fullConfigLoadPromise
85
- }
125
+ const resolvedId = environmentResolution?.id
86
126
 
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
127
+ if (!resolvedId) {
128
+ throw new Error(
129
+ `Unable to resolve ${moduleName} in the Tamagui Vite environment (plugins: ${environment.plugins.map((plugin) => plugin.name).join(', ')})`
130
+ )
131
+ }
132
+
133
+ return {
134
+ moduleName,
135
+ id: resolvedId,
136
+ module: (await environment.runner.import(resolvedId)) as Record<string, unknown>,
137
+ }
138
+ }
139
+
140
+ const evaluateProjectModules = async (
141
+ options: TamaguiOptions
142
+ ): Promise<EvaluatedProjectModules> => {
143
+ if (!environment) {
144
+ throw new Error(
145
+ `Cannot evaluate Tamagui without the ${TAMAGUI_EVALUATION_ENVIRONMENT} Vite environment`
146
+ )
147
+ }
148
+
149
+ const root = environment.config.root
150
+ const config = await resolveAndImport(
151
+ options.config || 'tamagui.config.ts',
152
+ root,
153
+ 'config'
154
+ )
155
+ const components = await Promise.all(
156
+ (options.components || []).map((name) => resolveAndImport(name, root, 'component'))
157
+ )
158
+
159
+ captureEvaluationDependencies([config, ...components])
160
+
161
+ return { config, components }
162
+ }
163
+
164
+ const loadProject = async (
165
+ options: TamaguiOptions
166
+ ): Promise<Static.CompilerProject> => {
167
+ if (projectPromise) return projectPromise
168
+
169
+ projectPromise = (async () => {
170
+ if (!environment) {
171
+ throw new Error(
172
+ `Cannot load Tamagui without the ${TAMAGUI_EVALUATION_ENVIRONMENT} Vite environment`
173
+ )
174
+ }
175
+
176
+ let evaluated: EvaluatedProjectModules | null = null
177
+ return Static.loadCompilerProject({
178
+ root: environment.config.root,
179
+ target: 'web',
180
+ options,
181
+ generation: `vite:${generation}`,
182
+ hostVersions: vitePluginVersions,
183
+ async load(normalizedOptions) {
184
+ evaluated = await evaluateProjectModules(normalizedOptions)
185
+ return Static.loadTamaguiFromModules(normalizedOptions, {
186
+ config: evaluated.config.module,
187
+ components: evaluated.components.map(({ moduleName, module }) => ({
188
+ moduleName,
189
+ module,
190
+ })),
191
+ stampSources: [...stampSources],
192
+ })
193
+ },
194
+ async resolveComponents(moduleNames) {
195
+ if (!evaluated) {
196
+ throw new Error('The Tamagui compiler project modules were not evaluated')
197
+ }
198
+ const byName = new Map(
199
+ evaluated.components.map(({ moduleName, id }) => [moduleName, id])
200
+ )
201
+ return moduleNames.map((moduleName) => {
202
+ const id = byName.get(moduleName)
203
+ if (!id) throw new Error(`Unable to resolve compiler component ${moduleName}`)
204
+ return { moduleName, id }
205
+ })
206
+ },
207
+ })
208
+ })()
209
+
210
+ return projectPromise
211
+ }
212
+
213
+ return {
214
+ getEnvironment: () => environment,
215
+ getGeneration: () => generation,
216
+ getLoadPromise: () => loadPromise,
217
+ getTamaguiOptions: () => loadedOptions,
218
+ async getTamaguiConfig() {
219
+ const options = await loadTamaguiBuildConfig()
220
+ if (options.disable) return null
221
+ return (await loadProject(options)).projectInfo.tamaguiConfig
222
+ },
223
+ async getCompilerProject() {
224
+ const options = await loadTamaguiBuildConfig()
225
+ return loadProject(options)
226
+ },
227
+ getEvaluationDependencies: () => [...evaluationDependencies],
228
+ isEvaluationDependency: (id: string) =>
229
+ evaluationDependencies.has(normalizeDependency(id)),
230
+ evaluateProjectModules,
231
+ loadTamaguiBuildConfig,
232
+
233
+ setEnvironment(next: RunnableDevEnvironment, options?: { owned?: boolean }) {
234
+ if (environment === next) return
235
+ environment = next
236
+ ownsEnvironment = options?.owned === true
237
+ generation++
238
+ projectPromise = null
239
+ },
240
+
241
+ invalidate(file?: string) {
242
+ if (file && environment) {
243
+ environment.runner.clearCache()
244
+ }
245
+ generation++
246
+ projectPromise = null
247
+ },
248
+
249
+ async ensureFullConfigLoaded() {
250
+ const options = await loadTamaguiBuildConfig()
251
+ if (!options.disable) {
252
+ await loadProject(options)
253
+ }
254
+ return [...evaluationDependencies]
255
+ },
256
+
257
+ async cleanup() {
258
+ try {
259
+ if (ownsEnvironment && environment) {
260
+ await environment.close()
261
+ }
262
+ } finally {
263
+ environment = null
264
+ ownsEnvironment = false
265
+ loadPromise = null
266
+ loadedOptions = null
267
+ projectPromise = null
268
+ }
269
+ },
270
+ }
94
271
  }