catalyst-core 0.2.0-beta.3 → 0.3.0-beta.2
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/babel.native.config.cjs +8 -0
- package/bin/catalyst.js +49 -105
- package/changelog.md +8 -0
- package/dist/caching.js +1 -1
- package/dist/globals.d.ts +7 -0
- package/dist/index.jsx +8 -0
- package/dist/logger.js +74 -3
- package/dist/native/androidProject/app/build.gradle.kts +45 -4
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +86 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +8 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +173 -13
- package/dist/native/androidProject/settings.gradle.kts +10 -0
- package/dist/native/androidSetup.js +300 -8
- package/dist/native/bridge/WebBridge.js +481 -59
- package/dist/native/bridge/constants/NativeInterfaces.js +304 -45
- package/dist/native/bridge/errors.js +250 -26
- package/dist/native/bridge/hooks/index.js +132 -1
- package/dist/native/bridge/hooks/useAI.js +94 -0
- package/dist/native/bridge/hooks/useCamera.js +347 -9
- package/dist/native/bridge/hooks/useDataProtection.js +200 -2
- package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
- package/dist/native/bridge/hooks/useFilePicker.js +429 -2
- package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
- package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
- package/dist/native/bridge/hooks/useIntent.js +95 -1
- package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
- package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
- package/dist/native/bridge/hooks/useNotification.js +318 -2
- package/dist/native/bridge/hooks/useSafeArea.js +84 -2
- package/dist/native/bridge/hooks/useVideoStream.js +434 -6
- package/dist/native/bridge/hooks/utils.js +27 -1
- package/dist/native/bridge/hooks.js +16 -1
- package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
- package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
- package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
- package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
- package/dist/native/bridge/perf/core/constants.js +103 -20
- package/dist/native/bridge/perf/core/measure.js +179 -30
- package/dist/native/bridge/perf/core/session.js +89 -23
- package/dist/native/bridge/perf/core/store.js +554 -1
- package/dist/native/bridge/perf/core/summary.js +174 -1
- package/dist/native/bridge/perf/core/traceExport.js +240 -1
- package/dist/native/bridge/perf/core/waterfalls.js +146 -1
- package/dist/native/bridge/perf/index.js +359 -38
- package/dist/native/bridge/safeArea.js +53 -5
- package/dist/native/bridge/standardizedHooks.js +595 -82
- package/dist/native/bridge/useBaseHook.js +274 -38
- package/dist/native/bridge/utils/CameraUtils.js +201 -56
- package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
- package/dist/native/bridge/utils/NativeBridge.js +425 -141
- package/dist/native/buildAndroid/assets.js +510 -13
- package/dist/native/buildAndroid/build.js +296 -19
- package/dist/native/buildAndroid/config.js +87 -1
- package/dist/native/buildAndroid/index.js +406 -1
- package/dist/native/buildAppAndroid.js +34 -2
- package/dist/native/buildAppIos.js +62 -2
- package/dist/native/buildIos/assets.js +609 -8
- package/dist/native/buildIos/build.js +639 -20
- package/dist/native/buildIos/config.js +294 -12
- package/dist/native/buildIos/index.js +343 -6
- package/dist/native/buildIos/plugins.js +343 -16
- package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
- package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
- package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
- package/dist/native/internalPluginUtils.js +232 -7
- package/dist/native/package.json +3 -0
- package/dist/native/plugin-bridge/PluginBridge.js +227 -1
- package/dist/native/pluginComposerAndroid.js +407 -23
- package/dist/native/pluginComposerIos.js +435 -11
- package/dist/native/renameAndroidProject.js +1094 -88
- package/dist/native/setupEmulatorIos.js +289 -22
- package/dist/native/setupServer.js +160 -19
- package/dist/native/terminalProgress.js +199 -11
- package/dist/native/utils.js +193 -15
- package/dist/offline/catalyst-sw.js +145 -1
- package/dist/offline/registerServiceWorker.js +33 -1
- package/dist/otel.js +375 -46
- package/dist/router/ClientRouter.js +14 -2
- package/dist/router/ServerRouter.js +9 -1
- package/dist/router.js +1 -1
- package/dist/scripts/build.js +112 -3
- package/dist/scripts/checkVersion.js +3 -1
- package/dist/scripts/cleanCache.js +30 -1
- package/dist/scripts/generateOfflineManifest.js +116 -1
- package/dist/scripts/loadEnvironmentVariables.js +32 -6
- package/dist/scripts/plugins.js +309 -1
- package/dist/scripts/preServerInit.js +13 -0
- package/dist/scripts/scriptUtils.js +54 -3
- package/dist/scripts/serve.js +51 -4
- package/dist/scripts/start.js +47 -10
- package/dist/scripts/validator.js +149 -1
- package/dist/sentry.cjs +127 -0
- package/dist/sentry.js +125 -5
- package/dist/server/expressServer.js +296 -5
- package/dist/server/manifestCache.js +36 -0
- package/dist/server/renderer/ChunkExtractor.js +144 -0
- package/dist/server/renderer/document/Body.jsx +59 -0
- package/dist/server/renderer/document/FastRefresh.jsx +20 -0
- package/dist/server/renderer/document/Head.jsx +60 -0
- package/dist/server/renderer/document/index.jsx +2 -0
- package/dist/server/renderer/extract.js +149 -31
- package/dist/server/renderer/handler.jsx +450 -0
- package/dist/server/renderer/index.js +8 -1
- package/dist/server/renderer/render.js +47 -0
- package/dist/server/utils/botDetectionMiddleware.js +29 -0
- package/dist/server/utils/cjsRequire.js +10 -0
- package/dist/server/utils/userAgentUtil.js +87 -9
- package/dist/server/utils/validator.js +164 -1
- package/dist/vite/FastRefresh.jsx +20 -0
- package/dist/vite/inject-cache-key-plugin.js +276 -0
- package/dist/vite/loadCustomViteConfig.js +22 -0
- package/dist/vite/manifest-categorization-plugin.js +455 -0
- package/dist/vite/node-loader.mjs +458 -0
- package/dist/vite/scssParams.js +21 -0
- package/dist/vite/vite.config.client.js +130 -0
- package/dist/vite/vite.config.js +498 -0
- package/dist/vite/vite.config.server.js +84 -0
- package/dist/web-router/components/MetaTag.jsx +34 -0
- package/dist/web-router/components/RouterDataProvider.jsx +332 -0
- package/dist/web-router/components/Split.jsx +200 -0
- package/dist/web-router/components/SplitInview.jsx +106 -0
- package/dist/web-router/components/SsrRequestContext.jsx +14 -0
- package/dist/web-router/context.jsx +12 -0
- package/dist/web-router/hooks.jsx +72 -0
- package/dist/web-router/types/components/MetaTag.d.ts +2 -0
- package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
- package/dist/web-router/types/context.d.ts +14 -0
- package/dist/web-router/types/hooks.d.ts +1 -0
- package/dist/web-router/types/index.d.ts +3 -0
- package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +119 -0
- package/mcp_v2/conversion-tasks.json +35 -17
- package/mcp_v2/knowledge-base.json +176 -17
- package/mcp_v2/lib/helpers.js +10 -0
- package/mcp_v2/mcp.js +40 -8
- package/mcp_v2/package.json +1 -0
- package/mcp_v2/tools/build.js +161 -37
- package/mcp_v2/tools/conversion.js +106 -5
- package/mcp_v2/tools/tasks.js +18 -14
- package/package.json +35 -68
- package/babel.config.js +0 -32
- package/dist/index.js +0 -1
- package/dist/scripts/devBuild.js +0 -3
- package/dist/scripts/devServe.js +0 -3
- package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
- package/dist/scripts/registerAliases.js +0 -6
- package/dist/server/renderer/document/Body.js +0 -11
- package/dist/server/renderer/document/Head.js +0 -9
- package/dist/server/renderer/document/index.js +0 -1
- package/dist/server/renderer/handler.js +0 -47
- package/dist/server/startServer.js +0 -57
- package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
- package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
- package/dist/webpack/babel.config.client.js +0 -1
- package/dist/webpack/babel.config.ssr.js +0 -1
- package/dist/webpack/base.babel.js +0 -7
- package/dist/webpack/development.client.babel.js +0 -10
- package/dist/webpack/production.client.babel.js +0 -4
- package/dist/webpack/production.ssr.babel.js +0 -8
- package/dist/webpack/scssParams.js +0 -9
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Plugin for Categorizing Assets by SSR Split Configuration
|
|
3
|
+
*
|
|
4
|
+
* This plugin categorizes build assets into four groups:
|
|
5
|
+
* - essential: Entry chunks + their transitive STATIC import closure (not dynamic)
|
|
6
|
+
* - ssrTrue: Assets loaded via split with ssr: true
|
|
7
|
+
* - ssrFalse: Assets loaded via split with ssr: false
|
|
8
|
+
* - orphan: Non-entry chunks with zero static importers (not split targets)
|
|
9
|
+
**/
|
|
10
|
+
import path from "path"
|
|
11
|
+
import fs from "fs"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Walk an AST node and invoke the visitor for every node.
|
|
15
|
+
*/
|
|
16
|
+
function walk(node, visitor) {
|
|
17
|
+
if (!node || typeof node !== "object") return
|
|
18
|
+
|
|
19
|
+
visitor(node)
|
|
20
|
+
|
|
21
|
+
for (const key of Object.keys(node)) {
|
|
22
|
+
const child = node[key]
|
|
23
|
+
if (Array.isArray(child)) {
|
|
24
|
+
for (const item of child) {
|
|
25
|
+
if (item && typeof item.type === "string") {
|
|
26
|
+
walk(item, visitor)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} else if (child && typeof child.type === "string") {
|
|
30
|
+
walk(child, visitor)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function manifestCategorizationPlugin(options = {}) {
|
|
36
|
+
const { outputFile = "asset-categories.json" } = options
|
|
37
|
+
|
|
38
|
+
// Collected during transform, resolved asynchronously in buildEnd
|
|
39
|
+
const pendingSplitPaths = [] // { importPath, importer, ssrValue }
|
|
40
|
+
const splitModules = new Map() // resolvedId -> { ssr: boolean, originalPath: string }
|
|
41
|
+
|
|
42
|
+
const moduleToChunk = new Map() // moduleId -> chunkFileName
|
|
43
|
+
const chunkDependencies = new Map() // chunkFileName -> Set<chunkFileName> (static imports only)
|
|
44
|
+
|
|
45
|
+
let processedManifest = null
|
|
46
|
+
|
|
47
|
+
function processBundle(bundle) {
|
|
48
|
+
// Pass 1: Build module → chunk mapping
|
|
49
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
50
|
+
if (chunk.type !== "chunk") continue
|
|
51
|
+
if (chunk.facadeModuleId) {
|
|
52
|
+
moduleToChunk.set(chunk.facadeModuleId, fileName)
|
|
53
|
+
}
|
|
54
|
+
if (chunk.modules) {
|
|
55
|
+
for (const moduleId of Object.keys(chunk.modules)) {
|
|
56
|
+
moduleToChunk.set(moduleId, fileName)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Pass 2: Build static-only dependency graph (excludes dynamicImports intentionally)
|
|
62
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
63
|
+
if (chunk.type !== "chunk") continue
|
|
64
|
+
chunkDependencies.set(fileName, new Set(chunk.imports || []))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Identify which chunks are split targets
|
|
68
|
+
const splitChunkNames = new Set()
|
|
69
|
+
for (const moduleId of splitModules.keys()) {
|
|
70
|
+
const chunkName = moduleToChunk.get(moduleId)
|
|
71
|
+
if (chunkName && bundle[chunkName]) splitChunkNames.add(chunkName)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Build reverse adjacency map once — O(n) instead of O(n²) per lookup
|
|
75
|
+
const importedBy = new Map() // chunkFileName -> Set<chunkFileNames that statically import it>
|
|
76
|
+
for (const [chunk, deps] of chunkDependencies.entries()) {
|
|
77
|
+
for (const dep of deps) {
|
|
78
|
+
if (!importedBy.has(dep)) importedBy.set(dep, new Set())
|
|
79
|
+
importedBy.get(dep).add(chunk)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isChunkOnlyUsedBySplits = (chunkName) => {
|
|
84
|
+
const importers = importedBy.get(chunkName)
|
|
85
|
+
if (!importers || importers.size === 0) return false
|
|
86
|
+
return [...importers].every((imp) => splitChunkNames.has(imp))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Orphan chunks: no static importers, not an entry chunk, and not a split target.
|
|
90
|
+
// Split targets have zero static importers (they're dynamically imported) but
|
|
91
|
+
// are already categorized as ssrTrue/ssrFalse — they're not true orphans.
|
|
92
|
+
const orphanChunkNames = new Set()
|
|
93
|
+
for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
94
|
+
if (chunk.type !== "chunk") continue
|
|
95
|
+
if (chunk.isEntry) continue
|
|
96
|
+
if (splitChunkNames.has(fileName)) continue
|
|
97
|
+
const importers = importedBy.get(fileName)
|
|
98
|
+
if (!importers || importers.size === 0) {
|
|
99
|
+
orphanChunkNames.add(fileName)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// BFS over STATIC imports from entry chunks only.
|
|
104
|
+
// Dynamic imports (dynamicImports) are intentionally excluded — they are lazy by design.
|
|
105
|
+
const entryChunks = Object.entries(bundle)
|
|
106
|
+
.filter(([, chunk]) => chunk.type === "chunk" && chunk.isEntry)
|
|
107
|
+
.map(([fileName]) => fileName)
|
|
108
|
+
|
|
109
|
+
const essentialChunkNames = new Set(entryChunks)
|
|
110
|
+
const queue = [...entryChunks]
|
|
111
|
+
|
|
112
|
+
while (queue.length > 0) {
|
|
113
|
+
const current = queue.shift()
|
|
114
|
+
for (const dep of chunkDependencies.get(current) || []) {
|
|
115
|
+
if (
|
|
116
|
+
!essentialChunkNames.has(dep) &&
|
|
117
|
+
!splitChunkNames.has(dep) &&
|
|
118
|
+
!isChunkOnlyUsedBySplits(dep) &&
|
|
119
|
+
!orphanChunkNames.has(dep)
|
|
120
|
+
) {
|
|
121
|
+
essentialChunkNames.add(dep)
|
|
122
|
+
queue.push(dep)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const toEntry = (fileName, chunk) => ({
|
|
128
|
+
file: fileName,
|
|
129
|
+
src: chunk.facadeModuleId || "",
|
|
130
|
+
isEntry: chunk.isEntry || false,
|
|
131
|
+
css: chunk.css || [],
|
|
132
|
+
imports: chunk.imports || [],
|
|
133
|
+
dynamicImports: chunk.dynamicImports || [],
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
const categorizedChunks = { essential: {}, ssrTrue: {}, ssrFalse: {}, orphan: {} }
|
|
137
|
+
|
|
138
|
+
for (const fileName of essentialChunkNames) {
|
|
139
|
+
if (bundle[fileName]?.type === "chunk") {
|
|
140
|
+
categorizedChunks.essential[fileName] = toEntry(fileName, bundle[fileName])
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const seen = new Set()
|
|
145
|
+
for (const [moduleId, splitInfo] of splitModules.entries()) {
|
|
146
|
+
const chunkName = moduleToChunk.get(moduleId)
|
|
147
|
+
if (chunkName && bundle[chunkName] && !seen.has(chunkName)) {
|
|
148
|
+
const category = splitInfo.ssr ? "ssrTrue" : "ssrFalse"
|
|
149
|
+
categorizedChunks[category][chunkName] = toEntry(chunkName, bundle[chunkName])
|
|
150
|
+
seen.add(chunkName)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const fileName of orphanChunkNames) {
|
|
155
|
+
if (bundle[fileName]?.type === "chunk") {
|
|
156
|
+
categorizedChunks.orphan[fileName] = toEntry(fileName, bundle[fileName])
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
processedManifest = {
|
|
161
|
+
...categorizedChunks,
|
|
162
|
+
metadata: {
|
|
163
|
+
generatedAt: new Date().toISOString(),
|
|
164
|
+
totalAssets: Object.keys(bundle).filter((k) => bundle[k].type === "chunk").length,
|
|
165
|
+
dependencyStats: {
|
|
166
|
+
ssrTrueModules: Object.keys(categorizedChunks.ssrTrue).length,
|
|
167
|
+
ssrFalseModules: Object.keys(categorizedChunks.ssrFalse).length,
|
|
168
|
+
essentialModules: Object.keys(categorizedChunks.essential).length,
|
|
169
|
+
orphanModules: Object.keys(categorizedChunks.orphan).length,
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return processedManifest
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Apply Vite manifest structure to categorized chunks
|
|
178
|
+
function applyViteManifestStructure(categorizedManifest, viteManifest) {
|
|
179
|
+
const newCategorizedChunks = {
|
|
180
|
+
essential: {},
|
|
181
|
+
ssrTrue: {},
|
|
182
|
+
ssrFalse: {},
|
|
183
|
+
orphan: {},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
for (const category of ["essential", "ssrTrue", "ssrFalse", "orphan"]) {
|
|
187
|
+
for (const [chunkName, chunkData] of Object.entries(categorizedManifest[category])) {
|
|
188
|
+
let matchedViteKey = null
|
|
189
|
+
let matchedViteEntry = null
|
|
190
|
+
|
|
191
|
+
for (const [viteKey, viteEntry] of Object.entries(viteManifest)) {
|
|
192
|
+
if (viteEntry.file === chunkName) {
|
|
193
|
+
matchedViteKey = viteKey
|
|
194
|
+
matchedViteEntry = viteEntry
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (matchedViteEntry && matchedViteKey) {
|
|
200
|
+
newCategorizedChunks[category][matchedViteKey] = { ...matchedViteEntry }
|
|
201
|
+
} else {
|
|
202
|
+
console.log(`❌ No manifest match found for ${chunkName}, keeping original structure`)
|
|
203
|
+
newCategorizedChunks[category][chunkName] = chunkData
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Ensure entry points are always in essential
|
|
209
|
+
for (const [viteKey, viteEntry] of Object.entries(viteManifest)) {
|
|
210
|
+
if (
|
|
211
|
+
viteEntry.isEntry &&
|
|
212
|
+
!newCategorizedChunks.essential[viteKey] &&
|
|
213
|
+
!newCategorizedChunks.ssrTrue[viteKey] &&
|
|
214
|
+
!newCategorizedChunks.ssrFalse[viteKey] &&
|
|
215
|
+
!newCategorizedChunks.orphan[viteKey]
|
|
216
|
+
) {
|
|
217
|
+
newCategorizedChunks.essential[viteKey] = { ...viteEntry }
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
Object.keys(categorizedManifest).forEach((category) => {
|
|
222
|
+
if (category !== "metadata") {
|
|
223
|
+
categorizedManifest[category] = newCategorizedChunks[category]
|
|
224
|
+
}
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Recursively collect all CSS files reachable from a manifest key
|
|
229
|
+
function collectTransitiveCss(viteKey, viteManifest, visited = new Set()) {
|
|
230
|
+
if (visited.has(viteKey)) return []
|
|
231
|
+
visited.add(viteKey)
|
|
232
|
+
|
|
233
|
+
const entry = viteManifest[viteKey]
|
|
234
|
+
if (!entry) return []
|
|
235
|
+
|
|
236
|
+
const css = [...(entry.css || [])]
|
|
237
|
+
for (const imp of entry.imports || []) {
|
|
238
|
+
css.push(...collectTransitiveCss(imp, viteManifest, visited))
|
|
239
|
+
}
|
|
240
|
+
return css
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Attach allCss (transitive) to every chunk in all categories
|
|
244
|
+
function enrichWithTransitiveCss(categorizedManifest, viteManifest) {
|
|
245
|
+
for (const category of ["essential", "ssrTrue", "ssrFalse", "orphan"]) {
|
|
246
|
+
for (const [key, chunk] of Object.entries(categorizedManifest[category])) {
|
|
247
|
+
const transitiveCss = collectTransitiveCss(key, viteManifest)
|
|
248
|
+
chunk.allCss = [...new Set(transitiveCss)]
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// When Vite leaves facadeModuleId unset, the chunk lands in the manifest under
|
|
254
|
+
// `_<name>-<hash>.js` with no `src` field. The cacheKey baked into bundles by
|
|
255
|
+
// injectCacheKeyPlugin is the source-relative path (e.g. "../../src/js/pages/
|
|
256
|
+
// HealthRecords/HealthRecords.js"), so the runtime ChunkExtractor lookup misses
|
|
257
|
+
// and the route's CSS never reaches the inline <style>. Alias every split's
|
|
258
|
+
// entry under its source-path key so the existing direct lookup hits.
|
|
259
|
+
//
|
|
260
|
+
// Aliases share the same object as the canonical entry — safe because this runs
|
|
261
|
+
// after enrichWithTransitiveCss, so allCss is already populated.
|
|
262
|
+
function addSourcePathAliases(categorizedManifest) {
|
|
263
|
+
// Match injectCacheKeyPlugin's manifestDir convention exactly so the alias key
|
|
264
|
+
// equals the cacheKey string baked into chunks.
|
|
265
|
+
const manifestDir = path.join(process.env.src_path, "build", ".vite")
|
|
266
|
+
|
|
267
|
+
for (const [moduleId, splitInfo] of splitModules.entries()) {
|
|
268
|
+
const chunkFile = moduleToChunk.get(moduleId)
|
|
269
|
+
if (!chunkFile) continue
|
|
270
|
+
|
|
271
|
+
let sourcePathKey = path.relative(manifestDir, moduleId).replace(/\\/g, "/")
|
|
272
|
+
if (!sourcePathKey.endsWith(".js")) sourcePathKey += ".js"
|
|
273
|
+
|
|
274
|
+
const bucket = splitInfo.ssr ? "ssrTrue" : "ssrFalse"
|
|
275
|
+
// Already keyed by source path (non-anonymous chunk) — nothing to do.
|
|
276
|
+
if (categorizedManifest[bucket][sourcePathKey]) continue
|
|
277
|
+
|
|
278
|
+
let aliasTarget = null
|
|
279
|
+
for (const entry of Object.values(categorizedManifest[bucket])) {
|
|
280
|
+
if (entry?.file === chunkFile) {
|
|
281
|
+
aliasTarget = entry
|
|
282
|
+
break
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (aliasTarget) {
|
|
286
|
+
categorizedManifest[bucket][sourcePathKey] = aliasTarget
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Guard against silent CSS loss: every CSS file emitted to disk should be referenced
|
|
292
|
+
// by at least one bucket's css or allCss. If enrichWithTransitiveCss ever misses a
|
|
293
|
+
// branch of the import graph, the affected CSS would vanish from server responses
|
|
294
|
+
// with no runtime error — this check surfaces that as a build-time warning.
|
|
295
|
+
function validateCssCoverage(categorizedManifest, buildDir) {
|
|
296
|
+
const cssDir = path.join(buildDir, "client", "assets", "css")
|
|
297
|
+
if (!fs.existsSync(cssDir)) return
|
|
298
|
+
|
|
299
|
+
const emitted = new Set(
|
|
300
|
+
fs
|
|
301
|
+
.readdirSync(cssDir)
|
|
302
|
+
.filter((f) => f.endsWith(".css"))
|
|
303
|
+
.map((f) => `client/assets/css/${f}`)
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
const covered = new Set()
|
|
307
|
+
for (const category of ["essential", "ssrTrue", "ssrFalse", "orphan"]) {
|
|
308
|
+
for (const chunk of Object.values(categorizedManifest[category])) {
|
|
309
|
+
for (const c of chunk.css || []) covered.add(c)
|
|
310
|
+
for (const c of chunk.allCss || []) covered.add(c)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const missing = [...emitted].filter((c) => !covered.has(c))
|
|
315
|
+
if (missing.length > 0) {
|
|
316
|
+
console.warn(
|
|
317
|
+
`\n⚠️ [manifest-categorization] ${missing.length} emitted CSS file(s) are not ` +
|
|
318
|
+
`referenced by any bucket's css/allCss.\n` +
|
|
319
|
+
`At request time the ChunkExtractor may silently drop them, causing unstyled ` +
|
|
320
|
+
`content on affected routes. Likely cause: a regression in enrichWithTransitiveCss, ` +
|
|
321
|
+
`a new chunk shape Vite emits that the walker doesn't follow, or a categorization ` +
|
|
322
|
+
`gap that leaves some chunk unreachable from every bucket.\n\n` +
|
|
323
|
+
`Unreferenced files:\n${missing.map((m) => " " + m).join("\n")}\n\n` +
|
|
324
|
+
`Please raise an issue at https://github.com/tata1mg/catalyst-core/issues so the ` +
|
|
325
|
+
`maintainers can investigate.\n`
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
name: "manifest-categorization",
|
|
332
|
+
|
|
333
|
+
// Scan source files to identify split calls, collect for async resolution in buildEnd
|
|
334
|
+
transform(code, id) {
|
|
335
|
+
if (id.includes("node_modules")) return null
|
|
336
|
+
if (!code.includes("split")) return null
|
|
337
|
+
|
|
338
|
+
let ast
|
|
339
|
+
try {
|
|
340
|
+
ast = this.parse(code)
|
|
341
|
+
} catch {
|
|
342
|
+
return null
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
walk(ast, (node) => {
|
|
346
|
+
if (node.type !== "CallExpression") return
|
|
347
|
+
if (node.callee.type !== "Identifier" || node.callee.name !== "split") return
|
|
348
|
+
|
|
349
|
+
const args = node.arguments
|
|
350
|
+
if (args.length === 0) return
|
|
351
|
+
|
|
352
|
+
// First arg must be a function that returns import("...")
|
|
353
|
+
const loader = args[0]
|
|
354
|
+
let importExpr
|
|
355
|
+
|
|
356
|
+
if (loader.type === "ArrowFunctionExpression") {
|
|
357
|
+
importExpr = loader.body
|
|
358
|
+
} else if (loader.type === "FunctionExpression") {
|
|
359
|
+
const stmts = loader.body.body
|
|
360
|
+
if (stmts.length !== 1 || stmts[0].type !== "ReturnStatement") return
|
|
361
|
+
importExpr = stmts[0].argument
|
|
362
|
+
} else {
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!importExpr || importExpr.type !== "ImportExpression") return
|
|
367
|
+
if (importExpr.source.type !== "Literal" || typeof importExpr.source.value !== "string")
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
const importPath = importExpr.source.value
|
|
371
|
+
|
|
372
|
+
// Extract ssr value from options object (second arg) if present
|
|
373
|
+
let ssrValue = true // default
|
|
374
|
+
if (args.length >= 2 && args[1].type === "ObjectExpression") {
|
|
375
|
+
const ssrProp = args[1].properties.find(
|
|
376
|
+
(p) =>
|
|
377
|
+
p.type === "Property" &&
|
|
378
|
+
p.key.type === "Identifier" &&
|
|
379
|
+
p.key.name === "ssr" &&
|
|
380
|
+
p.value.type === "Literal"
|
|
381
|
+
)
|
|
382
|
+
if (ssrProp) {
|
|
383
|
+
ssrValue = ssrProp.value.value === true
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
pendingSplitPaths.push({ importPath, importer: id, ssrValue })
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
return null
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
// Resolve all split paths here — buildEnd is async-safe, unlike transform
|
|
394
|
+
async buildEnd() {
|
|
395
|
+
await Promise.all(
|
|
396
|
+
pendingSplitPaths.map(async ({ importPath, importer, ssrValue }) => {
|
|
397
|
+
try {
|
|
398
|
+
const resolved = await this.resolve(importPath, importer)
|
|
399
|
+
if (resolved) {
|
|
400
|
+
splitModules.set(resolved.id, {
|
|
401
|
+
ssr: ssrValue,
|
|
402
|
+
originalPath: importPath,
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
} catch (err) {
|
|
406
|
+
console.log(`❌ Error resolving ${importPath}:`, err.message)
|
|
407
|
+
}
|
|
408
|
+
})
|
|
409
|
+
)
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
generateBundle(options, bundle) {
|
|
413
|
+
processedManifest = processBundle(bundle)
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
// Write the file after all files are written (manifest.json is available)
|
|
417
|
+
closeBundle() {
|
|
418
|
+
let viteManifest = null
|
|
419
|
+
|
|
420
|
+
try {
|
|
421
|
+
const manifestPath = path.join(
|
|
422
|
+
process.env.src_path,
|
|
423
|
+
process.env.BUILD_OUTPUT_PATH || "build",
|
|
424
|
+
".vite",
|
|
425
|
+
"manifest.json"
|
|
426
|
+
)
|
|
427
|
+
if (fs.existsSync(manifestPath)) {
|
|
428
|
+
const manifestContent = fs.readFileSync(manifestPath, "utf8")
|
|
429
|
+
viteManifest = JSON.parse(manifestContent)
|
|
430
|
+
} else {
|
|
431
|
+
console.log("❌ Could not find manifest.json at expected path:", manifestPath)
|
|
432
|
+
return
|
|
433
|
+
}
|
|
434
|
+
} catch (e) {
|
|
435
|
+
console.log("❌ Could not read manifest from file system:", e.message)
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!processedManifest) {
|
|
440
|
+
console.log("❌ No processed manifest available")
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
applyViteManifestStructure(processedManifest, viteManifest)
|
|
445
|
+
enrichWithTransitiveCss(processedManifest, viteManifest)
|
|
446
|
+
addSourcePathAliases(processedManifest)
|
|
447
|
+
|
|
448
|
+
const buildDir = path.join(process.env.src_path, process.env.BUILD_OUTPUT_PATH || "build")
|
|
449
|
+
validateCssCoverage(processedManifest, buildDir)
|
|
450
|
+
|
|
451
|
+
const outputPath = path.join(buildDir, ".vite", outputFile)
|
|
452
|
+
fs.writeFileSync(outputPath, JSON.stringify(processedManifest, null, 2))
|
|
453
|
+
},
|
|
454
|
+
}
|
|
455
|
+
}
|