@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.
- package/dist/cjs/compilerStats.cjs +127 -0
- package/dist/cjs/extensions.cjs +26 -18
- package/dist/cjs/index.cjs +10 -11
- package/dist/cjs/internal.cjs +25 -0
- package/dist/cjs/loadTamagui.cjs +194 -85
- package/dist/cjs/plugin.cjs +1076 -440
- package/dist/cjs/zeroRuntime.cjs +154 -0
- package/dist/esm/compilerStats.mjs +98 -0
- package/dist/esm/compilerStats.mjs.map +1 -0
- package/dist/esm/extensions.mjs +13 -1
- package/dist/esm/extensions.mjs.map +1 -1
- package/dist/esm/index.js +1 -2
- package/dist/esm/index.mjs +1 -2
- package/dist/esm/internal.mjs +3 -0
- package/dist/esm/loadTamagui.mjs +178 -59
- package/dist/esm/loadTamagui.mjs.map +1 -1
- package/dist/esm/plugin.mjs +1058 -418
- package/dist/esm/plugin.mjs.map +1 -1
- package/dist/esm/zeroRuntime.mjs +120 -0
- package/dist/esm/zeroRuntime.mjs.map +1 -0
- package/package.json +24 -9
- package/src/compilerStats.ts +168 -0
- package/src/internal.ts +9 -0
- package/src/loadTamagui.ts +251 -74
- package/src/plugin.ts +1362 -246
- package/src/zeroRuntime.ts +205 -0
- package/types/compilerStats.d.ts +44 -0
- package/types/compilerStats.d.ts.map +1 -0
- package/types/internal.d.ts +10 -0
- package/types/internal.d.ts.map +1 -0
- package/types/loadTamagui.d.ts +33 -13
- package/types/loadTamagui.d.ts.map +1 -1
- package/types/plugin.d.ts +32 -4
- package/types/plugin.d.ts.map +1 -1
- package/types/zeroRuntime.d.ts +60 -0
- package/types/zeroRuntime.d.ts.map +1 -0
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/index.mjs.map +0 -1
package/src/plugin.ts
CHANGED
|
@@ -1,56 +1,367 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { getPragmaOptions } from '@tamagui/static-worker'
|
|
1
|
+
import Static from '@tamagui/static'
|
|
2
|
+
import type { TamaguiOptions, ZeroGraphReceipt } from '@tamagui/static'
|
|
4
3
|
import { createHash } from 'node:crypto'
|
|
5
|
-
import { readdirSync } from 'node:fs'
|
|
4
|
+
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
5
|
+
import { gzipSync } from 'node:zlib'
|
|
6
|
+
import { readFile } from 'node:fs/promises'
|
|
6
7
|
import { createRequire } from 'node:module'
|
|
7
8
|
import path from 'node:path'
|
|
8
9
|
import { fileURLToPath } from 'node:url'
|
|
9
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
createIdResolver,
|
|
12
|
+
createRunnableDevEnvironment,
|
|
13
|
+
defaultClientConditions,
|
|
14
|
+
defaultClientMainFields,
|
|
15
|
+
isRunnableDevEnvironment,
|
|
16
|
+
resolveConfig,
|
|
17
|
+
} from 'vite'
|
|
18
|
+
import type {
|
|
19
|
+
EnvironmentOptions,
|
|
20
|
+
EnvironmentModuleNode,
|
|
21
|
+
Plugin,
|
|
22
|
+
PluginOption,
|
|
23
|
+
ResolvedConfig,
|
|
24
|
+
ViteDevServer,
|
|
25
|
+
} from 'vite'
|
|
10
26
|
import type { Environment } from 'vite'
|
|
27
|
+
import type { ViteTamaguiLoader } from './loadTamagui'
|
|
28
|
+
import { createViteTamaguiLoader, TAMAGUI_EVALUATION_ENVIRONMENT } from './loadTamagui'
|
|
29
|
+
import {
|
|
30
|
+
createCompilerStatsReport,
|
|
31
|
+
formatCompilerStatsReport,
|
|
32
|
+
type CompilerModuleReport,
|
|
33
|
+
} from './compilerStats'
|
|
11
34
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
35
|
+
assertZeroGraph,
|
|
36
|
+
buildIsland,
|
|
37
|
+
createZeroRuntimeController,
|
|
38
|
+
finalizeZeroCSS,
|
|
39
|
+
zeroModuleKey,
|
|
40
|
+
ZERO_CSS_FILENAME,
|
|
41
|
+
ZERO_ISLAND_DIRNAME,
|
|
42
|
+
type ZeroIslandBuildContext,
|
|
43
|
+
type ZeroRuntimeController,
|
|
44
|
+
} from './zeroRuntime'
|
|
45
|
+
|
|
46
|
+
const environmentSpecificTransformPluginNames = new Set([
|
|
47
|
+
'one:compiler',
|
|
48
|
+
'one:compiler-css-to-js',
|
|
49
|
+
])
|
|
50
|
+
|
|
51
|
+
const oneTsconfigPathsPluginName = 'one:tsconfig-paths'
|
|
52
|
+
const bareTamaguiPackage = /^@tamagui\/[^/?#]+(?:[/?#]|$)/
|
|
53
|
+
const inlineEvaluationTamaguiPackage = /^@tamagui\/(?:config|core|slider|web)(?:[/?#]|$)/
|
|
54
|
+
const externalizablePackageExtensions = new Set(['', '.js', '.mjs', '.cjs'])
|
|
55
|
+
type EvaluationResolveIdHandler = (this: any, source: string, ...args: any[]) => any
|
|
56
|
+
type EvaluationBarePackageResolver = (
|
|
57
|
+
environment: Environment,
|
|
58
|
+
source: string,
|
|
59
|
+
importer?: string
|
|
60
|
+
) =>
|
|
61
|
+
| Promise<string | { id: string; external: true } | undefined>
|
|
62
|
+
| string
|
|
63
|
+
| { id: string; external: true }
|
|
64
|
+
| undefined
|
|
65
|
+
|
|
66
|
+
function createEvaluationResolveId(
|
|
67
|
+
plugin: Plugin,
|
|
68
|
+
resolveBarePackage?: EvaluationBarePackageResolver
|
|
69
|
+
): Plugin['resolveId'] {
|
|
70
|
+
const resolveId = plugin.resolveId
|
|
71
|
+
if (plugin.name !== oneTsconfigPathsPluginName || !resolveId) {
|
|
72
|
+
return resolveId
|
|
73
|
+
}
|
|
17
74
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
|
|
75
|
+
const handler = (
|
|
76
|
+
typeof resolveId === 'object' ? resolveId.handler : resolveId
|
|
77
|
+
) as EvaluationResolveIdHandler
|
|
78
|
+
const evaluationHandler = function (this: any, source: string, ...args: any[]) {
|
|
79
|
+
// One's TS-path resolver can map workspace package imports to Metro's CJS
|
|
80
|
+
// directory fallbacks before Vite can apply the package exports map. Keep
|
|
81
|
+
// user TS aliases in this resolver, but let Tamagui packages use Vite's
|
|
82
|
+
// normal package resolution and externalization policy.
|
|
83
|
+
if (bareTamaguiPackage.test(source)) {
|
|
84
|
+
const importer = typeof args[0] === 'string' ? args[0] : undefined
|
|
85
|
+
return resolveBarePackage?.(this.environment, source, importer)
|
|
86
|
+
}
|
|
87
|
+
return Reflect.apply(handler, this, [source, ...args])
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return typeof resolveId === 'object'
|
|
91
|
+
? { ...resolveId, handler: evaluationHandler }
|
|
92
|
+
: evaluationHandler
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function createEvaluationPluginFacade(
|
|
96
|
+
plugin: Plugin,
|
|
97
|
+
resolveBarePackage?: EvaluationBarePackageResolver
|
|
98
|
+
): Plugin {
|
|
99
|
+
return {
|
|
100
|
+
name: plugin.name,
|
|
101
|
+
enforce: plugin.enforce,
|
|
102
|
+
resolveId: createEvaluationResolveId(plugin, resolveBarePackage),
|
|
103
|
+
load: plugin.load,
|
|
104
|
+
transform: environmentSpecificTransformPluginNames.has(plugin.name)
|
|
105
|
+
? undefined
|
|
106
|
+
: plugin.transform,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
24
109
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
110
|
+
const tamaguiEvaluationPluginNames = new Set([
|
|
111
|
+
'tamagui',
|
|
112
|
+
'tamagui-extract',
|
|
113
|
+
'tamagui-rnw-lite',
|
|
114
|
+
])
|
|
115
|
+
|
|
116
|
+
function isEvaluationUserPlugin(plugin: Plugin) {
|
|
117
|
+
return (
|
|
118
|
+
!!(plugin.resolveId || plugin.load || plugin.transform) &&
|
|
119
|
+
plugin.name !== 'alias' &&
|
|
120
|
+
!plugin.name.startsWith('native:') &&
|
|
121
|
+
!plugin.name.startsWith('vite:') &&
|
|
122
|
+
!plugin.name.startsWith('builtin:vite-') &&
|
|
123
|
+
!tamaguiEvaluationPluginNames.has(plugin.name)
|
|
124
|
+
)
|
|
30
125
|
}
|
|
31
126
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
127
|
+
function isEvaluationCorePlugin(plugin: Plugin) {
|
|
128
|
+
return (
|
|
129
|
+
plugin.name === 'alias' ||
|
|
130
|
+
plugin.name.startsWith('vite:') ||
|
|
131
|
+
plugin.name.startsWith('builtin:vite-')
|
|
132
|
+
)
|
|
133
|
+
}
|
|
35
134
|
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
135
|
+
function isConfiguredEvaluationPackage(source: string, packages: Set<string>) {
|
|
136
|
+
const cleanSource = source.split(/[?#]/, 1)[0]
|
|
137
|
+
return [...packages].some(
|
|
138
|
+
(packageName) =>
|
|
139
|
+
cleanSource === packageName || cleanSource.startsWith(`${packageName}/`)
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getEvaluationPackageName(source: string | undefined) {
|
|
144
|
+
if (!source) return
|
|
145
|
+
const cleanSource = source.split(/[?#]/, 1)[0]
|
|
146
|
+
if (
|
|
147
|
+
!cleanSource ||
|
|
148
|
+
cleanSource.startsWith('.') ||
|
|
149
|
+
cleanSource.startsWith('#') ||
|
|
150
|
+
cleanSource.startsWith('\0') ||
|
|
151
|
+
path.isAbsolute(cleanSource)
|
|
152
|
+
) {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
if (cleanSource.startsWith('@')) {
|
|
156
|
+
const [scope, name] = cleanSource.split('/')
|
|
157
|
+
return scope && name ? `${scope}/${name}` : undefined
|
|
39
158
|
}
|
|
40
|
-
|
|
159
|
+
const [name] = cleanSource.split('/')
|
|
160
|
+
return name && !path.extname(name) ? name : undefined
|
|
41
161
|
}
|
|
42
162
|
|
|
43
|
-
function
|
|
44
|
-
|
|
163
|
+
function getInstalledTamaguiPackages(
|
|
164
|
+
root: string,
|
|
165
|
+
configuredEvaluationPackages: Set<string>
|
|
166
|
+
) {
|
|
167
|
+
const packageRequire = createRequire(path.join(root, 'package.json'))
|
|
168
|
+
const packages = new Set<string>()
|
|
169
|
+
|
|
170
|
+
for (const modulePath of packageRequire.resolve.paths('@tamagui/core') || []) {
|
|
171
|
+
const scopePath = path.join(modulePath, '@tamagui')
|
|
172
|
+
if (!existsSync(scopePath)) continue
|
|
173
|
+
for (const entry of readdirSync(scopePath, { withFileTypes: true })) {
|
|
174
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue
|
|
175
|
+
const packageName = `@tamagui/${entry.name}`
|
|
176
|
+
if (
|
|
177
|
+
!inlineEvaluationTamaguiPackage.test(packageName) &&
|
|
178
|
+
!configuredEvaluationPackages.has(packageName)
|
|
179
|
+
) {
|
|
180
|
+
packages.add(packageName)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return packages
|
|
45
186
|
}
|
|
46
187
|
|
|
47
|
-
function
|
|
48
|
-
|
|
188
|
+
function getEvaluationResolve(
|
|
189
|
+
resolve: ResolvedConfig['environments'][string]['resolve'],
|
|
190
|
+
root: string,
|
|
191
|
+
disableTsconfigPaths: boolean,
|
|
192
|
+
configuredEvaluationPackages: Set<string>
|
|
193
|
+
) {
|
|
194
|
+
return {
|
|
195
|
+
...resolve,
|
|
196
|
+
external:
|
|
197
|
+
resolve.external === true
|
|
198
|
+
? (true as const)
|
|
199
|
+
: [
|
|
200
|
+
...new Set([
|
|
201
|
+
...(resolve.external || []).filter(
|
|
202
|
+
(packageName) =>
|
|
203
|
+
!isConfiguredEvaluationPackage(
|
|
204
|
+
packageName,
|
|
205
|
+
configuredEvaluationPackages
|
|
206
|
+
)
|
|
207
|
+
),
|
|
208
|
+
...getInstalledTamaguiPackages(root, configuredEvaluationPackages),
|
|
209
|
+
]),
|
|
210
|
+
],
|
|
211
|
+
...(disableTsconfigPaths && { tsconfigPaths: false }),
|
|
212
|
+
}
|
|
49
213
|
}
|
|
50
214
|
|
|
51
|
-
function
|
|
52
|
-
|
|
53
|
-
|
|
215
|
+
function isConfiguredExternalPackage(
|
|
216
|
+
source: string,
|
|
217
|
+
external: string[] | true | undefined
|
|
218
|
+
) {
|
|
219
|
+
if (external === true) return true
|
|
220
|
+
const cleanSource = source.split(/[?#]/, 1)[0]
|
|
221
|
+
return external?.some(
|
|
222
|
+
(packageName) =>
|
|
223
|
+
cleanSource === packageName || cleanSource.startsWith(`${packageName}/`)
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function createServeEvaluationConfig(
|
|
228
|
+
config: ResolvedConfig,
|
|
229
|
+
configuredEvaluationPackages: Set<string>
|
|
230
|
+
): ResolvedConfig {
|
|
231
|
+
const environment = config.environments[TAMAGUI_EVALUATION_ENVIRONMENT]
|
|
232
|
+
let packageResolver: ReturnType<typeof createIdResolver> | undefined
|
|
233
|
+
const resolveBarePackage: EvaluationBarePackageResolver = async (
|
|
234
|
+
evaluationEnvironment,
|
|
235
|
+
source,
|
|
236
|
+
importer
|
|
237
|
+
) => {
|
|
238
|
+
const resolved = await packageResolver?.(evaluationEnvironment, source, importer)
|
|
239
|
+
if (!resolved) return
|
|
240
|
+
const cleanResolved = resolved.split(/[?#]/, 1)[0]
|
|
241
|
+
if (
|
|
242
|
+
!inlineEvaluationTamaguiPackage.test(source) &&
|
|
243
|
+
!isConfiguredEvaluationPackage(source, configuredEvaluationPackages) &&
|
|
244
|
+
isConfiguredExternalPackage(source, evaluationEnvironment.config.resolve.external)
|
|
245
|
+
) {
|
|
246
|
+
return { id: source, external: true }
|
|
247
|
+
}
|
|
248
|
+
if (
|
|
249
|
+
inlineEvaluationTamaguiPackage.test(source) ||
|
|
250
|
+
isConfiguredEvaluationPackage(source, configuredEvaluationPackages) ||
|
|
251
|
+
!normalizePath(cleanResolved).includes('/node_modules/') ||
|
|
252
|
+
!externalizablePackageExtensions.has(path.extname(cleanResolved))
|
|
253
|
+
) {
|
|
254
|
+
return resolved
|
|
255
|
+
}
|
|
256
|
+
return { id: source, external: true }
|
|
257
|
+
}
|
|
258
|
+
const plugins = environment.plugins.flatMap((plugin) => {
|
|
259
|
+
if (isEvaluationCorePlugin(plugin)) {
|
|
260
|
+
return [plugin]
|
|
261
|
+
}
|
|
262
|
+
if (isEvaluationUserPlugin(plugin)) {
|
|
263
|
+
return [createEvaluationPluginFacade(plugin, resolveBarePackage)]
|
|
264
|
+
}
|
|
265
|
+
return []
|
|
266
|
+
})
|
|
267
|
+
const resolve = getEvaluationResolve(
|
|
268
|
+
environment.resolve,
|
|
269
|
+
config.root,
|
|
270
|
+
plugins.some((plugin) => plugin.name === oneTsconfigPathsPluginName),
|
|
271
|
+
configuredEvaluationPackages
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
const evaluationConfig: ResolvedConfig = {
|
|
275
|
+
...config,
|
|
276
|
+
environments: {
|
|
277
|
+
...config.environments,
|
|
278
|
+
[TAMAGUI_EVALUATION_ENVIRONMENT]: {
|
|
279
|
+
...environment,
|
|
280
|
+
plugins,
|
|
281
|
+
resolve,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
}
|
|
285
|
+
packageResolver = createIdResolver(evaluationConfig)
|
|
286
|
+
return evaluationConfig
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function createOwnedEvaluationConfig(
|
|
290
|
+
config: ResolvedConfig,
|
|
291
|
+
configuredEvaluationPackages: Set<string>
|
|
292
|
+
) {
|
|
293
|
+
const environment = config.environments[TAMAGUI_EVALUATION_ENVIRONMENT]
|
|
294
|
+
const plugins = environment.plugins
|
|
295
|
+
.filter(isEvaluationUserPlugin)
|
|
296
|
+
.map((plugin) => createEvaluationPluginFacade(plugin))
|
|
297
|
+
const resolve = getEvaluationResolve(
|
|
298
|
+
environment.resolve,
|
|
299
|
+
config.root,
|
|
300
|
+
plugins.some((plugin) => plugin.name === oneTsconfigPathsPluginName),
|
|
301
|
+
configuredEvaluationPackages
|
|
302
|
+
)
|
|
303
|
+
const { createEnvironment: _createEnvironment, ...dev } = environment.dev
|
|
304
|
+
|
|
305
|
+
// ModuleRunner needs Vite's serve-time core pipeline (especially import
|
|
306
|
+
// analysis), but user plugin selection must remain the already-resolved
|
|
307
|
+
// pipeline for the outer command. The facades retain only evaluation hooks,
|
|
308
|
+
// so resolving this owned config cannot replay user configuration or outer
|
|
309
|
+
// lifecycles.
|
|
310
|
+
return resolveConfig(
|
|
311
|
+
{
|
|
312
|
+
configFile: false,
|
|
313
|
+
root: config.root,
|
|
314
|
+
mode: config.mode,
|
|
315
|
+
logLevel: config.logLevel,
|
|
316
|
+
plugins,
|
|
317
|
+
define: environment.define,
|
|
318
|
+
resolve,
|
|
319
|
+
environments: {
|
|
320
|
+
[TAMAGUI_EVALUATION_ENVIRONMENT]: {
|
|
321
|
+
consumer: environment.consumer,
|
|
322
|
+
keepProcessEnv: environment.keepProcessEnv,
|
|
323
|
+
define: environment.define,
|
|
324
|
+
resolve,
|
|
325
|
+
optimizeDeps: environment.optimizeDeps,
|
|
326
|
+
dev: {
|
|
327
|
+
...dev,
|
|
328
|
+
moduleRunnerTransform: true,
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
'serve',
|
|
334
|
+
config.mode
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// handle ESM/CJS duality for plugin dependencies - resolve from plugin's location, not user's project
|
|
339
|
+
const _pluginRequire = createRequire(
|
|
340
|
+
typeof __filename === 'string' ? __filename : fileURLToPath(import.meta.url)
|
|
341
|
+
)
|
|
342
|
+
const resolve = (name: string) => _pluginRequire.resolve(name)
|
|
343
|
+
const normalizePath = (value: string) => value.replace(/\\/g, '/')
|
|
344
|
+
|
|
345
|
+
const PLUGIN_INSTANCE_KEY = '__tamagui_vite_plugin_instance__'
|
|
346
|
+
|
|
347
|
+
function reportCompilerStats(root: string, reports: Map<string, CompilerModuleReport>) {
|
|
348
|
+
const report = createCompilerStatsReport(root, reports)
|
|
349
|
+
console.info(
|
|
350
|
+
formatCompilerStatsReport(report, process.env.TAMAGUI_COMPILER_STATS === 'verbose')
|
|
351
|
+
)
|
|
352
|
+
if (process.env.TAMAGUI_COMPILER_STATS_FILE) {
|
|
353
|
+
const outputPath = path.resolve(root, process.env.TAMAGUI_COMPILER_STATS_FILE)
|
|
354
|
+
writeFileSync(outputPath, `${JSON.stringify(report, null, 2)}\n`)
|
|
355
|
+
console.info(
|
|
356
|
+
`[tamagui] compiler stats JSON: ${path.relative(process.cwd(), outputPath)}`
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function getNextPluginInstanceId() {
|
|
362
|
+
const next = ((globalThis as any)[PLUGIN_INSTANCE_KEY] || 0) + 1
|
|
363
|
+
;(globalThis as any)[PLUGIN_INSTANCE_KEY] = next
|
|
364
|
+
return next
|
|
54
365
|
}
|
|
55
366
|
|
|
56
367
|
// resolves package ids against the user's project root (not the plugin's
|
|
@@ -81,14 +392,6 @@ function addIfInstalled(
|
|
|
81
392
|
}
|
|
82
393
|
}
|
|
83
394
|
|
|
84
|
-
// pending extractions map - dedupes concurrent requests for same file
|
|
85
|
-
function getPendingExtractions(): Map<string, Promise<CacheEntry | null>> {
|
|
86
|
-
if (!(globalThis as any)[PENDING_KEY]) {
|
|
87
|
-
;(globalThis as any)[PENDING_KEY] = new Map()
|
|
88
|
-
}
|
|
89
|
-
return (globalThis as any)[PENDING_KEY]
|
|
90
|
-
}
|
|
91
|
-
|
|
92
395
|
type AliasOptions = {
|
|
93
396
|
/** use @tamagui/react-native-web-lite, 'without-animated' for smaller bundle */
|
|
94
397
|
rnwLite?: boolean | 'without-animated'
|
|
@@ -124,8 +427,8 @@ export function tamaguiAliases(options: AliasOptions = {}): AliasEntry[] {
|
|
|
124
427
|
: 'dist/esm/index.mjs'
|
|
125
428
|
)
|
|
126
429
|
)
|
|
127
|
-
//
|
|
128
|
-
//
|
|
430
|
+
// only alias deep imports that rnw-lite actually implements. unimplemented
|
|
431
|
+
// react-native-web exports must fall through to the full package.
|
|
129
432
|
const rnwlFlatModules = readdirSync(path.join(rnwlBase, 'dist/esm'))
|
|
130
433
|
.filter((file) => file.endsWith('.mjs'))
|
|
131
434
|
.map((file) => file.slice(0, -'.mjs'.length))
|
|
@@ -134,15 +437,6 @@ export function tamaguiAliases(options: AliasOptions = {}): AliasEntry[] {
|
|
|
134
437
|
{
|
|
135
438
|
// map deep RNW paths like dist/exports/StyleSheet/preprocess to rnw-lite's flat structure
|
|
136
439
|
// extracts the final path segment (e.g. "preprocess" or "createReactDOMStyle")
|
|
137
|
-
//
|
|
138
|
-
// only match segments rnw-lite actually ships. it implements part of
|
|
139
|
-
// react-native-web's export surface, not all of it, and there is no
|
|
140
|
-
// flat StyleSheet.mjs. expo sdk 56 added
|
|
141
|
-
// expo/src/launch/AppRegistry.web.tsx, which does
|
|
142
|
-
// `require('react-native-web/dist/exports/StyleSheet')`; the unscoped
|
|
143
|
-
// pattern rewrote that onto a file that does not exist and failed the
|
|
144
|
-
// whole optimize. anything lite lacks now falls through to the real
|
|
145
|
-
// package.
|
|
146
440
|
find: new RegExp(
|
|
147
441
|
`^react-native(?:-web)?\\/dist\\/(?:exports|modules)\\/(?:.*\\/)?(${rnwlFlatModules.join('|')})$`
|
|
148
442
|
),
|
|
@@ -170,18 +464,269 @@ export function tamaguiAliases(options: AliasOptions = {}): AliasEntry[] {
|
|
|
170
464
|
return aliases
|
|
171
465
|
}
|
|
172
466
|
|
|
173
|
-
|
|
467
|
+
type VxrnNativePluginContext = {
|
|
468
|
+
root: string
|
|
469
|
+
platform: 'ios' | 'android'
|
|
470
|
+
dev: boolean
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function createTamaguiNativePlugin(
|
|
474
|
+
tamaguiOptionsIn: TamaguiOptions,
|
|
475
|
+
nativeContext?: VxrnNativePluginContext
|
|
476
|
+
): Plugin {
|
|
477
|
+
let compilerFrontend = new Static.CompilerFrontend()
|
|
478
|
+
const projectDependencies = new Set<string>()
|
|
479
|
+
let root = nativeContext?.root || process.cwd()
|
|
480
|
+
let projectPromise: Promise<Static.CompilerProject | null> | null = null
|
|
481
|
+
let rebuildProject = false
|
|
482
|
+
let generation = 0
|
|
483
|
+
|
|
484
|
+
const loadProject = async (resolveModule: (specifier: string) => Promise<string>) => {
|
|
485
|
+
if (projectPromise) return projectPromise
|
|
486
|
+
const shouldRebuild = rebuildProject
|
|
487
|
+
rebuildProject = false
|
|
488
|
+
const pending = (async () => {
|
|
489
|
+
projectDependencies.clear()
|
|
490
|
+
const loadedOptions = await Static.loadTamaguiBuildConfigAsync({
|
|
491
|
+
...tamaguiOptionsIn,
|
|
492
|
+
root,
|
|
493
|
+
platform: 'native',
|
|
494
|
+
outputCSS: undefined,
|
|
495
|
+
})
|
|
496
|
+
const options = { ...loadedOptions, root, outputCSS: undefined }
|
|
497
|
+
for (const dependency of Static.getTamaguiBuildConfigDependencies(loadedOptions)) {
|
|
498
|
+
projectDependencies.add(normalizePath(dependency))
|
|
499
|
+
}
|
|
500
|
+
if (options.disable || options.disableExtraction) return null
|
|
501
|
+
const project = await Static.loadCompilerProject({
|
|
502
|
+
root,
|
|
503
|
+
target: 'native',
|
|
504
|
+
options,
|
|
505
|
+
rebuild: shouldRebuild,
|
|
506
|
+
generation: `vite-native:${generation + 1}`,
|
|
507
|
+
missingProjectMessage:
|
|
508
|
+
'Unable to load the Tamagui project for Vite native compilation',
|
|
509
|
+
async resolveComponents(moduleNames) {
|
|
510
|
+
return Promise.all(
|
|
511
|
+
moduleNames.map(async (moduleName) => {
|
|
512
|
+
const id = await resolveModule(moduleName)
|
|
513
|
+
projectDependencies.add(normalizePath(id.split(/[?#]/, 1)[0]))
|
|
514
|
+
return { moduleName, id }
|
|
515
|
+
})
|
|
516
|
+
)
|
|
517
|
+
},
|
|
518
|
+
})
|
|
519
|
+
for (const dependency of project.projectInfo.dependencies ?? []) {
|
|
520
|
+
projectDependencies.add(normalizePath(dependency.split(/[?#]/, 1)[0]))
|
|
521
|
+
}
|
|
522
|
+
const configPath = options.config || 'tamagui.config.ts'
|
|
523
|
+
projectDependencies.add(
|
|
524
|
+
normalizePath(
|
|
525
|
+
path.isAbsolute(configPath) ? configPath : path.resolve(root, configPath)
|
|
526
|
+
)
|
|
527
|
+
)
|
|
528
|
+
const buildFile = options.buildFile || 'tamagui.build.ts'
|
|
529
|
+
projectDependencies.add(
|
|
530
|
+
normalizePath(
|
|
531
|
+
path.isAbsolute(buildFile) ? buildFile : path.resolve(root, buildFile)
|
|
532
|
+
)
|
|
533
|
+
)
|
|
534
|
+
if (options.themeBuilder?.input) {
|
|
535
|
+
projectDependencies.add(
|
|
536
|
+
normalizePath(
|
|
537
|
+
path.isAbsolute(options.themeBuilder.input)
|
|
538
|
+
? options.themeBuilder.input
|
|
539
|
+
: path.resolve(root, options.themeBuilder.input)
|
|
540
|
+
)
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
generation++
|
|
544
|
+
return project
|
|
545
|
+
})()
|
|
546
|
+
const guarded = pending.catch((error) => {
|
|
547
|
+
if (projectPromise === guarded) projectPromise = null
|
|
548
|
+
rebuildProject = true
|
|
549
|
+
throw error
|
|
550
|
+
})
|
|
551
|
+
projectPromise = guarded
|
|
552
|
+
return projectPromise
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
name: 'tamagui-native-compiler',
|
|
557
|
+
enforce: 'post',
|
|
558
|
+
configResolved(config) {
|
|
559
|
+
root = config.root
|
|
560
|
+
},
|
|
561
|
+
watchChange(id) {
|
|
562
|
+
if (projectDependencies.has(normalizePath(id.split(/[?#]/, 1)[0]))) {
|
|
563
|
+
rebuildProject = true
|
|
564
|
+
projectPromise = null
|
|
565
|
+
compilerFrontend = new Static.CompilerFrontend()
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
transform: {
|
|
569
|
+
order: 'pre',
|
|
570
|
+
async handler(code, id) {
|
|
571
|
+
const environmentName = nativeContext?.platform || this.environment?.name
|
|
572
|
+
if (environmentName !== 'ios' && environmentName !== 'android') return
|
|
573
|
+
const [validId] = id.split('?')
|
|
574
|
+
if (
|
|
575
|
+
!validId ||
|
|
576
|
+
!/\.[jt]sx$/.test(validId) ||
|
|
577
|
+
normalizePath(validId).split('/').includes('node_modules')
|
|
578
|
+
) {
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
const { shouldDisable } = await Static.getPragmaOptions({
|
|
582
|
+
source: code,
|
|
583
|
+
path: validId,
|
|
584
|
+
})
|
|
585
|
+
if (shouldDisable) return
|
|
586
|
+
|
|
587
|
+
const resolve = async (specifier: string, importer: string) => {
|
|
588
|
+
const resolution = await this.resolve(specifier, importer, { skipSelf: true })
|
|
589
|
+
return resolution
|
|
590
|
+
? { id: resolution.id, external: resolution.external === true }
|
|
591
|
+
: null
|
|
592
|
+
}
|
|
593
|
+
const project = await loadProject(async (specifier) => {
|
|
594
|
+
const resolution = await resolve(
|
|
595
|
+
specifier,
|
|
596
|
+
path.join(root, '__tamagui_native.tsx')
|
|
597
|
+
)
|
|
598
|
+
if (!resolution) {
|
|
599
|
+
throw new Error(`Unable to resolve native compiler component ${specifier}`)
|
|
600
|
+
}
|
|
601
|
+
return resolution.id
|
|
602
|
+
})
|
|
603
|
+
if (!project) return
|
|
604
|
+
for (const dependency of projectDependencies) this.addWatchFile(dependency)
|
|
605
|
+
|
|
606
|
+
const result = await compilerFrontend.compile({
|
|
607
|
+
id: validId,
|
|
608
|
+
source: code,
|
|
609
|
+
root,
|
|
610
|
+
target: 'native',
|
|
611
|
+
project,
|
|
612
|
+
resolve,
|
|
613
|
+
load: async (dependencyId) => {
|
|
614
|
+
const cleanDependencyId = dependencyId.split(/[?#]/, 1)[0]
|
|
615
|
+
if (!path.isAbsolute(cleanDependencyId)) return null
|
|
616
|
+
try {
|
|
617
|
+
return await readFile(cleanDependencyId, 'utf8')
|
|
618
|
+
} catch {
|
|
619
|
+
return null
|
|
620
|
+
}
|
|
621
|
+
},
|
|
622
|
+
})
|
|
623
|
+
for (const dependency of result.plan.dependencies) {
|
|
624
|
+
if (path.isAbsolute(dependency)) this.addWatchFile(dependency)
|
|
625
|
+
}
|
|
626
|
+
if (result.plan.css) {
|
|
627
|
+
throw new Error(
|
|
628
|
+
`Native Tamagui compilation produced unexpected CSS for ${validId}`
|
|
629
|
+
)
|
|
630
|
+
}
|
|
631
|
+
return result.output.changed
|
|
632
|
+
? { code: result.output.code, map: result.output.map as any }
|
|
633
|
+
: undefined
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export function tamaguiNativePlugin(tamaguiOptionsIn: TamaguiOptions = {}): Plugin {
|
|
640
|
+
const plugin = createTamaguiNativePlugin(tamaguiOptionsIn)
|
|
641
|
+
const api =
|
|
642
|
+
plugin.api && typeof plugin.api === 'object'
|
|
643
|
+
? (plugin.api as Record<string, unknown>)
|
|
644
|
+
: {}
|
|
645
|
+
|
|
646
|
+
return {
|
|
647
|
+
...plugin,
|
|
648
|
+
api: {
|
|
649
|
+
...api,
|
|
650
|
+
vxrnNative: (context: VxrnNativePluginContext) =>
|
|
651
|
+
createTamaguiNativePlugin(tamaguiOptionsIn, context),
|
|
652
|
+
},
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export type TamaguiVitePluginOptions = TamaguiOptions & {
|
|
657
|
+
disableResolveConfig?: boolean
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export type TamaguiInternalPluginOptions = TamaguiVitePluginOptions & {
|
|
661
|
+
/**
|
|
662
|
+
* Wraps compiler-extracted Tamagui CSS before it is served.
|
|
663
|
+
* `@tamagui/tailwind/vite` uses it to put those rules in `@layer tamagui`, which is
|
|
664
|
+
* what orders them against official Tailwind's `theme`/`utilities` layers.
|
|
665
|
+
*/
|
|
666
|
+
wrapExtractedCSS?: (css: string) => string
|
|
667
|
+
/**
|
|
668
|
+
* Set by the zero-runtime controller when this invocation is an island child
|
|
669
|
+
* build. The island keeps the full runtime and contributes its compiler atomic
|
|
670
|
+
* CSS to the parent's single artifact instead of injecting its own.
|
|
671
|
+
*/
|
|
672
|
+
zeroIslandBuild?: ZeroIslandBuildContext
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* The base Tamagui Vite plugins plus the one config loader they evaluate through.
|
|
677
|
+
*
|
|
678
|
+
* `@tamagui/tailwind/vite` wraps this: it reuses the returned loader for its own
|
|
679
|
+
* scanner plugin, so the Tamagui config is evaluated exactly once for both.
|
|
680
|
+
*/
|
|
681
|
+
export function createTamaguiPlugins({
|
|
174
682
|
disableResolveConfig,
|
|
683
|
+
wrapExtractedCSS = (css) => css,
|
|
684
|
+
zeroIslandBuild,
|
|
175
685
|
...tamaguiOptionsIn
|
|
176
|
-
}:
|
|
177
|
-
|
|
178
|
-
|
|
686
|
+
}: TamaguiInternalPluginOptions = {}): {
|
|
687
|
+
plugins: PluginOption[]
|
|
688
|
+
loader: ViteTamaguiLoader
|
|
689
|
+
} {
|
|
179
690
|
// extraction ON by default, set disableExtraction: true to opt out
|
|
180
691
|
let shouldExtract = !tamaguiOptionsIn.disableExtraction
|
|
181
|
-
let watcher: Promise<{ dispose: () => void } | void | undefined> | undefined
|
|
182
692
|
|
|
183
693
|
// temporary vxrn native env bridge
|
|
184
694
|
const enableNativeEnv = !!globalThis.__vxrnEnableNativeEnv
|
|
695
|
+
const tamaguiLoader = createViteTamaguiLoader(tamaguiOptionsIn)
|
|
696
|
+
const compilerFrontend = new Static.CompilerFrontend()
|
|
697
|
+
const pluginInstanceId = getNextPluginInstanceId()
|
|
698
|
+
const configuredEvaluationPackages = new Set<string>()
|
|
699
|
+
let buildEnvironmentPromise: Promise<void> | null = null
|
|
700
|
+
let buildCleanupPromise: Promise<void> | null = null
|
|
701
|
+
const activeBuildEnvironments = new Set<Environment>()
|
|
702
|
+
const compilerReports =
|
|
703
|
+
process.env.TAMAGUI_COMPILER_STATS || process.env.TAMAGUI_COMPILER_STATS_FILE
|
|
704
|
+
? new Map<string, CompilerModuleReport>()
|
|
705
|
+
: null
|
|
706
|
+
|
|
707
|
+
const releaseBuildEnvironment = async (environment: Environment) => {
|
|
708
|
+
if (!activeBuildEnvironments.delete(environment) || activeBuildEnvironments.size) {
|
|
709
|
+
return
|
|
710
|
+
}
|
|
711
|
+
if (compilerReports?.size) {
|
|
712
|
+
reportCompilerStats(config?.root ?? process.cwd(), compilerReports)
|
|
713
|
+
}
|
|
714
|
+
const currentCleanup = Promise.resolve().then(async () => {
|
|
715
|
+
try {
|
|
716
|
+
await tamaguiLoader.cleanup()
|
|
717
|
+
} finally {
|
|
718
|
+
buildEnvironmentPromise = null
|
|
719
|
+
}
|
|
720
|
+
})
|
|
721
|
+
buildCleanupPromise = currentCleanup
|
|
722
|
+
try {
|
|
723
|
+
await currentCleanup
|
|
724
|
+
} finally {
|
|
725
|
+
if (buildCleanupPromise === currentCleanup) {
|
|
726
|
+
buildCleanupPromise = null
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
185
730
|
|
|
186
731
|
const extensions = [
|
|
187
732
|
`.web.mjs`,
|
|
@@ -198,14 +743,51 @@ export function tamaguiPlugin({
|
|
|
198
743
|
'.json',
|
|
199
744
|
]
|
|
200
745
|
|
|
746
|
+
const getEvaluationEnvironmentOptions = (): EnvironmentOptions => ({
|
|
747
|
+
consumer: 'server',
|
|
748
|
+
keepProcessEnv: true,
|
|
749
|
+
define: {
|
|
750
|
+
'process.env.IS_STATIC': JSON.stringify('is_static'),
|
|
751
|
+
'process.env.TAMAGUI_IS_CLIENT': JSON.stringify(false),
|
|
752
|
+
'process.env.TAMAGUI_IS_SERVER': JSON.stringify(true),
|
|
753
|
+
'process.env.TAMAGUI_TARGET': JSON.stringify('web'),
|
|
754
|
+
'process.env.TAMAGUI_ENVIRONMENT': JSON.stringify(TAMAGUI_EVALUATION_ENVIRONMENT),
|
|
755
|
+
// Config evaluation must retain createTamagui and CSS generation even when
|
|
756
|
+
// the client graph is zero or the client claims the artifact. Inheriting
|
|
757
|
+
// either literal from the outer build empties the artifact it generates.
|
|
758
|
+
'process.env.TAMAGUI_RUNTIME': JSON.stringify('full'),
|
|
759
|
+
'process.env.TAMAGUI_DID_OUTPUT_CSS': JSON.stringify(''),
|
|
760
|
+
// Client configs may strip theme values. Compiler evaluation and outputCSS
|
|
761
|
+
// must use the full config regardless of which outer Vite environment runs last.
|
|
762
|
+
'process.env.VITE_ENVIRONMENT': JSON.stringify('ssr'),
|
|
763
|
+
'process.env.TAMAGUI_DISABLE_SLIDER_INTERVAL': JSON.stringify('1'),
|
|
764
|
+
},
|
|
765
|
+
resolve: {
|
|
766
|
+
conditions: [...defaultClientConditions],
|
|
767
|
+
mainFields: [...defaultClientMainFields],
|
|
768
|
+
noExternal: [inlineEvaluationTamaguiPackage, ...configuredEvaluationPackages],
|
|
769
|
+
extensions,
|
|
770
|
+
},
|
|
771
|
+
dev: {
|
|
772
|
+
createEnvironment(name, resolved) {
|
|
773
|
+
const evaluationConfig = createServeEvaluationConfig(
|
|
774
|
+
resolved,
|
|
775
|
+
configuredEvaluationPackages
|
|
776
|
+
)
|
|
777
|
+
return createRunnableDevEnvironment(name, evaluationConfig)
|
|
778
|
+
},
|
|
779
|
+
moduleRunnerTransform: true,
|
|
780
|
+
},
|
|
781
|
+
})
|
|
782
|
+
|
|
201
783
|
// start loading immediately but don't block
|
|
202
|
-
loadTamaguiBuildConfig(
|
|
784
|
+
tamaguiLoader.loadTamaguiBuildConfig()
|
|
203
785
|
|
|
204
786
|
// helper to await load when needed
|
|
205
787
|
const ensureLoaded = async () => {
|
|
206
|
-
const promise = getLoadPromise()
|
|
788
|
+
const promise = tamaguiLoader.getLoadPromise()
|
|
207
789
|
if (promise) await promise
|
|
208
|
-
const options = getTamaguiOptions()
|
|
790
|
+
const options = tamaguiLoader.getTamaguiOptions()
|
|
209
791
|
// update shouldExtract from loaded config (tamagui.build.ts)
|
|
210
792
|
if (options) {
|
|
211
793
|
shouldExtract = !options.disableExtraction
|
|
@@ -216,12 +798,25 @@ export function tamaguiPlugin({
|
|
|
216
798
|
// extract plugin state
|
|
217
799
|
const getHash = (input: string) => createHash('sha1').update(input).digest('base64')
|
|
218
800
|
|
|
219
|
-
// use shared cache across environments
|
|
220
|
-
const memoryCache = getSharedCache()
|
|
221
|
-
|
|
222
801
|
const cssMap = new Map<string, string>()
|
|
802
|
+
const transformedModuleIds = new Set<string>()
|
|
803
|
+
const compilerHotUpdateSignatures = new Map<string, string>()
|
|
804
|
+
const compilerHotReloadSignatures = new Map<string, string>()
|
|
223
805
|
let config: ResolvedConfig
|
|
224
806
|
let server: ViteDevServer
|
|
807
|
+
let zero: ZeroRuntimeController | null = null
|
|
808
|
+
let zeroReceipt: ZeroGraphReceipt | null = null
|
|
809
|
+
// closeBundle runs even when the build already failed, so a check there would
|
|
810
|
+
// replace the real error with a derived one
|
|
811
|
+
let zeroBuildFailed = false
|
|
812
|
+
// The compiled-global-CSS tier: an ordinary compiled build that also owns an
|
|
813
|
+
// `outputCSS` artifact and therefore derives TAMAGUI_DID_OUTPUT_CSS from it.
|
|
814
|
+
let globalCSS: Static.GlobalCSSOwnership | null = null
|
|
815
|
+
let globalCSSExpected: string | null = null
|
|
816
|
+
// How many HTML entries received the zero artifact's stylesheet link. A zero
|
|
817
|
+
// build with no HTML entry strips the rules and loads nothing.
|
|
818
|
+
let zeroHtmlEntries = 0
|
|
819
|
+
let zeroDevIslands: Promise<unknown> = Promise.resolve()
|
|
225
820
|
const virtualExt = `.tamagui.css`
|
|
226
821
|
|
|
227
822
|
const getAbsoluteVirtualFileId = (filePath: string) => {
|
|
@@ -231,6 +826,20 @@ export function tamaguiPlugin({
|
|
|
231
826
|
return normalizePath(path.join(config.root, filePath))
|
|
232
827
|
}
|
|
233
828
|
|
|
829
|
+
const isAppJSXSource = (filePath: string) => {
|
|
830
|
+
if (!/\.[jt]sx$/.test(filePath)) return false
|
|
831
|
+
const relative = path.relative(config.root, filePath)
|
|
832
|
+
return (
|
|
833
|
+
relative !== '' &&
|
|
834
|
+
relative !== '..' &&
|
|
835
|
+
!relative.startsWith(`..${path.sep}`) &&
|
|
836
|
+
!relative.split(path.sep).includes('node_modules')
|
|
837
|
+
)
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const isFrameworkAnalysisRequest = (id: string) =>
|
|
841
|
+
id.includes('__react-router-build-client-route')
|
|
842
|
+
|
|
234
843
|
function isNotClient(environment?: Environment) {
|
|
235
844
|
return environment?.name && environment.name !== 'client'
|
|
236
845
|
}
|
|
@@ -241,18 +850,21 @@ export function tamaguiPlugin({
|
|
|
241
850
|
)
|
|
242
851
|
}
|
|
243
852
|
|
|
244
|
-
function
|
|
245
|
-
if (
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
853
|
+
function invalidateCompilerModules() {
|
|
854
|
+
if (server) {
|
|
855
|
+
const ids = new Set([...transformedModuleIds, ...cssMap.keys()])
|
|
856
|
+
for (const environment of Object.values(server.environments)) {
|
|
857
|
+
if (environment.name === TAMAGUI_EVALUATION_ENVIRONMENT) continue
|
|
858
|
+
for (const id of ids) {
|
|
859
|
+
const modules = environment.moduleGraph.getModulesByFile(id)
|
|
860
|
+
if (!modules) continue
|
|
861
|
+
for (const module of modules) {
|
|
862
|
+
environment.moduleGraph.invalidateModule(module)
|
|
863
|
+
}
|
|
864
|
+
}
|
|
254
865
|
}
|
|
255
866
|
}
|
|
867
|
+
cssMap.clear()
|
|
256
868
|
}
|
|
257
869
|
|
|
258
870
|
const basePlugin: Plugin = {
|
|
@@ -261,15 +873,20 @@ export function tamaguiPlugin({
|
|
|
261
873
|
|
|
262
874
|
configureServer(_server) {
|
|
263
875
|
server = _server
|
|
876
|
+
const evaluationEnvironment = server.environments[TAMAGUI_EVALUATION_ENVIRONMENT]
|
|
877
|
+
if (!isRunnableDevEnvironment(evaluationEnvironment)) {
|
|
878
|
+
throw new Error(
|
|
879
|
+
`The ${TAMAGUI_EVALUATION_ENVIRONMENT} Vite environment must support ModuleRunner evaluation`
|
|
880
|
+
)
|
|
881
|
+
}
|
|
882
|
+
tamaguiLoader.setEnvironment(evaluationEnvironment)
|
|
264
883
|
},
|
|
265
884
|
|
|
266
885
|
async buildEnd() {
|
|
267
|
-
await
|
|
268
|
-
res?.dispose()
|
|
269
|
-
})
|
|
886
|
+
await releaseBuildEnvironment(this.environment)
|
|
270
887
|
},
|
|
271
888
|
|
|
272
|
-
async config(
|
|
889
|
+
async config(userConfig, env) {
|
|
273
890
|
const options = await ensureLoaded()
|
|
274
891
|
|
|
275
892
|
if (!options) {
|
|
@@ -278,17 +895,29 @@ export function tamaguiPlugin({
|
|
|
278
895
|
const useReactNativeWebLite =
|
|
279
896
|
tamaguiOptionsIn.useReactNativeWebLite ?? options.useReactNativeWebLite
|
|
280
897
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
...options,
|
|
287
|
-
}).catch((err) => {
|
|
288
|
-
console.error(` [Tamagui] Error watching config: ${err}`)
|
|
289
|
-
})
|
|
898
|
+
for (const source of [options.config, ...(options.components || [])]) {
|
|
899
|
+
const packageName = getEvaluationPackageName(source)
|
|
900
|
+
if (packageName) {
|
|
901
|
+
configuredEvaluationPackages.add(packageName)
|
|
902
|
+
}
|
|
290
903
|
}
|
|
291
904
|
|
|
905
|
+
const resolvedRoot = userConfig.root ? path.resolve(userConfig.root) : process.cwd()
|
|
906
|
+
|
|
907
|
+
// An island child build is the full-runtime half of the same project, so it
|
|
908
|
+
// never re-enters zero mode even though it reads the same tamagui.build.ts.
|
|
909
|
+
zero = zeroIslandBuild
|
|
910
|
+
? null
|
|
911
|
+
: await createZeroRuntimeController(options, resolvedRoot, userConfig.base || '/')
|
|
912
|
+
|
|
913
|
+
// The island child build's artifact is the parent's, and a dev server has
|
|
914
|
+
// no final graph to prove the relationship against, so both keep runtime
|
|
915
|
+
// CSS generation. Production is where the claim is made and gated.
|
|
916
|
+
globalCSS =
|
|
917
|
+
zeroIslandBuild || env.command !== 'build'
|
|
918
|
+
? null
|
|
919
|
+
: Static.resolveGlobalCSSOwnership(options, resolvedRoot)
|
|
920
|
+
|
|
292
921
|
return {
|
|
293
922
|
envPrefix: ['TAMAGUI_'],
|
|
294
923
|
|
|
@@ -297,11 +926,26 @@ export function tamaguiPlugin({
|
|
|
297
926
|
define: {
|
|
298
927
|
'process.env.TAMAGUI_IS_CLIENT': JSON.stringify(true),
|
|
299
928
|
'process.env.TAMAGUI_ENVIRONMENT': '"client"',
|
|
929
|
+
// An enforced zero client and its SSR peer both receive 'zero', so
|
|
930
|
+
// SSR never imports a runtime hydration removed.
|
|
931
|
+
...(zero?.isEnforcing && {
|
|
932
|
+
'process.env.TAMAGUI_RUNTIME': JSON.stringify('zero'),
|
|
933
|
+
}),
|
|
934
|
+
// Derived, never author-set. generateBundle proves the artifact
|
|
935
|
+
// exists, matches this build's config, and is in the client graph;
|
|
936
|
+
// a build that cannot prove it fails instead of shipping.
|
|
937
|
+
...(globalCSS && {
|
|
938
|
+
'process.env.TAMAGUI_DID_OUTPUT_CSS': JSON.stringify('1'),
|
|
939
|
+
}),
|
|
300
940
|
},
|
|
301
941
|
},
|
|
942
|
+
[TAMAGUI_EVALUATION_ENVIRONMENT]: getEvaluationEnvironmentOptions(),
|
|
302
943
|
},
|
|
303
944
|
|
|
304
945
|
define: {
|
|
946
|
+
// Config evaluation, report builds, native builds, and full-runtime
|
|
947
|
+
// island child builds all keep ordinary Tamagui runtime behavior.
|
|
948
|
+
'process.env.TAMAGUI_RUNTIME': JSON.stringify('full'),
|
|
305
949
|
// reanimated support
|
|
306
950
|
_frameTimestamp: undefined,
|
|
307
951
|
_WORKLET: false,
|
|
@@ -338,8 +982,8 @@ export function tamaguiPlugin({
|
|
|
338
982
|
|
|
339
983
|
const rnwLitePlugin: Plugin = {
|
|
340
984
|
name: 'tamagui-rnw-lite',
|
|
341
|
-
// framework plugins
|
|
342
|
-
// normal config hook
|
|
985
|
+
// framework plugins add their default react-native-web aliases from a
|
|
986
|
+
// normal config hook, so apply the explicit lite choice after them.
|
|
343
987
|
enforce: 'post',
|
|
344
988
|
|
|
345
989
|
config() {
|
|
@@ -347,16 +991,16 @@ export function tamaguiPlugin({
|
|
|
347
991
|
return {}
|
|
348
992
|
}
|
|
349
993
|
|
|
350
|
-
const options = getTamaguiOptions()
|
|
994
|
+
const options = tamaguiLoader.getTamaguiOptions()
|
|
351
995
|
const useReactNativeWebLite =
|
|
352
996
|
tamaguiOptionsIn.useReactNativeWebLite ?? options?.useReactNativeWebLite
|
|
353
997
|
if (!useReactNativeWebLite) {
|
|
354
998
|
return {}
|
|
355
999
|
}
|
|
356
1000
|
|
|
357
|
-
// the dep scanner
|
|
358
|
-
// react-native
|
|
359
|
-
// would otherwise reach the browser raw or trigger a mid-load
|
|
1001
|
+
// the dep scanner does not follow transitive packages through the
|
|
1002
|
+
// react-native to rnw-lite alias. pre-include the CJS dependencies that
|
|
1003
|
+
// would otherwise reach the browser raw or trigger a mid-load optimize.
|
|
360
1004
|
const include: string[] = []
|
|
361
1005
|
for (const dependency of ['memoize-one', '@react-native/normalize-color']) {
|
|
362
1006
|
if (isInstalled(process.cwd(), dependency)) include.push(dependency)
|
|
@@ -366,6 +1010,13 @@ export function tamaguiPlugin({
|
|
|
366
1010
|
resolve: {
|
|
367
1011
|
alias: tamaguiAliases({ rnwLite: useReactNativeWebLite }),
|
|
368
1012
|
},
|
|
1013
|
+
ssr: {
|
|
1014
|
+
// Installed packages are externalized by default in SSR builds, which
|
|
1015
|
+
// bypasses the RNW-lite alias and executes React Native Web's CJS entry
|
|
1016
|
+
// directly in Node. Bundle the Tamagui/RN boundary just as Vite does
|
|
1017
|
+
// for linked workspace packages.
|
|
1018
|
+
noExternal: [/^@tamagui\//, 'tamagui', 'react-native', 'react-native-web'],
|
|
1019
|
+
},
|
|
369
1020
|
optimizeDeps: {
|
|
370
1021
|
// upstream react-native-web must not be pre-bundled when aliased to lite
|
|
371
1022
|
exclude: ['react-native-web'],
|
|
@@ -388,14 +1039,16 @@ export function tamaguiPlugin({
|
|
|
388
1039
|
userConf.optimizeDeps ||= {}
|
|
389
1040
|
userConf.optimizeDeps.include ||= []
|
|
390
1041
|
|
|
391
|
-
//
|
|
392
|
-
// (
|
|
1042
|
+
// These dependencies are CJS and break when served directly to the browser
|
|
1043
|
+
// (`exports`/`module` is not defined). Pre-bundle them before Tamagui's linked
|
|
1044
|
+
// package graph can expose them as late-discovered transitive dependencies.
|
|
393
1045
|
userConf.optimizeDeps.include.push('inline-style-prefixer')
|
|
1046
|
+
addIfInstalled(userConf, userConf.root, ['@react-native/normalize-color'])
|
|
394
1047
|
|
|
395
|
-
// pre-bundle tamagui packages that use internal
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
//
|
|
1048
|
+
// pre-bundle core and web alongside tamagui packages that use internal
|
|
1049
|
+
// contexts and hooks. if either remains linked while these entries are
|
|
1050
|
+
// optimized, Provider imports and optimized consumers can receive
|
|
1051
|
+
// separate theme/component/config contexts even with resolve.dedupe.
|
|
399
1052
|
//
|
|
400
1053
|
// @tamagui/sheet/controller is the lightweight controller subpath imported
|
|
401
1054
|
// by popover/dialog/select; the app imports @tamagui/sheet (full). if these
|
|
@@ -404,8 +1057,9 @@ export function tamaguiPlugin({
|
|
|
404
1057
|
// and the Sheet consumer (from the full entry) never match and adapted
|
|
405
1058
|
// sheets silently never open. include both so they share one context chunk.
|
|
406
1059
|
addIfInstalled(userConf, userConf.root, [
|
|
1060
|
+
'@tamagui/core',
|
|
1061
|
+
'@tamagui/web',
|
|
407
1062
|
'@tamagui/toast',
|
|
408
|
-
'@tamagui/toast/v2',
|
|
409
1063
|
'@tamagui/sheet',
|
|
410
1064
|
'@tamagui/sheet/controller',
|
|
411
1065
|
])
|
|
@@ -439,9 +1093,155 @@ export function tamaguiPlugin({
|
|
|
439
1093
|
config = resolvedConfig
|
|
440
1094
|
},
|
|
441
1095
|
|
|
442
|
-
async
|
|
443
|
-
|
|
1096
|
+
async buildStart() {
|
|
1097
|
+
const buildConfig = this.environment.getTopLevelConfig()
|
|
1098
|
+
if (buildConfig.command !== 'build') return
|
|
1099
|
+
|
|
1100
|
+
const pendingCleanup = buildCleanupPromise
|
|
1101
|
+
if (pendingCleanup) {
|
|
1102
|
+
await pendingCleanup
|
|
1103
|
+
}
|
|
444
1104
|
|
|
1105
|
+
const buildEnvironment = this.environment
|
|
1106
|
+
activeBuildEnvironments.add(buildEnvironment)
|
|
1107
|
+
try {
|
|
1108
|
+
if (!tamaguiLoader.getEnvironment()) {
|
|
1109
|
+
await tamaguiLoader.loadTamaguiBuildConfig()
|
|
1110
|
+
buildEnvironmentPromise ||= (async () => {
|
|
1111
|
+
const evaluationConfig = await createOwnedEvaluationConfig(
|
|
1112
|
+
buildConfig,
|
|
1113
|
+
configuredEvaluationPackages
|
|
1114
|
+
)
|
|
1115
|
+
const evaluationEnvironment = createRunnableDevEnvironment(
|
|
1116
|
+
TAMAGUI_EVALUATION_ENVIRONMENT,
|
|
1117
|
+
evaluationConfig,
|
|
1118
|
+
{ hot: false }
|
|
1119
|
+
)
|
|
1120
|
+
try {
|
|
1121
|
+
await evaluationEnvironment.init()
|
|
1122
|
+
} catch (error) {
|
|
1123
|
+
await evaluationEnvironment.close().catch(() => undefined)
|
|
1124
|
+
throw error
|
|
1125
|
+
}
|
|
1126
|
+
tamaguiLoader.setEnvironment(evaluationEnvironment, { owned: true })
|
|
1127
|
+
})()
|
|
1128
|
+
await buildEnvironmentPromise
|
|
1129
|
+
}
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
await releaseBuildEnvironment(buildEnvironment)
|
|
1132
|
+
throw error
|
|
1133
|
+
}
|
|
1134
|
+
},
|
|
1135
|
+
|
|
1136
|
+
hotUpdate: {
|
|
1137
|
+
order: 'post',
|
|
1138
|
+
async handler(options) {
|
|
1139
|
+
if (!tamaguiLoader.isEvaluationDependency(options.file)) {
|
|
1140
|
+
if (this.environment.name !== 'client') return
|
|
1141
|
+
const source = options.type === 'delete' ? null : await options.read()
|
|
1142
|
+
const affectedModules = new Set<EnvironmentModuleNode>()
|
|
1143
|
+
const compilerHmrRoots = new Set<string>(
|
|
1144
|
+
compilerFrontend.dependentsOf(options.file)
|
|
1145
|
+
)
|
|
1146
|
+
if (compilerHmrRoots.size || compilerFrontend.has(options.file)) {
|
|
1147
|
+
compilerHmrRoots.add(options.file)
|
|
1148
|
+
}
|
|
1149
|
+
if (compilerFrontend.has(options.file) || compilerHmrRoots.size > 0) {
|
|
1150
|
+
const loadedOptions = await ensureLoaded()
|
|
1151
|
+
if (!loadedOptions?.disable) {
|
|
1152
|
+
const invalidatedIds =
|
|
1153
|
+
options.type === 'delete'
|
|
1154
|
+
? (await compilerFrontend.remove(options.file)).invalidatedIds
|
|
1155
|
+
: await compilerFrontend.update({
|
|
1156
|
+
id: options.file,
|
|
1157
|
+
source: source!,
|
|
1158
|
+
root: config.root,
|
|
1159
|
+
project: {
|
|
1160
|
+
...(await tamaguiLoader.getCompilerProject()),
|
|
1161
|
+
generation: `${pluginInstanceId}:${tamaguiLoader.getGeneration()}`,
|
|
1162
|
+
},
|
|
1163
|
+
resolve: async (specifier, importer) => {
|
|
1164
|
+
const resolution =
|
|
1165
|
+
await this.environment.pluginContainer.resolveId(
|
|
1166
|
+
specifier,
|
|
1167
|
+
importer
|
|
1168
|
+
)
|
|
1169
|
+
return resolution
|
|
1170
|
+
? { id: resolution.id, external: resolution.external === true }
|
|
1171
|
+
: null
|
|
1172
|
+
},
|
|
1173
|
+
load: async (dependencyId) => {
|
|
1174
|
+
const cleanDependencyId = dependencyId.split(/[?#]/, 1)[0]
|
|
1175
|
+
if (!path.isAbsolute(cleanDependencyId)) return null
|
|
1176
|
+
try {
|
|
1177
|
+
return await readFile(cleanDependencyId, 'utf8')
|
|
1178
|
+
} catch {
|
|
1179
|
+
return null
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
})
|
|
1183
|
+
for (const invalidatedId of invalidatedIds) {
|
|
1184
|
+
for (const module of this.environment.moduleGraph.getModulesByFile(
|
|
1185
|
+
invalidatedId
|
|
1186
|
+
) ?? []) {
|
|
1187
|
+
this.environment.moduleGraph.invalidateModule(module)
|
|
1188
|
+
if (compilerHmrRoots.has(invalidatedId) || module.isSelfAccepting) {
|
|
1189
|
+
affectedModules.add(module)
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
const cssId = getAbsoluteVirtualFileId(`${invalidatedId}${virtualExt}`)
|
|
1193
|
+
const cssModule = this.environment.moduleGraph.getModuleById(cssId)
|
|
1194
|
+
if (cssModule) {
|
|
1195
|
+
this.environment.moduleGraph.invalidateModule(cssModule)
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
return affectedModules.size ? [...affectedModules] : undefined
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
const signature = await (async () => {
|
|
1204
|
+
if (options.type === 'delete') {
|
|
1205
|
+
return getHash(`${options.type}:${options.file}`)
|
|
1206
|
+
}
|
|
1207
|
+
try {
|
|
1208
|
+
return getHash(`${options.type}:${options.file}:${await options.read()}`)
|
|
1209
|
+
} catch {
|
|
1210
|
+
return getHash(`${options.type}:${options.file}:${options.timestamp}`)
|
|
1211
|
+
}
|
|
1212
|
+
})()
|
|
1213
|
+
|
|
1214
|
+
if (compilerHotUpdateSignatures.get(options.file) !== signature) {
|
|
1215
|
+
compilerHotUpdateSignatures.set(options.file, signature)
|
|
1216
|
+
tamaguiLoader.invalidate(options.file)
|
|
1217
|
+
invalidateCompilerModules()
|
|
1218
|
+
}
|
|
1219
|
+
if (
|
|
1220
|
+
this.environment.name === 'client' &&
|
|
1221
|
+
compilerHotReloadSignatures.get(options.file) !== signature
|
|
1222
|
+
) {
|
|
1223
|
+
compilerHotReloadSignatures.set(options.file, signature)
|
|
1224
|
+
this.environment.hot.send({
|
|
1225
|
+
type: 'full-reload',
|
|
1226
|
+
path: '*',
|
|
1227
|
+
triggeredBy: options.file,
|
|
1228
|
+
})
|
|
1229
|
+
}
|
|
1230
|
+
return []
|
|
1231
|
+
},
|
|
1232
|
+
},
|
|
1233
|
+
|
|
1234
|
+
async watchChange(id) {
|
|
1235
|
+
if (config.command !== 'build') {
|
|
1236
|
+
return
|
|
1237
|
+
}
|
|
1238
|
+
if (tamaguiLoader.isEvaluationDependency(id)) {
|
|
1239
|
+
tamaguiLoader.invalidate(id)
|
|
1240
|
+
invalidateCompilerModules()
|
|
1241
|
+
}
|
|
1242
|
+
},
|
|
1243
|
+
|
|
1244
|
+
async resolveId(source) {
|
|
445
1245
|
if (isNative(this.environment)) {
|
|
446
1246
|
return
|
|
447
1247
|
}
|
|
@@ -450,14 +1250,16 @@ export function tamaguiPlugin({
|
|
|
450
1250
|
return
|
|
451
1251
|
}
|
|
452
1252
|
|
|
1253
|
+
if (!shouldExtract) return
|
|
1254
|
+
|
|
453
1255
|
const [validId, query] = source.split('?')
|
|
454
1256
|
|
|
455
1257
|
if (!validId.endsWith(virtualExt)) {
|
|
456
1258
|
return
|
|
457
1259
|
}
|
|
458
1260
|
|
|
459
|
-
const absoluteId =
|
|
460
|
-
?
|
|
1261
|
+
const absoluteId = validId.startsWith(config.root)
|
|
1262
|
+
? validId
|
|
461
1263
|
: getAbsoluteVirtualFileId(validId)
|
|
462
1264
|
|
|
463
1265
|
if (cssMap.has(absoluteId)) {
|
|
@@ -466,9 +1268,7 @@ export function tamaguiPlugin({
|
|
|
466
1268
|
},
|
|
467
1269
|
|
|
468
1270
|
async load(id) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const options = getTamaguiOptions()
|
|
1271
|
+
const options = tamaguiLoader.getTamaguiOptions()
|
|
472
1272
|
if (options?.disable) {
|
|
473
1273
|
return
|
|
474
1274
|
}
|
|
@@ -481,183 +1281,499 @@ export function tamaguiPlugin({
|
|
|
481
1281
|
return
|
|
482
1282
|
}
|
|
483
1283
|
|
|
1284
|
+
if (!shouldExtract) return
|
|
1285
|
+
|
|
484
1286
|
const [validId] = id.split('?')
|
|
485
1287
|
return cssMap.get(validId)
|
|
486
1288
|
},
|
|
1289
|
+
}
|
|
487
1290
|
|
|
1291
|
+
// Source and compiled JSX reach this filtered post-transform after user syntax
|
|
1292
|
+
// plugins and before Vite import analysis.
|
|
1293
|
+
const sharedCompilerPlugin: Plugin = {
|
|
1294
|
+
name: 'tamagui-compiler',
|
|
1295
|
+
enforce: 'post',
|
|
488
1296
|
transform: {
|
|
489
1297
|
order: 'pre',
|
|
490
1298
|
async handler(code, id) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
// ensure full config (heavy bundling) is loaded before extraction
|
|
495
|
-
await ensureFullConfigLoaded()
|
|
496
|
-
|
|
497
|
-
// fully disabled = no extraction AND no debug attrs
|
|
498
|
-
if (options?.disable) {
|
|
499
|
-
return
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
if (isNative(this.environment)) {
|
|
503
|
-
return
|
|
504
|
-
}
|
|
1299
|
+
if (this.environment?.name === TAMAGUI_EVALUATION_ENVIRONMENT) return
|
|
1300
|
+
if (!tamaguiLoader.getEnvironment()) return
|
|
1301
|
+
if (isNative(this.environment)) return
|
|
505
1302
|
|
|
506
1303
|
const [validId] = id.split('?')
|
|
507
|
-
if (
|
|
1304
|
+
if (
|
|
1305
|
+
isFrameworkAnalysisRequest(id) ||
|
|
1306
|
+
!isAppJSXSource(validId) ||
|
|
1307
|
+
!/\.[jt]sx$/.test(validId)
|
|
1308
|
+
)
|
|
508
1309
|
return
|
|
509
|
-
|
|
1310
|
+
const options = await ensureLoaded()
|
|
1311
|
+
if (options?.disable || !shouldExtract) return
|
|
510
1312
|
|
|
511
|
-
const { shouldDisable
|
|
1313
|
+
const { shouldDisable } = await Static.getPragmaOptions({
|
|
512
1314
|
source: code,
|
|
513
1315
|
path: validId,
|
|
514
1316
|
})
|
|
1317
|
+
if (shouldDisable) return
|
|
515
1318
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
if (process.env.DEBUG_TAMAGUI_CACHE) {
|
|
555
|
-
console.info(
|
|
556
|
-
`[tamagui-cache] WAIT ${this.environment?.name || 'unknown'} ${id.split('/').pop()} key=${cacheKey.slice(0, 8)}`
|
|
557
|
-
)
|
|
558
|
-
}
|
|
559
|
-
const result = await pendingExtraction
|
|
560
|
-
if (result) {
|
|
561
|
-
return formatResult(result)
|
|
562
|
-
}
|
|
563
|
-
return
|
|
1319
|
+
const evaluationDependencies = await tamaguiLoader.ensureFullConfigLoaded()
|
|
1320
|
+
for (const dependency of evaluationDependencies) this.addWatchFile(dependency)
|
|
1321
|
+
const compilerProject = await tamaguiLoader.getCompilerProject()
|
|
1322
|
+
const result = await compilerFrontend.compile({
|
|
1323
|
+
id: validId,
|
|
1324
|
+
source: code,
|
|
1325
|
+
root: config.root,
|
|
1326
|
+
target: 'web',
|
|
1327
|
+
project: {
|
|
1328
|
+
...compilerProject,
|
|
1329
|
+
generation: `${pluginInstanceId}:${tamaguiLoader.getGeneration()}`,
|
|
1330
|
+
// `report` runs the same analysis as `enforce`, including the
|
|
1331
|
+
// mode-aware diagnostics, so both emit the identical violation list
|
|
1332
|
+
zeroRuntime: zero !== null,
|
|
1333
|
+
},
|
|
1334
|
+
resolve: async (specifier, importer) => {
|
|
1335
|
+
const resolution = await this.resolve(specifier, importer, { skipSelf: true })
|
|
1336
|
+
return resolution
|
|
1337
|
+
? { id: resolution.id, external: resolution.external === true }
|
|
1338
|
+
: null
|
|
1339
|
+
},
|
|
1340
|
+
load: async (dependencyId) => {
|
|
1341
|
+
const cleanDependencyId = dependencyId.split(/[?#]/, 1)[0]
|
|
1342
|
+
if (!path.isAbsolute(cleanDependencyId)) return null
|
|
1343
|
+
try {
|
|
1344
|
+
return await readFile(cleanDependencyId, 'utf8')
|
|
1345
|
+
} catch {
|
|
1346
|
+
return null
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
})
|
|
1350
|
+
transformedModuleIds.add(validId)
|
|
1351
|
+
compilerReports?.set(validId, {
|
|
1352
|
+
stats: result.plan.stats,
|
|
1353
|
+
diagnostics: result.plan.diagnostics,
|
|
1354
|
+
})
|
|
1355
|
+
for (const dependency of result.plan.dependencies) {
|
|
1356
|
+
if (path.isAbsolute(dependency)) this.addWatchFile(dependency)
|
|
564
1357
|
}
|
|
565
1358
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
1359
|
+
// Island child build: the parent owns the one CSS artifact, so route this
|
|
1360
|
+
// module's atomic rules there and inject nothing.
|
|
1361
|
+
if (zeroIslandBuild) {
|
|
1362
|
+
zeroIslandBuild.artifact.setIslandModuleCSS(
|
|
1363
|
+
zeroIslandBuild.islandId,
|
|
1364
|
+
validId,
|
|
1365
|
+
wrapExtractedCSS(result.plan.css)
|
|
569
1366
|
)
|
|
1367
|
+
return result.output.changed
|
|
1368
|
+
? { code: result.output.code, map: result.output.map as any }
|
|
1369
|
+
: undefined
|
|
570
1370
|
}
|
|
571
1371
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
`[tamagui-cache] ERROR extracting ${id.split('/').pop()}:`,
|
|
586
|
-
err
|
|
1372
|
+
if (zero) {
|
|
1373
|
+
const zeroResult = Static.transformZeroModule({
|
|
1374
|
+
mode: zero.isEnforcing ? 'enforce' : 'report',
|
|
1375
|
+
id: validId,
|
|
1376
|
+
root: config.root,
|
|
1377
|
+
source: code,
|
|
1378
|
+
plan: result.plan,
|
|
1379
|
+
config: (await tamaguiLoader.getTamaguiConfig())!,
|
|
1380
|
+
isTamaguiSpecifier: (specifier) =>
|
|
1381
|
+
specifier === 'tamagui' || specifier.startsWith('@tamagui/'),
|
|
1382
|
+
resolveIslandLoader: (specifier) => {
|
|
1383
|
+
const islandId = zero!.loaderIds.get(
|
|
1384
|
+
zeroModuleKey(path.resolve(path.dirname(validId), specifier))
|
|
587
1385
|
)
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
1386
|
+
return islandId ? { islandId } : null
|
|
1387
|
+
},
|
|
1388
|
+
resolveIslandModule: (specifier) =>
|
|
1389
|
+
zero!.islandModuleIds.get(
|
|
1390
|
+
zeroModuleKey(path.resolve(path.dirname(validId), specifier))
|
|
1391
|
+
) ?? null,
|
|
1392
|
+
})
|
|
1393
|
+
zero.transformed.add(validId)
|
|
1394
|
+
if (zeroResult.erased.exports.length) {
|
|
1395
|
+
zero.erasedExports.set(validId, zeroResult.erased.exports)
|
|
1396
|
+
}
|
|
1397
|
+
for (const violation of zeroResult.violations) {
|
|
1398
|
+
const { line, column } = Static.offsetToLineColumn(code, violation.span.start)
|
|
1399
|
+
zero.violations.push({
|
|
1400
|
+
file: path.relative(config.root, validId),
|
|
1401
|
+
line,
|
|
1402
|
+
column,
|
|
1403
|
+
rule: violation.rule,
|
|
1404
|
+
code: violation.code,
|
|
1405
|
+
component: violation.component,
|
|
1406
|
+
message: violation.message,
|
|
1407
|
+
})
|
|
591
1408
|
}
|
|
1409
|
+
// `report` runs the same analysis and then leaves everything else
|
|
1410
|
+
// alone: full runtime, ordinary CSS handling, unchanged source. So it
|
|
1411
|
+
// falls through to the ordinary path below.
|
|
1412
|
+
if (zero.isEnforcing) {
|
|
1413
|
+
Static.mergeIslandBridges(zero.bridges, zeroResult.bridges)
|
|
1414
|
+
const moduleCSS = [
|
|
1415
|
+
wrapExtractedCSS(result.plan.css),
|
|
1416
|
+
...[...zeroResult.bridgeCSS.values()],
|
|
1417
|
+
]
|
|
1418
|
+
.filter(Boolean)
|
|
1419
|
+
.join('\n')
|
|
1420
|
+
|
|
1421
|
+
// Production combines every module's rules into the one artifact the
|
|
1422
|
+
// entry loads. Development keeps them on Vite's per-module CSS
|
|
1423
|
+
// modules, where the importer owns the ordering and hot replacement
|
|
1424
|
+
// already works.
|
|
1425
|
+
if (config.command !== 'build') {
|
|
1426
|
+
let cssImport = ''
|
|
1427
|
+
if (moduleCSS) {
|
|
1428
|
+
const rootRelativeId = `${validId}${virtualExt}`
|
|
1429
|
+
cssMap.set(getAbsoluteVirtualFileId(rootRelativeId), moduleCSS)
|
|
1430
|
+
this.addWatchFile(rootRelativeId)
|
|
1431
|
+
cssImport = `\nimport "${rootRelativeId}";`
|
|
1432
|
+
}
|
|
1433
|
+
return {
|
|
1434
|
+
code: `${zeroResult.output.code}${cssImport}`,
|
|
1435
|
+
map: zeroResult.output.map as any,
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
592
1438
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
console.info(
|
|
596
|
-
`[tamagui-cache] no extraction result for ${id.split('/').pop()}`
|
|
597
|
-
)
|
|
1439
|
+
for (const [identifier, rules] of zeroResult.bridgeCSS) {
|
|
1440
|
+
zero.artifact.setBridgeRules(identifier, rules)
|
|
598
1441
|
}
|
|
599
|
-
|
|
1442
|
+
zero.artifact.setZeroModuleCSS(validId, wrapExtractedCSS(result.plan.css))
|
|
1443
|
+
return zeroResult.output.changed
|
|
1444
|
+
? { code: zeroResult.output.code, map: zeroResult.output.map as any }
|
|
1445
|
+
: undefined
|
|
600
1446
|
}
|
|
1447
|
+
}
|
|
601
1448
|
|
|
1449
|
+
const isSSR = isNotClient(this.environment)
|
|
1450
|
+
let cssImport: string | null = null
|
|
1451
|
+
if (result.plan.css) {
|
|
602
1452
|
const rootRelativeId = `${validId}${virtualExt}`
|
|
603
1453
|
const absoluteId = getAbsoluteVirtualFileId(rootRelativeId)
|
|
1454
|
+
cssMap.set(absoluteId, wrapExtractedCSS(result.plan.css))
|
|
1455
|
+
this.addWatchFile(rootRelativeId)
|
|
1456
|
+
if (!isSSR) cssImport = `import "${rootRelativeId}";`
|
|
1457
|
+
}
|
|
1458
|
+
const finalCode = cssImport
|
|
1459
|
+
? `${result.output.code}\n${cssImport}`
|
|
1460
|
+
: result.output.code
|
|
1461
|
+
return result.output.changed || cssImport
|
|
1462
|
+
? { code: finalCode, map: result.output.map as any }
|
|
1463
|
+
: undefined
|
|
1464
|
+
},
|
|
1465
|
+
},
|
|
1466
|
+
}
|
|
604
1467
|
|
|
605
|
-
|
|
1468
|
+
// Owns the single CSS artifact, the island child builds, and the module-graph
|
|
1469
|
+
// gate that is the only thing that actually proves the zero guarantee.
|
|
1470
|
+
//
|
|
1471
|
+
// Development runs the same lowering and reference erasure, so the runtime
|
|
1472
|
+
// that generates design-system, :root, font and theme CSS is gone there too.
|
|
1473
|
+
// The dev server therefore has to serve that CSS itself: it publishes the
|
|
1474
|
+
// config half at the same href production uses and builds the islands once at
|
|
1475
|
+
// startup. Per-module atomic rules keep Vite's own `.tamagui.css` modules in
|
|
1476
|
+
// dev, which is where hot replacement already works; production combines them
|
|
1477
|
+
// into the one artifact instead.
|
|
1478
|
+
const zeroRuntimePlugin: Plugin = {
|
|
1479
|
+
name: 'tamagui-zero-runtime',
|
|
1480
|
+
enforce: 'post',
|
|
606
1481
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
1482
|
+
async buildStart() {
|
|
1483
|
+
if (!zero || this.environment.name !== 'client') return
|
|
1484
|
+
await tamaguiLoader.ensureFullConfigLoaded()
|
|
1485
|
+
const tamaguiConfig = await tamaguiLoader.getTamaguiConfig()
|
|
1486
|
+
if (!tamaguiConfig) {
|
|
1487
|
+
throw new Error(
|
|
1488
|
+
`[tamagui zero-runtime] the Tamagui config did not evaluate, so no CSS artifact can be generated`
|
|
1489
|
+
)
|
|
1490
|
+
}
|
|
1491
|
+
zero.violations.length = 0
|
|
1492
|
+
zero.transformed.clear()
|
|
1493
|
+
zero.erasedExports.clear()
|
|
1494
|
+
if (!zero.isEnforcing) return
|
|
1495
|
+
Static.assertZeroConfigDrivers(tamaguiConfig)
|
|
1496
|
+
zero.artifact.clearGraphs()
|
|
1497
|
+
zero.bridges.clear()
|
|
1498
|
+
zeroHtmlEntries = 0
|
|
1499
|
+
zero.artifact.setConfigCSS(tamaguiConfig.getCSS())
|
|
1500
|
+
|
|
1501
|
+
// Production builds the islands at the end, once the zero graph is known.
|
|
1502
|
+
// Development has no such end, so they are built here, after the reset
|
|
1503
|
+
// that would otherwise discard their rules, and the dev server's artifact
|
|
1504
|
+
// route waits on this.
|
|
1505
|
+
if (config.command !== 'build') {
|
|
1506
|
+
const islands = zero
|
|
1507
|
+
zeroDevIslands = Promise.all(
|
|
1508
|
+
islands.resolved.islands.map((island) =>
|
|
1509
|
+
buildIsland({
|
|
1510
|
+
island,
|
|
1511
|
+
controller: islands,
|
|
1512
|
+
root: config.root,
|
|
1513
|
+
outDir: zeroDevIslandDir(islands),
|
|
1514
|
+
mode: 'development',
|
|
1515
|
+
})
|
|
1516
|
+
)
|
|
1517
|
+
)
|
|
1518
|
+
await zeroDevIslands
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
610
1521
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
1522
|
+
async configureServer(devServer) {
|
|
1523
|
+
if (!zero?.isEnforcing) return
|
|
1524
|
+
const islandBase = `${zero.cssHref.replace(ZERO_CSS_FILENAME, '')}${ZERO_ISLAND_DIRNAME}/`
|
|
1525
|
+
devServer.middlewares.use(async (request, response, next) => {
|
|
1526
|
+
const url = (request.url || '').split('?')[0]
|
|
1527
|
+
if (url !== zero!.cssHref && !url.startsWith(islandBase)) return next()
|
|
1528
|
+
// buildStart owns the artifact's contents and the island builds, so a
|
|
1529
|
+
// request that arrives first waits for it rather than reading a
|
|
1530
|
+
// half-populated artifact
|
|
1531
|
+
await zeroDevIslands
|
|
1532
|
+
if (url === zero!.cssHref) {
|
|
1533
|
+
response.setHeader('content-type', 'text/css; charset=utf-8')
|
|
1534
|
+
response.setHeader('cache-control', 'no-cache')
|
|
1535
|
+
response.end(zero!.artifact.css())
|
|
1536
|
+
return
|
|
1537
|
+
}
|
|
1538
|
+
const islandId = url.slice(islandBase.length).replace(/\.js$/, '')
|
|
1539
|
+
const file = path.join(
|
|
1540
|
+
zeroDevIslandDir(zero!),
|
|
1541
|
+
ZERO_ISLAND_DIRNAME,
|
|
1542
|
+
`${islandId}.js`
|
|
1543
|
+
)
|
|
1544
|
+
if (!existsSync(file)) return next()
|
|
1545
|
+
response.setHeader('content-type', 'text/javascript; charset=utf-8')
|
|
1546
|
+
response.setHeader('cache-control', 'no-cache')
|
|
1547
|
+
response.end(readFileSync(file))
|
|
1548
|
+
})
|
|
1549
|
+
},
|
|
614
1550
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
1551
|
+
// The last hook that still sees the resolved graph and runs before rolldown
|
|
1552
|
+
// renders chunks. An erased export that some module still imports has to be
|
|
1553
|
+
// reported here: by render time the bundler has already failed on it with a
|
|
1554
|
+
// message about a missing export, which says nothing about why it is missing.
|
|
1555
|
+
buildEnd(error) {
|
|
1556
|
+
if (!zero || this.environment.name !== 'client') return
|
|
1557
|
+
if (error) {
|
|
1558
|
+
zeroBuildFailed = true
|
|
1559
|
+
return
|
|
1560
|
+
}
|
|
1561
|
+
if (!zero.isEnforcing) return
|
|
1562
|
+
const importers = new Map<string, readonly string[]>()
|
|
1563
|
+
for (const moduleId of this.getModuleIds()) {
|
|
1564
|
+
importers.set(moduleId, this.getModuleInfo(moduleId)?.importers ?? [])
|
|
1565
|
+
}
|
|
1566
|
+
const escape = Static.erasedExportEscape({
|
|
1567
|
+
integration: 'vite',
|
|
1568
|
+
transformed: zero.transformed,
|
|
1569
|
+
erasedExports: zero.erasedExports,
|
|
1570
|
+
importersOf: importers,
|
|
1571
|
+
})
|
|
1572
|
+
if (escape) {
|
|
1573
|
+
zeroBuildFailed = true
|
|
1574
|
+
throw new Error(escape)
|
|
1575
|
+
}
|
|
1576
|
+
},
|
|
618
1577
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
1578
|
+
transformIndexHtml: {
|
|
1579
|
+
order: 'post',
|
|
1580
|
+
handler(html) {
|
|
1581
|
+
if (!zero?.isEnforcing) return
|
|
1582
|
+
zeroHtmlEntries++
|
|
1583
|
+
return {
|
|
1584
|
+
html,
|
|
1585
|
+
tags: [
|
|
1586
|
+
{
|
|
1587
|
+
tag: 'link',
|
|
1588
|
+
attrs: { rel: 'stylesheet', href: zero.cssHref },
|
|
1589
|
+
injectTo: 'head',
|
|
1590
|
+
},
|
|
1591
|
+
],
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
},
|
|
626
1595
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1596
|
+
generateBundle(_outputOptions, bundle) {
|
|
1597
|
+
if (!zero?.isEnforcing || this.environment.name !== 'client') return
|
|
1598
|
+
// rolldown reports the modules that contributed rendered code per chunk,
|
|
1599
|
+
// which is exactly what shipped. Importer edges come from the whole
|
|
1600
|
+
// resolved graph so a forbidden module can name its shortest chain.
|
|
1601
|
+
const importers = new Map<string, string[]>()
|
|
1602
|
+
for (const moduleId of this.getModuleIds()) {
|
|
1603
|
+
for (const imported of this.getModuleInfo(moduleId)?.importedIds ?? []) {
|
|
1604
|
+
const list = importers.get(imported)
|
|
1605
|
+
if (list) list.push(moduleId)
|
|
1606
|
+
else importers.set(imported, [moduleId])
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
const entries: string[] = []
|
|
1610
|
+
const modules: { id: string; importers: readonly string[] }[] = []
|
|
1611
|
+
for (const chunk of Object.values(bundle)) {
|
|
1612
|
+
if (chunk.type !== 'chunk') continue
|
|
1613
|
+
for (const moduleId of Object.keys(chunk.modules)) {
|
|
1614
|
+
modules.push({ id: moduleId, importers: importers.get(moduleId) ?? [] })
|
|
1615
|
+
if (this.getModuleInfo(moduleId)?.isEntry) entries.push(moduleId)
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
const checked = Static.checkZeroGraph({
|
|
1619
|
+
entries,
|
|
1620
|
+
modules,
|
|
1621
|
+
importerEdges: importers,
|
|
1622
|
+
root: zero.resolved.root,
|
|
1623
|
+
})
|
|
1624
|
+
zeroReceipt = {
|
|
1625
|
+
integration: 'vite',
|
|
1626
|
+
graph: 'zero',
|
|
1627
|
+
entries: entries.sort(),
|
|
1628
|
+
moduleCount: modules.length,
|
|
1629
|
+
tamaguiModules: checked.tamaguiModules,
|
|
1630
|
+
forbidden: checked.forbidden,
|
|
1631
|
+
cssArtifact: null,
|
|
1632
|
+
identity: '',
|
|
1633
|
+
gzip: Object.fromEntries(
|
|
1634
|
+
Object.values(bundle)
|
|
1635
|
+
.filter((chunk) => chunk.type === 'chunk')
|
|
1636
|
+
.map((chunk) => [
|
|
1637
|
+
chunk.fileName,
|
|
1638
|
+
gzipSync(Buffer.from((chunk as any).code), { level: 9 }).length,
|
|
1639
|
+
])
|
|
1640
|
+
),
|
|
1641
|
+
}
|
|
1642
|
+
},
|
|
635
1643
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1644
|
+
async closeBundle() {
|
|
1645
|
+
if (!zero || this.environment.name !== 'client') return
|
|
1646
|
+
const outDir = path.resolve(config.root, this.environment.config.build.outDir)
|
|
1647
|
+
// one receipt per output directory, so a zero build and its negative
|
|
1648
|
+
// control never overwrite each other's evidence
|
|
1649
|
+
const receiptName = `vite-${path.basename(outDir)}`
|
|
1650
|
+
// Written in both modes and before the failure, so `report` and `enforce`
|
|
1651
|
+
// emit the identical list and only their exit differs.
|
|
1652
|
+
Static.writeZeroViolationReport(zero.resolved.outDir, receiptName, {
|
|
1653
|
+
integration: 'vite',
|
|
1654
|
+
mode: zero.isEnforcing ? 'enforce' : 'report',
|
|
1655
|
+
violations: zero.violations,
|
|
1656
|
+
})
|
|
1657
|
+
if (!zero.isEnforcing || zeroBuildFailed) return
|
|
1658
|
+
if (zero.violations.length) {
|
|
1659
|
+
throw new Error(Static.formatZeroViolations(zero.violations))
|
|
1660
|
+
}
|
|
1661
|
+
const islandOutputHashes: Record<string, string> = {}
|
|
1662
|
+
for (const island of zero.resolved.islands) {
|
|
1663
|
+
const built = await buildIsland({
|
|
1664
|
+
island,
|
|
1665
|
+
controller: zero,
|
|
1666
|
+
root: config.root,
|
|
1667
|
+
outDir,
|
|
1668
|
+
mode: config.mode,
|
|
1669
|
+
})
|
|
1670
|
+
islandOutputHashes[island.id] = built.hash
|
|
1671
|
+
}
|
|
641
1672
|
|
|
642
|
-
|
|
643
|
-
|
|
1673
|
+
// The plugin, not the app, injects the zero artifact's stylesheet link, so
|
|
1674
|
+
// an entry graph with no HTML entry strips the rules and loads nothing.
|
|
1675
|
+
if (zeroHtmlEntries === 0) {
|
|
1676
|
+
throw new Error(
|
|
1677
|
+
`[tamagui zero-runtime] the zero entry graph has no HTML entry, so the one generated CSS artifact ${zero.cssHref} is never loaded. Build a zero entry through its HTML document.`
|
|
1678
|
+
)
|
|
1679
|
+
}
|
|
644
1680
|
|
|
645
|
-
|
|
646
|
-
|
|
1681
|
+
const css = finalizeZeroCSS(zero, outDir)
|
|
1682
|
+
const bridgeManifest = Static.canonicalizeBridgeManifest(
|
|
1683
|
+
Object.fromEntries(
|
|
1684
|
+
[...zero.bridges.entries()].sort(([left], [right]) => (left < right ? -1 : 1))
|
|
1685
|
+
)
|
|
1686
|
+
)
|
|
1687
|
+
const identityInputs = {
|
|
1688
|
+
runtimeLiteral: 'zero' as const,
|
|
1689
|
+
target: 'web' as const,
|
|
1690
|
+
configGeneration: `${pluginInstanceId}:${tamaguiLoader.getGeneration()}`,
|
|
1691
|
+
cssHash: css.hash,
|
|
1692
|
+
compilerVersion: Static.ZERO_COMPILER_VERSION,
|
|
1693
|
+
islandEntries: zero.resolved.islands.map((island) => island.module),
|
|
1694
|
+
bridgeManifestHash: Static.hashBridgeManifest(bridgeManifest),
|
|
1695
|
+
islandOutputHashes,
|
|
1696
|
+
}
|
|
1697
|
+
const identity = Static.hashZeroIdentity(identityInputs)
|
|
647
1698
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
}
|
|
658
|
-
|
|
1699
|
+
if (!zeroReceipt) {
|
|
1700
|
+
throw new Error(
|
|
1701
|
+
`[tamagui zero-runtime] no module graph was recorded for the zero entry`
|
|
1702
|
+
)
|
|
1703
|
+
}
|
|
1704
|
+
zeroReceipt.cssArtifact = { path: css.href, hash: css.hash }
|
|
1705
|
+
zeroReceipt.identity = identity
|
|
1706
|
+
Static.writeZeroGraphReceipt(zero.resolved.outDir, receiptName, zeroReceipt)
|
|
1707
|
+
writeFileSync(
|
|
1708
|
+
path.join(zero.resolved.outDir, `${receiptName}.bridges.json`),
|
|
1709
|
+
`${JSON.stringify(
|
|
1710
|
+
{ identity, identityInputs, cssGzip: css.gzip, bridges: bridgeManifest },
|
|
1711
|
+
null,
|
|
1712
|
+
2
|
|
1713
|
+
)}\n`
|
|
1714
|
+
)
|
|
1715
|
+
assertZeroGraph(zeroReceipt)
|
|
659
1716
|
},
|
|
660
1717
|
}
|
|
661
1718
|
|
|
662
|
-
|
|
1719
|
+
// The compiled-global-CSS tier. `TAMAGUI_DID_OUTPUT_CSS` was already inlined
|
|
1720
|
+
// in the client environment, so this proves the artifact that replaces those
|
|
1721
|
+
// stripped rules exists, matches this build's config, and is in the graph.
|
|
1722
|
+
const globalCSSPlugin: Plugin = {
|
|
1723
|
+
name: 'tamagui-global-css',
|
|
1724
|
+
enforce: 'post',
|
|
1725
|
+
apply: 'build',
|
|
1726
|
+
|
|
1727
|
+
async buildStart() {
|
|
1728
|
+
if (!globalCSS || this.environment.name !== 'client') return
|
|
1729
|
+
await tamaguiLoader.ensureFullConfigLoaded()
|
|
1730
|
+
const tamaguiConfig = await tamaguiLoader.getTamaguiConfig()
|
|
1731
|
+
if (!tamaguiConfig) {
|
|
1732
|
+
throw new Error(
|
|
1733
|
+
`[tamagui] outputCSS is set but the Tamagui config did not evaluate, so no CSS artifact can be generated`
|
|
1734
|
+
)
|
|
1735
|
+
}
|
|
1736
|
+
globalCSSExpected = tamaguiConfig.getCSS()
|
|
1737
|
+
},
|
|
1738
|
+
|
|
1739
|
+
generateBundle() {
|
|
1740
|
+
if (!globalCSS || this.environment.name !== 'client') return
|
|
1741
|
+
const failure = Static.checkGlobalCSSArtifact({
|
|
1742
|
+
cssPath: globalCSS.cssPath,
|
|
1743
|
+
expectedCSS: globalCSSExpected ?? '',
|
|
1744
|
+
loadedModuleIds: this.getModuleIds(),
|
|
1745
|
+
importHint: `Import it once from your client entry: import ${JSON.stringify(
|
|
1746
|
+
relativeImportSpecifier(config.root, globalCSS.cssPath)
|
|
1747
|
+
)}`,
|
|
1748
|
+
})
|
|
1749
|
+
if (failure) throw new Error(failure.message)
|
|
1750
|
+
},
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
return {
|
|
1754
|
+
plugins: [
|
|
1755
|
+
basePlugin,
|
|
1756
|
+
rnwLitePlugin,
|
|
1757
|
+
extractPlugin,
|
|
1758
|
+
sharedCompilerPlugin,
|
|
1759
|
+
zeroRuntimePlugin,
|
|
1760
|
+
globalCSSPlugin,
|
|
1761
|
+
tamaguiNativePlugin(tamaguiOptionsIn),
|
|
1762
|
+
],
|
|
1763
|
+
loader: tamaguiLoader,
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
|
|
1767
|
+
/** Where the dev server's island bundles are built and served from. */
|
|
1768
|
+
function zeroDevIslandDir(zero: ZeroRuntimeController) {
|
|
1769
|
+
return path.join(zero.resolved.outDir, 'dev')
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function relativeImportSpecifier(from: string, to: string) {
|
|
1773
|
+
const relative = normalizePath(path.relative(from, to))
|
|
1774
|
+
return relative.startsWith('.') ? relative : `./${relative}`
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
export function tamaguiPlugin(options: TamaguiVitePluginOptions = {}): PluginOption {
|
|
1778
|
+
return createTamaguiPlugins(options).plugins
|
|
663
1779
|
}
|