@decocms/start 1.3.7 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cms/applySectionConventions.ts +4 -0
- package/src/cms/index.ts +3 -0
- package/src/cms/resolve.test.ts +1 -0
- package/src/cms/resolve.ts +140 -6
- package/src/hooks/DecoPageRenderer.tsx +12 -9
- package/src/routes/cmsRoute.ts +17 -4
- package/src/vite/plugin.js +29 -1
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
registerLayoutSections,
|
|
16
16
|
} from "./sectionLoaders";
|
|
17
17
|
import {
|
|
18
|
+
registerEagerSections,
|
|
18
19
|
registerSeoSections,
|
|
19
20
|
setAsyncRenderingConfig,
|
|
20
21
|
getAsyncRenderingConfig,
|
|
@@ -77,6 +78,9 @@ export function applySectionConventions(input: ApplySectionConventionsInput): vo
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
if (eagerSections.length > 0) {
|
|
81
|
+
// Permanent registry — survives subsequent setAsyncRenderingConfig() calls
|
|
82
|
+
registerEagerSections(eagerSections);
|
|
83
|
+
// Also add to alwaysEager for backward compat with code that reads the config
|
|
80
84
|
const existing = getAsyncRenderingConfig() ?? {};
|
|
81
85
|
setAsyncRenderingConfig({
|
|
82
86
|
...existing,
|
package/src/cms/index.ts
CHANGED
|
@@ -41,13 +41,16 @@ export {
|
|
|
41
41
|
evaluateMatcher,
|
|
42
42
|
extractSeoFromProps,
|
|
43
43
|
extractSeoFromSections,
|
|
44
|
+
cacheDeferredRawProps,
|
|
44
45
|
getAsyncRenderingConfig,
|
|
46
|
+
getDeferredRawProps,
|
|
45
47
|
isSeoSection,
|
|
46
48
|
onBeforeResolve,
|
|
47
49
|
registerBotPattern,
|
|
48
50
|
registerCommerceLoader,
|
|
49
51
|
registerCommerceLoaders,
|
|
50
52
|
registerMatcher,
|
|
53
|
+
registerEagerSections,
|
|
51
54
|
registerSeoSections,
|
|
52
55
|
resolveDecoPage,
|
|
53
56
|
resolvePageSections,
|
package/src/cms/resolve.test.ts
CHANGED
package/src/cms/resolve.ts
CHANGED
|
@@ -2,12 +2,14 @@ import { findPageByPath, loadBlocks } from "./loader";
|
|
|
2
2
|
import { getOnBeforeResolveProps, getSection, registerOnBeforeResolveProps } from "./registry";
|
|
3
3
|
import { isLayoutSection, runSingleSectionLoader } from "./sectionLoaders";
|
|
4
4
|
import { normalizeUrlsInObject } from "../sdk/normalizeUrls";
|
|
5
|
+
import { djb2Hex } from "../sdk/djb2";
|
|
5
6
|
|
|
6
7
|
// globalThis-backed: share state across Vite server function split modules
|
|
7
8
|
const G = globalThis as any;
|
|
8
9
|
if (!G.__deco) G.__deco = {};
|
|
9
10
|
if (!G.__deco.commerceLoaders) G.__deco.commerceLoaders = {};
|
|
10
11
|
if (!G.__deco.customMatchers) G.__deco.customMatchers = {};
|
|
12
|
+
if (!G.__deco.eagerSectionKeys) G.__deco.eagerSectionKeys = new Set<string>();
|
|
11
13
|
|
|
12
14
|
// ---------------------------------------------------------------------------
|
|
13
15
|
// onBeforeResolveProps helper — eagerly loads the section module if needed
|
|
@@ -69,9 +71,19 @@ export interface DeferredSection {
|
|
|
69
71
|
key: string;
|
|
70
72
|
/** Position in the original page section list. */
|
|
71
73
|
index: number;
|
|
72
|
-
/**
|
|
74
|
+
/**
|
|
75
|
+
* Short hash of rawProps for client-side cache busting.
|
|
76
|
+
* Keeps the serialized payload small — full rawProps are resolved
|
|
77
|
+
* server-side from the deferred props cache or page re-resolution.
|
|
78
|
+
*/
|
|
79
|
+
propsHash: string;
|
|
80
|
+
/**
|
|
81
|
+
* CMS-resolved props without section-loader enrichment.
|
|
82
|
+
* @deprecated Stripped before serialization to reduce HTML payload.
|
|
83
|
+
* Only present server-side in the rawProps cache.
|
|
84
|
+
*/
|
|
73
85
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
74
|
-
rawProps
|
|
86
|
+
rawProps?: Record<string, any>;
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
// ---------------------------------------------------------------------------
|
|
@@ -120,10 +132,15 @@ export function setAsyncRenderingConfig(config?: {
|
|
|
120
132
|
alwaysEager?: string[];
|
|
121
133
|
respectCmsLazy?: boolean;
|
|
122
134
|
}): void {
|
|
135
|
+
const existing = getAsyncConfig();
|
|
136
|
+
const merged = new Set([
|
|
137
|
+
...(existing?.alwaysEager ?? []),
|
|
138
|
+
...(config?.alwaysEager ?? []),
|
|
139
|
+
]);
|
|
123
140
|
G.__deco.asyncConfig = {
|
|
124
|
-
respectCmsLazy: config?.respectCmsLazy ?? true,
|
|
125
|
-
foldThreshold: config?.foldThreshold ?? Infinity,
|
|
126
|
-
alwaysEager:
|
|
141
|
+
respectCmsLazy: config?.respectCmsLazy ?? existing?.respectCmsLazy ?? true,
|
|
142
|
+
foldThreshold: config?.foldThreshold ?? existing?.foldThreshold ?? Infinity,
|
|
143
|
+
alwaysEager: merged,
|
|
127
144
|
};
|
|
128
145
|
}
|
|
129
146
|
|
|
@@ -132,6 +149,64 @@ export function getAsyncRenderingConfig(): AsyncRenderingConfig | null {
|
|
|
132
149
|
return getAsyncConfig();
|
|
133
150
|
}
|
|
134
151
|
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Permanent eager section registry — survives setAsyncRenderingConfig() calls
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Register sections that declared `export const eager = true`.
|
|
158
|
+
* This is a permanent registry that cannot be overwritten by
|
|
159
|
+
* subsequent calls to `setAsyncRenderingConfig()`.
|
|
160
|
+
*/
|
|
161
|
+
export function registerEagerSections(keys: string[]): void {
|
|
162
|
+
const set: Set<string> = G.__deco.eagerSectionKeys;
|
|
163
|
+
for (const k of keys) set.add(k);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function isEagerSection(key: string): boolean {
|
|
167
|
+
return (G.__deco.eagerSectionKeys as Set<string>).has(key);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---------------------------------------------------------------------------
|
|
171
|
+
// Deferred rawProps cache — keeps rawProps server-side to trim HTML payload
|
|
172
|
+
// ---------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
const DEFERRED_PROPS_TTL = 120_000; // 2 minutes
|
|
175
|
+
const deferredRawPropsCache = new Map<string, { rawProps: Record<string, unknown>; ts: number }>();
|
|
176
|
+
|
|
177
|
+
function deferredPropsCacheKey(pagePath: string, component: string, index: number): string {
|
|
178
|
+
return `${pagePath}::${component}::${index}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function cacheDeferredRawProps(
|
|
182
|
+
pagePath: string,
|
|
183
|
+
component: string,
|
|
184
|
+
index: number,
|
|
185
|
+
rawProps: Record<string, unknown>,
|
|
186
|
+
): void {
|
|
187
|
+
const key = deferredPropsCacheKey(pagePath, component, index);
|
|
188
|
+
deferredRawPropsCache.set(key, { rawProps, ts: Date.now() });
|
|
189
|
+
|
|
190
|
+
// Lazy eviction: remove expired entries when cache grows
|
|
191
|
+
if (deferredRawPropsCache.size > 500) {
|
|
192
|
+
const now = Date.now();
|
|
193
|
+
for (const [k, v] of deferredRawPropsCache) {
|
|
194
|
+
if (now - v.ts > DEFERRED_PROPS_TTL) deferredRawPropsCache.delete(k);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function getDeferredRawProps(
|
|
200
|
+
pagePath: string,
|
|
201
|
+
component: string,
|
|
202
|
+
index: number,
|
|
203
|
+
): Record<string, unknown> | null {
|
|
204
|
+
const key = deferredPropsCacheKey(pagePath, component, index);
|
|
205
|
+
const entry = deferredRawPropsCache.get(key);
|
|
206
|
+
if (!entry || Date.now() - entry.ts > DEFERRED_PROPS_TTL) return null;
|
|
207
|
+
return entry.rawProps;
|
|
208
|
+
}
|
|
209
|
+
|
|
135
210
|
// ---------------------------------------------------------------------------
|
|
136
211
|
// Bot detection — bots always receive fully eager pages for SEO
|
|
137
212
|
// ---------------------------------------------------------------------------
|
|
@@ -932,6 +1007,8 @@ function shouldDeferSection(
|
|
|
932
1007
|
const finalKey = resolveFinalSectionKey(section, matcherCtx);
|
|
933
1008
|
if (!finalKey) return false;
|
|
934
1009
|
|
|
1010
|
+
// Permanent registry — `export const eager = true` cannot be clobbered
|
|
1011
|
+
if (isEagerSection(finalKey)) return false;
|
|
935
1012
|
if (cfg.alwaysEager.has(finalKey)) return false;
|
|
936
1013
|
if (isLayoutSection(finalKey)) return false;
|
|
937
1014
|
|
|
@@ -1024,6 +1101,7 @@ function resolveSectionShallow(
|
|
|
1024
1101
|
component: rt,
|
|
1025
1102
|
key: rt,
|
|
1026
1103
|
index: -1,
|
|
1104
|
+
propsHash: djb2Hex(JSON.stringify(rawProps)),
|
|
1027
1105
|
rawProps: rawProps as Record<string, unknown>,
|
|
1028
1106
|
};
|
|
1029
1107
|
}
|
|
@@ -1241,6 +1319,14 @@ export async function resolveDecoPage(
|
|
|
1241
1319
|
const deferred = resolveSectionShallow(section, ctx);
|
|
1242
1320
|
if (deferred) {
|
|
1243
1321
|
deferred.index = currentFlatIndex;
|
|
1322
|
+
|
|
1323
|
+
// Cache rawProps server-side and strip from the deferred object
|
|
1324
|
+
// so they are NOT serialized into the HTML payload.
|
|
1325
|
+
if (deferred.rawProps) {
|
|
1326
|
+
cacheDeferredRawProps(targetPath, deferred.component, currentFlatIndex, deferred.rawProps);
|
|
1327
|
+
delete deferred.rawProps;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1244
1330
|
deferredSections.push(deferred);
|
|
1245
1331
|
deferredOk = true;
|
|
1246
1332
|
}
|
|
@@ -1441,9 +1527,16 @@ export async function resolveDeferredSectionFull(
|
|
|
1441
1527
|
request: Request,
|
|
1442
1528
|
matcherCtx?: MatcherContext,
|
|
1443
1529
|
): Promise<ResolvedSection | null> {
|
|
1530
|
+
// rawProps may be stripped from the client payload — resolve from cache or page
|
|
1531
|
+
const rawProps = ds.rawProps
|
|
1532
|
+
?? getDeferredRawProps(pagePath, ds.component, ds.index)
|
|
1533
|
+
?? await reExtractRawProps(pagePath, ds.component, ds.index, matcherCtx);
|
|
1534
|
+
|
|
1535
|
+
if (!rawProps) return null;
|
|
1536
|
+
|
|
1444
1537
|
const section = await resolveDeferredSection(
|
|
1445
1538
|
ds.component,
|
|
1446
|
-
|
|
1539
|
+
rawProps,
|
|
1447
1540
|
pagePath,
|
|
1448
1541
|
matcherCtx,
|
|
1449
1542
|
);
|
|
@@ -1452,3 +1545,44 @@ export async function resolveDeferredSectionFull(
|
|
|
1452
1545
|
const enriched = await runSingleSectionLoader(section, request);
|
|
1453
1546
|
return normalizeUrlsInObject(enriched);
|
|
1454
1547
|
}
|
|
1548
|
+
|
|
1549
|
+
/**
|
|
1550
|
+
* Fallback for deferred rawProps cache miss: re-resolve the page and extract
|
|
1551
|
+
* rawProps for the section at the given index. Expensive but ensures correctness
|
|
1552
|
+
* when the in-memory cache has been evicted (different isolate, TTL expired).
|
|
1553
|
+
*/
|
|
1554
|
+
async function reExtractRawProps(
|
|
1555
|
+
pagePath: string,
|
|
1556
|
+
component: string,
|
|
1557
|
+
sectionIndex: number,
|
|
1558
|
+
matcherCtx?: MatcherContext,
|
|
1559
|
+
): Promise<Record<string, unknown> | null> {
|
|
1560
|
+
ensureInitialized();
|
|
1561
|
+
|
|
1562
|
+
const match = findPageByPath(pagePath);
|
|
1563
|
+
if (!match) return null;
|
|
1564
|
+
|
|
1565
|
+
const { page } = match;
|
|
1566
|
+
const ctx: MatcherContext = { ...matcherCtx, path: pagePath };
|
|
1567
|
+
|
|
1568
|
+
let rawSections: unknown[];
|
|
1569
|
+
if (Array.isArray(page.sections)) {
|
|
1570
|
+
rawSections = page.sections;
|
|
1571
|
+
} else {
|
|
1572
|
+
const rctx: ResolveContext = { matcherCtx: ctx, memo: new Map(), depth: 0 };
|
|
1573
|
+
rawSections = await resolveSectionsList(page.sections, rctx);
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
if (sectionIndex < 0 || sectionIndex >= rawSections.length) return null;
|
|
1577
|
+
|
|
1578
|
+
const section = rawSections[sectionIndex];
|
|
1579
|
+
const shallow = resolveSectionShallow(section, ctx);
|
|
1580
|
+
if (!shallow || shallow.component !== component) return null;
|
|
1581
|
+
|
|
1582
|
+
// Cache for subsequent requests
|
|
1583
|
+
if (shallow.rawProps) {
|
|
1584
|
+
cacheDeferredRawProps(pagePath, component, sectionIndex, shallow.rawProps);
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
return shallow.rawProps ?? null;
|
|
1588
|
+
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
setResolvedComponent,
|
|
20
20
|
} from "../cms/registry";
|
|
21
21
|
import type { DeferredSection, ResolvedSection } from "../cms/resolve";
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
import { SectionErrorBoundary } from "./SectionErrorFallback";
|
|
24
24
|
|
|
25
25
|
type LazyComponent = ReturnType<typeof lazy>;
|
|
@@ -235,9 +235,10 @@ interface DeferredSectionWrapperProps {
|
|
|
235
235
|
errorFallback?: ReactNode;
|
|
236
236
|
loadFn: (data: {
|
|
237
237
|
component: string;
|
|
238
|
-
rawProps
|
|
238
|
+
rawProps?: Record<string, unknown>;
|
|
239
239
|
pagePath: string;
|
|
240
240
|
pageUrl?: string;
|
|
241
|
+
index?: number;
|
|
241
242
|
}) => Promise<ResolvedSection | null>;
|
|
242
243
|
}
|
|
243
244
|
|
|
@@ -249,8 +250,7 @@ function DeferredSectionWrapper({
|
|
|
249
250
|
errorFallback,
|
|
250
251
|
loadFn,
|
|
251
252
|
}: DeferredSectionWrapperProps) {
|
|
252
|
-
const
|
|
253
|
-
const stableKey = `${pagePath}::${deferred.component}::${deferred.index}::${propsHash}`;
|
|
253
|
+
const stableKey = `${pagePath}::${deferred.component}::${deferred.index}::${deferred.propsHash ?? ""}`;
|
|
254
254
|
const [section, setSection] = useState<ResolvedSection | null>(() =>
|
|
255
255
|
typeof document === "undefined" ? null : getCachedDeferredSection(stableKey),
|
|
256
256
|
);
|
|
@@ -308,9 +308,9 @@ function DeferredSectionWrapper({
|
|
|
308
308
|
const key0 = stableKey;
|
|
309
309
|
loadFn({
|
|
310
310
|
component: deferred.component,
|
|
311
|
-
rawProps: deferred.rawProps,
|
|
312
311
|
pagePath,
|
|
313
312
|
pageUrl,
|
|
313
|
+
index: deferred.index,
|
|
314
314
|
})
|
|
315
315
|
.then((result) => {
|
|
316
316
|
if (result) deferredSectionCache.set(key0, { section: result, ts: Date.now() });
|
|
@@ -328,9 +328,9 @@ function DeferredSectionWrapper({
|
|
|
328
328
|
const key1 = stableKey;
|
|
329
329
|
loadFn({
|
|
330
330
|
component: deferred.component,
|
|
331
|
-
rawProps: deferred.rawProps,
|
|
332
331
|
pagePath,
|
|
333
332
|
pageUrl,
|
|
333
|
+
index: deferred.index,
|
|
334
334
|
})
|
|
335
335
|
.then((result) => {
|
|
336
336
|
if (result) deferredSectionCache.set(key1, { section: result, ts: Date.now() });
|
|
@@ -344,7 +344,7 @@ function DeferredSectionWrapper({
|
|
|
344
344
|
|
|
345
345
|
observer.observe(el);
|
|
346
346
|
return () => observer.disconnect();
|
|
347
|
-
}, [deferred.component, deferred.
|
|
347
|
+
}, [deferred.component, deferred.index, deferred.propsHash, pagePath, pageUrl, section, loadFn]);
|
|
348
348
|
|
|
349
349
|
if (error) {
|
|
350
350
|
const errFallback = loadedOptions?.errorFallback
|
|
@@ -402,7 +402,9 @@ function DeferredSectionSkeleton({
|
|
|
402
402
|
}) {
|
|
403
403
|
const options = getSectionOptions(deferred.component);
|
|
404
404
|
if (options?.loadingFallback) {
|
|
405
|
-
|
|
405
|
+
// rawProps are no longer serialized to the client — pass empty object.
|
|
406
|
+
// LoadingFallback components should be pure layout skeletons.
|
|
407
|
+
return createElement(options.loadingFallback, deferred.rawProps ?? {});
|
|
406
408
|
}
|
|
407
409
|
if (fallback) return <>{fallback}</>;
|
|
408
410
|
if (isDev) return <DevMissingFallbackWarning component={deferred.component} />;
|
|
@@ -466,9 +468,10 @@ interface Props {
|
|
|
466
468
|
/** @deprecated Use deferredPromises instead (TanStack native streaming). */
|
|
467
469
|
loadDeferredSectionFn?: (data: {
|
|
468
470
|
component: string;
|
|
469
|
-
rawProps
|
|
471
|
+
rawProps?: Record<string, unknown>;
|
|
470
472
|
pagePath: string;
|
|
471
473
|
pageUrl?: string;
|
|
474
|
+
index?: number;
|
|
472
475
|
}) => Promise<ResolvedSection | null>;
|
|
473
476
|
}
|
|
474
477
|
|
package/src/routes/cmsRoute.ts
CHANGED
|
@@ -35,6 +35,7 @@ import type { DeferredSection, MatcherContext, PageSeo, ResolvedSection } from "
|
|
|
35
35
|
import {
|
|
36
36
|
extractSeoFromProps,
|
|
37
37
|
extractSeoFromSections,
|
|
38
|
+
getDeferredRawProps,
|
|
38
39
|
resolveDecoPage,
|
|
39
40
|
resolveDeferredSection,
|
|
40
41
|
resolveDeferredSectionFull,
|
|
@@ -198,7 +199,8 @@ export const loadDeferredSection = createServerFn({ method: "POST" })
|
|
|
198
199
|
(data: unknown) =>
|
|
199
200
|
data as {
|
|
200
201
|
component: string;
|
|
201
|
-
rawProps
|
|
202
|
+
/** @deprecated rawProps are now resolved server-side from the deferred props cache. */
|
|
203
|
+
rawProps?: Record<string, any>;
|
|
202
204
|
pagePath: string;
|
|
203
205
|
pageUrl?: string;
|
|
204
206
|
/** Original position in the page section list — preserved for correct SPA ordering. */
|
|
@@ -206,7 +208,7 @@ export const loadDeferredSection = createServerFn({ method: "POST" })
|
|
|
206
208
|
},
|
|
207
209
|
)
|
|
208
210
|
.handler(async (ctx) => {
|
|
209
|
-
const { component, rawProps, pagePath, pageUrl, index } = ctx.data;
|
|
211
|
+
const { component, rawProps: clientRawProps, pagePath, pageUrl, index } = ctx.data;
|
|
210
212
|
|
|
211
213
|
const originRequest = getRequest();
|
|
212
214
|
const serverUrl = getRequestUrl().toString();
|
|
@@ -218,6 +220,15 @@ export const loadDeferredSection = createServerFn({ method: "POST" })
|
|
|
218
220
|
request: originRequest,
|
|
219
221
|
};
|
|
220
222
|
|
|
223
|
+
// Resolve rawProps: prefer client-provided (backward compat), then server cache
|
|
224
|
+
const rawProps = clientRawProps
|
|
225
|
+
?? (index !== undefined ? getDeferredRawProps(pagePath, component, index) : null);
|
|
226
|
+
|
|
227
|
+
if (!rawProps) {
|
|
228
|
+
console.warn(`[CMS] Deferred section cache miss: ${component} at index ${index} on ${pagePath}`);
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
|
|
221
232
|
const section = await resolveDeferredSection(component, rawProps, pagePath, matcherCtx);
|
|
222
233
|
if (!section) return null;
|
|
223
234
|
|
|
@@ -253,14 +264,16 @@ export const deferredSectionLoader = async ({
|
|
|
253
264
|
rawProps,
|
|
254
265
|
pagePath,
|
|
255
266
|
pageUrl,
|
|
267
|
+
index,
|
|
256
268
|
}: {
|
|
257
269
|
component: string;
|
|
258
|
-
rawProps
|
|
270
|
+
rawProps?: Record<string, unknown>;
|
|
259
271
|
pagePath: string;
|
|
260
272
|
pageUrl?: string;
|
|
273
|
+
index?: number;
|
|
261
274
|
}): Promise<ResolvedSection | null> => {
|
|
262
275
|
return loadDeferredSection({
|
|
263
|
-
data: { component, rawProps, pagePath, pageUrl },
|
|
276
|
+
data: { component, rawProps, pagePath, pageUrl, index },
|
|
264
277
|
});
|
|
265
278
|
};
|
|
266
279
|
|
package/src/vite/plugin.js
CHANGED
|
@@ -131,12 +131,40 @@ export function decoVitePlugin() {
|
|
|
131
131
|
) {
|
|
132
132
|
return "vendor-react";
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
// TanStack Router — client-side router (always needed)
|
|
134
136
|
if (
|
|
135
137
|
id.includes("@tanstack/react-router") ||
|
|
136
|
-
id.includes("@tanstack/
|
|
138
|
+
id.includes("@tanstack/router-core")
|
|
139
|
+
) {
|
|
140
|
+
return "vendor-router";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// TanStack Start — specific checks before broad catch-all
|
|
144
|
+
// (react-start-client includes "react-start" so must come first)
|
|
145
|
+
if (
|
|
146
|
+
id.includes("@tanstack/react-start-client") ||
|
|
147
|
+
id.includes("@tanstack/start-client-core")
|
|
148
|
+
) {
|
|
149
|
+
return "vendor-router";
|
|
150
|
+
}
|
|
151
|
+
// Server-only TanStack packages — let Rollup tree-shake
|
|
152
|
+
if (
|
|
153
|
+
id.includes("@tanstack/react-start-server") ||
|
|
154
|
+
id.includes("@tanstack/start-server-core")
|
|
137
155
|
) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
// Remaining @tanstack/start (storage-context, plugin-core, etc.)
|
|
159
|
+
if (id.includes("@tanstack/start")) {
|
|
138
160
|
return "vendor-router";
|
|
139
161
|
}
|
|
162
|
+
|
|
163
|
+
// isbot — server-only (bot detection in resolve.ts)
|
|
164
|
+
if (id.includes("node_modules/isbot")) {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
140
168
|
if (id.includes("@tanstack/react-query")) {
|
|
141
169
|
return "vendor-query";
|
|
142
170
|
}
|