one 1.2.39 → 1.2.41

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.
Files changed (135) hide show
  1. package/dist/cjs/cli/build.cjs +18 -4
  2. package/dist/cjs/cli/build.js +21 -5
  3. package/dist/cjs/cli/build.js.map +1 -1
  4. package/dist/cjs/cli/build.native.js +31 -4
  5. package/dist/cjs/cli/build.native.js.map +1 -1
  6. package/dist/cjs/cli/buildPage.cjs +68 -14
  7. package/dist/cjs/cli/buildPage.js +65 -14
  8. package/dist/cjs/cli/buildPage.js.map +1 -1
  9. package/dist/cjs/cli/buildPage.native.js +86 -28
  10. package/dist/cjs/cli/buildPage.native.js.map +1 -1
  11. package/dist/cjs/constants.cjs +1 -0
  12. package/dist/cjs/constants.js +1 -0
  13. package/dist/cjs/constants.js.map +1 -1
  14. package/dist/cjs/constants.native.js +1 -0
  15. package/dist/cjs/constants.native.js.map +1 -1
  16. package/dist/cjs/createApp.cjs +2 -1
  17. package/dist/cjs/createApp.js +2 -1
  18. package/dist/cjs/createApp.js.map +1 -1
  19. package/dist/cjs/router/filterRootHTML.cjs +4 -4
  20. package/dist/cjs/router/filterRootHTML.js +4 -4
  21. package/dist/cjs/router/filterRootHTML.js.map +1 -1
  22. package/dist/cjs/router/filterRootHTML.native.js +7 -6
  23. package/dist/cjs/router/filterRootHTML.native.js.map +1 -1
  24. package/dist/cjs/router/useScreens.cjs +14 -5
  25. package/dist/cjs/router/useScreens.js +20 -3
  26. package/dist/cjs/router/useScreens.js.map +1 -1
  27. package/dist/cjs/router/useScreens.native.js +13 -4
  28. package/dist/cjs/router/useScreens.native.js.map +1 -1
  29. package/dist/cjs/server/ServerContextScript.cjs +12 -2
  30. package/dist/cjs/server/ServerContextScript.js +6 -2
  31. package/dist/cjs/server/ServerContextScript.js.map +1 -1
  32. package/dist/cjs/server/ServerContextScript.native.js +12 -2
  33. package/dist/cjs/server/ServerContextScript.native.js.map +1 -1
  34. package/dist/cjs/server/oneServe.cjs +3 -2
  35. package/dist/cjs/server/oneServe.js +4 -2
  36. package/dist/cjs/server/oneServe.js.map +2 -2
  37. package/dist/cjs/server/oneServe.native.js +3 -2
  38. package/dist/cjs/server/oneServe.native.js.map +1 -1
  39. package/dist/cjs/server-render.cjs +9 -1
  40. package/dist/cjs/server-render.js +9 -1
  41. package/dist/cjs/server-render.js.map +1 -1
  42. package/dist/cjs/server-render.native.js +12 -3
  43. package/dist/cjs/server-render.native.js.map +1 -1
  44. package/dist/cjs/vite/one.cjs +5 -1
  45. package/dist/cjs/vite/one.js +6 -3
  46. package/dist/cjs/vite/one.js.map +1 -1
  47. package/dist/cjs/vite/one.native.js +11 -2
  48. package/dist/cjs/vite/one.native.js.map +1 -1
  49. package/dist/esm/cli/build.js +21 -5
  50. package/dist/esm/cli/build.js.map +1 -1
  51. package/dist/esm/cli/build.mjs +18 -4
  52. package/dist/esm/cli/build.mjs.map +1 -1
  53. package/dist/esm/cli/build.native.js +31 -4
  54. package/dist/esm/cli/build.native.js.map +1 -1
  55. package/dist/esm/cli/buildPage.js +65 -14
  56. package/dist/esm/cli/buildPage.js.map +1 -1
  57. package/dist/esm/cli/buildPage.mjs +68 -14
  58. package/dist/esm/cli/buildPage.mjs.map +1 -1
  59. package/dist/esm/cli/buildPage.native.js +86 -28
  60. package/dist/esm/cli/buildPage.native.js.map +1 -1
  61. package/dist/esm/constants.js +1 -0
  62. package/dist/esm/constants.js.map +1 -1
  63. package/dist/esm/constants.mjs +1 -0
  64. package/dist/esm/constants.mjs.map +1 -1
  65. package/dist/esm/constants.native.js +1 -0
  66. package/dist/esm/constants.native.js.map +1 -1
  67. package/dist/esm/createApp.js +2 -1
  68. package/dist/esm/createApp.js.map +1 -1
  69. package/dist/esm/createApp.mjs +2 -1
  70. package/dist/esm/createApp.mjs.map +1 -1
  71. package/dist/esm/router/filterRootHTML.js +4 -4
  72. package/dist/esm/router/filterRootHTML.js.map +1 -1
  73. package/dist/esm/router/filterRootHTML.mjs +4 -4
  74. package/dist/esm/router/filterRootHTML.mjs.map +1 -1
  75. package/dist/esm/router/filterRootHTML.native.js +7 -6
  76. package/dist/esm/router/filterRootHTML.native.js.map +1 -1
  77. package/dist/esm/router/useScreens.js +20 -3
  78. package/dist/esm/router/useScreens.js.map +1 -1
  79. package/dist/esm/router/useScreens.mjs +14 -5
  80. package/dist/esm/router/useScreens.mjs.map +1 -1
  81. package/dist/esm/router/useScreens.native.js +13 -4
  82. package/dist/esm/router/useScreens.native.js.map +1 -1
  83. package/dist/esm/server/ServerContextScript.js +6 -2
  84. package/dist/esm/server/ServerContextScript.js.map +1 -1
  85. package/dist/esm/server/ServerContextScript.mjs +12 -2
  86. package/dist/esm/server/ServerContextScript.mjs.map +1 -1
  87. package/dist/esm/server/ServerContextScript.native.js +12 -2
  88. package/dist/esm/server/ServerContextScript.native.js.map +1 -1
  89. package/dist/esm/server/oneServe.js +4 -2
  90. package/dist/esm/server/oneServe.js.map +2 -2
  91. package/dist/esm/server/oneServe.mjs +3 -2
  92. package/dist/esm/server/oneServe.mjs.map +1 -1
  93. package/dist/esm/server/oneServe.native.js +3 -2
  94. package/dist/esm/server/oneServe.native.js.map +1 -1
  95. package/dist/esm/server-render.js +9 -1
  96. package/dist/esm/server-render.js.map +1 -1
  97. package/dist/esm/server-render.mjs +9 -1
  98. package/dist/esm/server-render.mjs.map +1 -1
  99. package/dist/esm/server-render.native.js +12 -3
  100. package/dist/esm/server-render.native.js.map +1 -1
  101. package/dist/esm/vite/one.js +6 -3
  102. package/dist/esm/vite/one.js.map +1 -1
  103. package/dist/esm/vite/one.mjs +5 -1
  104. package/dist/esm/vite/one.mjs.map +1 -1
  105. package/dist/esm/vite/one.native.js +11 -2
  106. package/dist/esm/vite/one.native.js.map +1 -1
  107. package/package.json +9 -9
  108. package/src/cli/build.ts +54 -6
  109. package/src/cli/buildPage.ts +104 -12
  110. package/src/constants.ts +1 -0
  111. package/src/createApp.tsx +1 -0
  112. package/src/router/filterRootHTML.ts +14 -15
  113. package/src/router/useScreens.tsx +27 -4
  114. package/src/server/ServerContextScript.tsx +13 -1
  115. package/src/server/oneServe.ts +3 -2
  116. package/src/server-render.tsx +29 -2
  117. package/src/types.ts +9 -0
  118. package/src/vite/one.ts +9 -3
  119. package/src/vite/types.ts +27 -0
  120. package/types/cli/build.d.ts.map +1 -1
  121. package/types/cli/buildPage.d.ts +1 -1
  122. package/types/cli/buildPage.d.ts.map +1 -1
  123. package/types/constants.d.ts.map +1 -1
  124. package/types/createApp.d.ts.map +1 -1
  125. package/types/router/filterRootHTML.d.ts.map +1 -1
  126. package/types/router/useScreens.d.ts.map +1 -1
  127. package/types/server/ServerContextScript.d.ts.map +1 -1
  128. package/types/server/oneServe.d.ts.map +1 -1
  129. package/types/server-render.d.ts +14 -2
  130. package/types/server-render.d.ts.map +1 -1
  131. package/types/types.d.ts +9 -0
  132. package/types/types.d.ts.map +1 -1
  133. package/types/vite/one.d.ts.map +1 -1
  134. package/types/vite/types.d.ts +26 -0
  135. package/types/vite/types.d.ts.map +1 -1
@@ -23,7 +23,11 @@ export async function buildPage(
23
23
  preloads: string[],
24
24
  allCSS: string[],
25
25
  routePreloads: Record<string, string>,
26
- allCSSContents?: string[]
26
+ allCSSContents?: string[],
27
+ criticalPreloads?: string[],
28
+ deferredPreloads?: string[],
29
+ useAfterLCP?: boolean,
30
+ useAfterLCPAggressive?: boolean
27
31
  ): Promise<One.RouteBuildInfo> {
28
32
  const render = await getRender(serverEntry)
29
33
  const htmlPath = `${path.endsWith('/') ? `${removeTrailingSlash(path)}/index` : path}.html`
@@ -65,13 +69,19 @@ export async function buildPage(
65
69
  await FSExtra.writeFile(join(clientDir, preloadPath), preloadContent)
66
70
 
67
71
  // Generate CSS preload file with prefetch (on hover) and inject (on navigation) functions
72
+ // Deduplicate CSS URLs to avoid loading the same file multiple times
73
+ const uniqueCSS = [...new Set(allCSS)]
68
74
  const cssPreloadContent = `
69
75
  const CSS_TIMEOUT = 1000
70
- const cssUrls = ${JSON.stringify(allCSS)}
76
+ const cssUrls = ${JSON.stringify(uniqueCSS)}
77
+
78
+ // Global cache for loaded CSS - avoids DOM queries and tracks across navigations
79
+ const loaded = (window.__oneLoadedCSS ||= new Set())
71
80
 
72
81
  // Prefetch CSS without applying - called on link hover
73
82
  export function prefetchCSS() {
74
83
  cssUrls.forEach(href => {
84
+ if (loaded.has(href)) return
75
85
  if (document.querySelector(\`link[href="\${href}"]\`)) return
76
86
  const link = document.createElement('link')
77
87
  link.rel = 'prefetch'
@@ -84,21 +94,31 @@ export function prefetchCSS() {
84
94
  // Inject CSS to apply styles - called on actual navigation
85
95
  export function injectCSS() {
86
96
  return Promise.all(cssUrls.map(href => {
97
+ // Skip if already loaded
98
+ if (loaded.has(href)) return Promise.resolve()
87
99
  // Remove any prefetch link for this href
88
100
  const prefetchLink = document.querySelector(\`link[rel="prefetch"][href="\${href}"]\`)
89
101
  if (prefetchLink) prefetchLink.remove()
90
- // Skip if stylesheet already exists
91
- if (document.querySelector(\`link[rel="stylesheet"][href="\${href}"]\`)) return Promise.resolve()
102
+ // Skip if stylesheet already exists in DOM
103
+ if (document.querySelector(\`link[rel="stylesheet"][href="\${href}"]\`)) {
104
+ loaded.add(href)
105
+ return Promise.resolve()
106
+ }
92
107
  return new Promise(resolve => {
93
108
  const link = document.createElement('link')
94
109
  link.rel = 'stylesheet'
95
110
  link.href = href
96
- link.onload = link.onerror = resolve
97
- document.head.appendChild(link)
98
- setTimeout(() => {
111
+ const timeoutId = setTimeout(() => {
99
112
  console.warn('[one] CSS load timeout:', href)
113
+ loaded.add(href)
100
114
  resolve()
101
115
  }, CSS_TIMEOUT)
116
+ link.onload = link.onerror = () => {
117
+ clearTimeout(timeoutId)
118
+ loaded.add(href)
119
+ resolve()
120
+ }
121
+ document.head.appendChild(link)
102
122
  })
103
123
  }))
104
124
  }
@@ -134,9 +154,16 @@ if (typeof document === 'undefined') globalThis.document = {}
134
154
  globalThis['__vxrnresetState']?.()
135
155
 
136
156
  if (foundRoute.type === 'ssg') {
137
- const html = await render({
157
+ // Aggressive mode: only modulepreload critical scripts, skip deferred to prevent network saturation
158
+ // Regular after-lcp mode: modulepreload all scripts for parallel downloads, defer execution
159
+ // Default: all scripts load normally
160
+ const renderPreloads = criticalPreloads || preloads
161
+ const renderDeferredPreloads = useAfterLCPAggressive ? [] : deferredPreloads
162
+
163
+ let html = await render({
138
164
  path,
139
- preloads,
165
+ preloads: renderPreloads,
166
+ deferredPreloads: renderDeferredPreloads,
140
167
  loaderProps,
141
168
  loaderData,
142
169
  css: allCSS,
@@ -144,6 +171,13 @@ if (typeof document === 'undefined') globalThis.document = {}
144
171
  mode: 'ssg',
145
172
  routePreloads,
146
173
  })
174
+
175
+ // Apply after-LCP script loading if enabled
176
+ // Load all preloads (not just critical) to ensure good TTI after first paint
177
+ if (useAfterLCP) {
178
+ html = applyAfterLCPScriptLoad(html, preloads)
179
+ }
180
+
147
181
  await outputFile(htmlOutPath, html)
148
182
  } else if (foundRoute.type === 'spa') {
149
183
  // Generate CSS - either inline styles or link tags
@@ -154,13 +188,22 @@ if (typeof document === 'undefined') globalThis.document = {}
154
188
  .join('\n')
155
189
  : allCSS.map((file) => ` <link rel="stylesheet" href=${file} />`).join('\n')
156
190
 
191
+ // Use separated preloads if available
192
+ const criticalScripts = (criticalPreloads || preloads)
193
+ .map((preload) => ` <script type="module" src="${preload}"></script>`)
194
+ .join('\n')
195
+
196
+ // Non-critical scripts as modulepreload hints only
197
+ const deferredLinks = (deferredPreloads || [])
198
+ .map((preload) => ` <link rel="modulepreload" fetchPriority="low" href="${preload}"/>`)
199
+ .join('\n')
200
+
157
201
  await outputFile(
158
202
  htmlOutPath,
159
203
  `<html><head>
160
204
  ${constants.getSpaHeaderElements({ serverContext: { loaderProps, loaderData } })}
161
- ${preloads
162
- .map((preload) => ` <script type="module" src="${preload}"></script>`)
163
- .join('\n')}
205
+ ${criticalScripts}
206
+ ${deferredLinks}
164
207
  ${cssOutput}
165
208
  </head></html>`
166
209
  )
@@ -202,6 +245,8 @@ params:\n\n${JSON.stringify(params || null, null, 2)}`
202
245
  params,
203
246
  path,
204
247
  preloads,
248
+ criticalPreloads,
249
+ deferredPreloads,
205
250
  }
206
251
  }
207
252
 
@@ -234,3 +279,50 @@ async function getRender(serverEntry: string) {
234
279
  function removeTrailingSlash(path: string) {
235
280
  return path.endsWith('/') ? path.slice(0, path.length - 1) : path
236
281
  }
282
+
283
+ /**
284
+ * Transforms HTML to delay script execution until after first paint.
285
+ * Keeps modulepreload links so critical scripts download in parallel.
286
+ * Removes async script tags and adds a loader that executes scripts after paint.
287
+ */
288
+ function applyAfterLCPScriptLoad(html: string, preloads: string[]): string {
289
+ // Remove all <script type="module" ... async> tags (prevents immediate execution)
290
+ // Keep modulepreload links so critical scripts download in parallel
291
+ html = html.replace(/<script\s+type="module"[^>]*async[^>]*><\/script>/gi, '')
292
+
293
+ // Create the loader script
294
+ // Nested setTimeout yields to event loop multiple times, letting browser settle before loading scripts
295
+ const loaderScript = `
296
+ <script>
297
+ (function() {
298
+ var scripts = ${JSON.stringify(preloads)};
299
+ function loadScripts() {
300
+ scripts.forEach(function(src) {
301
+ var script = document.createElement('script');
302
+ script.type = 'module';
303
+ script.src = src;
304
+ document.head.appendChild(script);
305
+ });
306
+ }
307
+ function waitIdle(n) {
308
+ if (n <= 0) {
309
+ requestAnimationFrame(function() {
310
+ requestAnimationFrame(loadScripts);
311
+ });
312
+ return;
313
+ }
314
+ setTimeout(function() {
315
+ setTimeout(function() {
316
+ waitIdle(n - 1);
317
+ }, 0);
318
+ }, 0);
319
+ }
320
+ waitIdle(5);
321
+ })();
322
+ </script>`
323
+
324
+ // Insert the loader script before </head>
325
+ html = html.replace('</head>', `${loaderScript}</head>`)
326
+
327
+ return html
328
+ }
package/src/constants.ts CHANGED
@@ -28,4 +28,5 @@ export const getSpaHeaderElements = ({
28
28
  <script>globalThis['global'] = globalThis</script>
29
29
  <script>globalThis['__vxrnIsSPA'] = true</script>
30
30
  <script>globalThis["${SERVER_CONTEXT_KEY}"] = ${JSON.stringify(serverContext)}</script>
31
+ <script>globalThis.__oneLoadedCSS = new Set(${JSON.stringify(serverContext.css || [])})</script>
31
32
  `
package/src/createApp.tsx CHANGED
@@ -73,6 +73,7 @@ export function createApp(options: CreateAppProps) {
73
73
 
74
74
  let html = await renderToString(rootElement, {
75
75
  preloads: props.preloads,
76
+ deferredPreloads: props.deferredPreloads,
76
77
  })
77
78
 
78
79
  try {
@@ -1,4 +1,4 @@
1
- import { cloneElement, isValidElement } from 'react'
1
+ import { isValidElement } from 'react'
2
2
 
3
3
  type Props = Record<string, any>
4
4
 
@@ -36,12 +36,8 @@ export function filterRootHTML(el: React.ReactNode): FoundRootHTML {
36
36
  const { type, props } = reactElement
37
37
 
38
38
  if (type === 'html') {
39
- if (
40
- reactElement.props &&
41
- typeof reactElement.props === 'object' &&
42
- 'children' in reactElement.props
43
- ) {
44
- const { children, ...restProps } = reactElement.props
39
+ if (props && typeof props === 'object' && 'children' in props) {
40
+ const { children, ...restProps } = props
45
41
  htmlProps = restProps
46
42
  return traverse(children as React.ReactNode)
47
43
  }
@@ -54,13 +50,13 @@ export function filterRootHTML(el: React.ReactNode): FoundRootHTML {
54
50
  }
55
51
 
56
52
  if (type === 'body') {
57
- if (
58
- reactElement.props &&
59
- typeof reactElement.props === 'object' &&
60
- 'children' in reactElement.props
61
- ) {
62
- const { children, ...restProps } = reactElement.props
53
+ if (props && typeof props === 'object' && 'children' in props) {
54
+ const { children, ...restProps } = props
63
55
  bodyProps = restProps
56
+ if (process.env.TAMAGUI_TARGET === 'native') {
57
+ // must traverse children so nested HTML elements (e.g. <div>) get filtered on native
58
+ return traverse(children as React.ReactNode)
59
+ }
64
60
  return children as React.ReactNode
65
61
  }
66
62
  return null
@@ -72,8 +68,11 @@ export function filterRootHTML(el: React.ReactNode): FoundRootHTML {
72
68
  typeof element.type === 'string' &&
73
69
  element.type.toLowerCase() === element.type
74
70
  ) {
75
- // filter out things like <meta /> etc on native
76
- // because it could just be thown in <html> or a fragment to be hoisted on web
71
+ // filter out HTML elements on native (e.g. <div>, <meta>)
72
+ // preserve children so <div><Slot/></div> renders <Slot/> instead of nothing
73
+ if (element.props && typeof element.props === 'object' && 'children' in element.props) {
74
+ return traverse(element.props.children as React.ReactNode)
75
+ }
77
76
  return null
78
77
  }
79
78
  }
@@ -29,6 +29,22 @@ import { sortRoutesWithInitial } from './sortRoutes'
29
29
  // do this hack.
30
30
  export const { Screen, Group } = createNavigatorFactory({} as any)()
31
31
 
32
+ // Cache inline CSS elements at module load (before React hydrates).
33
+ // Reads CSS content from SSR'd <style> elements and creates matching JSX
34
+ // so hydration sees identical content without 100KB+ JSON payload.
35
+ const cachedInlineCSSElements: React.ReactNode[] =
36
+ typeof document !== 'undefined'
37
+ ? Array.from(document.querySelectorAll<HTMLStyleElement>('style[id^="__one_css_"]')).map(
38
+ (el, i) => (
39
+ <style
40
+ key={`inline-css-${i}`}
41
+ id={el.id}
42
+ dangerouslySetInnerHTML={{ __html: el.innerHTML }}
43
+ />
44
+ )
45
+ )
46
+ : []
47
+
32
48
  export type ScreenProps<
33
49
  TOptions extends Record<string, any> = Record<string, any>,
34
50
  State extends NavigationState = NavigationState,
@@ -205,10 +221,17 @@ export function getQualifiedRouteComponent(value: RouteNode) {
205
221
  __html: `globalThis['global'] = globalThis`,
206
222
  }}
207
223
  />
208
- {serverContext?.cssContents
209
- ? serverContext.cssContents.map((content, i) =>
210
- content ? <style key={i} dangerouslySetInnerHTML={{ __html: content }} /> : null
211
- )
224
+ {serverContext?.cssContents?.length || serverContext?.cssInlineCount
225
+ ? // Inline CSS: SSR renders fresh, client uses cached elements from module load
226
+ serverContext?.cssContents
227
+ ? serverContext.cssContents.map((content, i) => (
228
+ <style
229
+ key={`inline-css-${i}`}
230
+ id={`__one_css_${i}`}
231
+ dangerouslySetInnerHTML={{ __html: content }}
232
+ />
233
+ ))
234
+ : cachedInlineCSSElements
212
235
  : serverContext?.css?.map((file) => <link key={file} rel="stylesheet" href={file} />)}
213
236
  <ServerContextScript />
214
237
  {headChildren}
@@ -19,6 +19,17 @@ export function ServerContextScript() {
19
19
 
20
20
  if (process.env.VITE_ENVIRONMENT === 'ssr') {
21
21
  const context = useServerContext()
22
+ const cssUrls = context?.css || []
23
+
24
+ // Strip cssContents from JSON payload - we'll read it from DOM instead.
25
+ // This avoids duplicating 100KB+ of CSS as JSON in the HTML.
26
+ // The CSSPrehydrateScript reads the actual <style> elements' innerHTML
27
+ // and stores them in globalThis.__oneCSSContents for hydration matching.
28
+ const { cssContents, ...restContext } = context || {}
29
+ const clientContext = {
30
+ ...restContext,
31
+ cssInlineCount: cssContents?.length || 0,
32
+ }
22
33
 
23
34
  return (
24
35
  <script
@@ -29,9 +40,10 @@ export function ServerContextScript() {
29
40
  dangerouslySetInnerHTML={{
30
41
  __html: `
31
42
  globalThis["${SERVER_CONTEXT_KEY}"] = ${JSON.stringify({
32
- ...context,
43
+ ...clientContext,
33
44
  postRenderData: SERVER_CONTEXT_POST_RENDER_STRING,
34
45
  })};
46
+ globalThis.__oneLoadedCSS = new Set(${JSON.stringify(cssUrls)});
35
47
  `,
36
48
  }}
37
49
  />
@@ -111,7 +111,6 @@ export async function oneServe(oneOptions: One.PluginOptions, buildInfo: One.Bui
111
111
  try {
112
112
  const exported = await import(toAbsolute(buildInfo.serverJsPath))
113
113
  const loaderData = await exported.loader?.(loaderProps)
114
- const preloads = buildInfo.preloads
115
114
 
116
115
  const headers = new Headers()
117
116
  headers.set('content-type', 'text/html')
@@ -121,7 +120,9 @@ export async function oneServe(oneOptions: One.PluginOptions, buildInfo: One.Bui
121
120
  loaderData,
122
121
  loaderProps,
123
122
  path: loaderProps?.path || '/',
124
- preloads,
123
+ // Use separated preloads for optimal loading
124
+ preloads: buildInfo.criticalPreloads || buildInfo.preloads,
125
+ deferredPreloads: buildInfo.deferredPreloads,
125
126
  css: buildInfo.css,
126
127
  cssContents: buildInfo.cssContents,
127
128
  })
@@ -1,11 +1,38 @@
1
1
  import ReactDOMServer from 'react-dom/server.browser'
2
2
 
3
- export const renderToString = async (app: React.ReactElement, options: { preloads?: string[] }) => {
3
+ export type RenderToStringOptions = {
4
+ /**
5
+ * Critical scripts that need to execute immediately (will use async).
6
+ * These are added to bootstrapModules and generate both modulepreload links and async script tags.
7
+ * Keep this list minimal (typically: setupClient, one-entry, page entry).
8
+ */
9
+ preloads?: string[]
10
+
11
+ /**
12
+ * Non-critical scripts that can wait (will only be modulepreload hints).
13
+ * These only generate <link rel="modulepreload"> tags and are loaded when imported.
14
+ * Use this for component libraries, utilities, and other non-essential modules.
15
+ */
16
+ deferredPreloads?: string[]
17
+ }
18
+
19
+ export const renderToString = async (app: React.ReactElement, options: RenderToStringOptions) => {
4
20
  const readableStream = await ReactDOMServer.renderToReadableStream(app, {
21
+ // Only pass critical scripts to bootstrapModules
22
+ // These generate both modulepreload links AND async script tags
5
23
  bootstrapModules: options.preloads,
6
24
  })
7
25
  await readableStream.allReady
8
- const out = await streamToString(readableStream)
26
+ let out = await streamToString(readableStream)
27
+
28
+ // Add non-critical modulepreload links to head (just hints, no script execution)
29
+ if (options.deferredPreloads?.length) {
30
+ const modulepreloadLinks = options.deferredPreloads
31
+ .map((src) => `<link rel="modulepreload" fetchPriority="low" href="${src}"/>`)
32
+ .join('')
33
+ out = out.replace('</head>', `${modulepreloadLinks}</head>`)
34
+ }
35
+
9
36
  return out
10
37
  }
11
38
 
package/src/types.ts CHANGED
@@ -19,7 +19,16 @@ export type LoaderProps<Params extends Object = Record<string, string | string[]
19
19
  export type RenderAppProps = {
20
20
  mode: One.RouteRenderMode
21
21
  path: string
22
+ /**
23
+ * Critical scripts that need to execute immediately (will use async).
24
+ * These generate both modulepreload links and async script tags.
25
+ */
22
26
  preloads?: string[]
27
+ /**
28
+ * Non-critical scripts that can wait (will only be modulepreload hints).
29
+ * These only generate <link rel="modulepreload"> tags and load when imported.
30
+ */
31
+ deferredPreloads?: string[]
23
32
  css?: string[]
24
33
  /** When inlineLayoutCSS is enabled, this contains the actual CSS content to inline */
25
34
  cssContents?: string[]
package/src/vite/one.ts CHANGED
@@ -204,9 +204,15 @@ export function one(options: One.PluginOptions = {}): PluginOption {
204
204
  return
205
205
  }
206
206
 
207
- tsConfigPathsPlugin = tsconfigPaths(
208
- pathsConfig && typeof pathsConfig === 'object' ? pathsConfig : {}
209
- )
207
+ const skipDotDirs = (dir: string) => {
208
+ const name = dir.split('/').pop() || ''
209
+ return name.startsWith('.')
210
+ }
211
+
212
+ tsConfigPathsPlugin = tsconfigPaths({
213
+ skip: skipDotDirs,
214
+ ...(pathsConfig && typeof pathsConfig === 'object' ? pathsConfig : {}),
215
+ })
210
216
  },
211
217
 
212
218
  configResolved() {},
package/src/vite/types.ts CHANGED
@@ -344,6 +344,26 @@ export namespace One {
344
344
  */
345
345
  inlineLayoutCSS?: boolean
346
346
 
347
+ /**
348
+ * @experimental
349
+ * Controls how scripts are loaded for improved performance.
350
+ *
351
+ * - `'defer-non-critical'`: Critical scripts (framework entry, page, layouts) load immediately.
352
+ * Non-critical scripts (component imports, utilities) become modulepreload hints only,
353
+ * reducing network/CPU contention.
354
+ *
355
+ * - `'after-lcp'`: Scripts download immediately via modulepreload but execution is deferred
356
+ * until after first paint using double requestAnimationFrame. This allows the browser to
357
+ * paint the SSR content before executing JavaScript. Only applies to SSG pages.
358
+ *
359
+ * - `'after-lcp-aggressive'`: Only modulepreloads critical scripts (entry, layouts).
360
+ * Non-critical scripts have no modulepreload hints, reducing network saturation.
361
+ * Best for pages with many chunks or slow networks.
362
+ *
363
+ * @default undefined (all scripts load with async)
364
+ */
365
+ experimental_scriptLoading?: 'defer-non-critical' | 'after-lcp' | 'after-lcp-aggressive'
366
+
347
367
  /**
348
368
  * Generate a sitemap.xml file during build.
349
369
  *
@@ -445,7 +465,12 @@ export namespace One {
445
465
  serverJsPath: string
446
466
  params: Object
447
467
  loaderData: any
468
+ /** All preloads (for backwards compatibility) */
448
469
  preloads: string[]
470
+ /** Critical preloads that load immediately with async */
471
+ criticalPreloads?: string[]
472
+ /** Non-critical preloads that are modulepreload hints only */
473
+ deferredPreloads?: string[]
449
474
  css: string[]
450
475
  /** When inlineLayoutCSS is enabled, contains the actual CSS content */
451
476
  cssContents?: string[]
@@ -455,6 +480,8 @@ export namespace One {
455
480
  css?: string[]
456
481
  /** When inlineLayoutCSS is enabled, this contains the actual CSS content to inline */
457
482
  cssContents?: string[]
483
+ /** Number of inline CSS entries - used for hydration matching when cssContents is stripped */
484
+ cssInlineCount?: number
458
485
  postRenderData?: any
459
486
  loaderData?: any
460
487
  loaderProps?: any
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAoCA,wBAAsB,KAAK,CAAC,IAAI,EAAE;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAA;CACrC,iBAqmBA"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/cli/build.ts"],"names":[],"mappings":"AAoCA,wBAAsB,KAAK,CAAC,IAAI,EAAE;IAChC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAAA;CACrC,iBAqpBA"}
@@ -1,3 +1,3 @@
1
1
  import type { One, RouteInfo } from '../vite/types';
2
- export declare function buildPage(serverEntry: string, path: string, relativeId: string, params: any, foundRoute: RouteInfo<string>, clientManifestEntry: any, staticDir: string, clientDir: string, builtMiddlewares: Record<string, string>, serverJsPath: string, preloads: string[], allCSS: string[], routePreloads: Record<string, string>, allCSSContents?: string[]): Promise<One.RouteBuildInfo>;
2
+ export declare function buildPage(serverEntry: string, path: string, relativeId: string, params: any, foundRoute: RouteInfo<string>, clientManifestEntry: any, staticDir: string, clientDir: string, builtMiddlewares: Record<string, string>, serverJsPath: string, preloads: string[], allCSS: string[], routePreloads: Record<string, string>, allCSSContents?: string[], criticalPreloads?: string[], deferredPreloads?: string[], useAfterLCP?: boolean, useAfterLCPAggressive?: boolean): Promise<One.RouteBuildInfo>;
3
3
  //# sourceMappingURL=buildPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildPage.d.ts","sourceRoot":"","sources":["../../src/cli/buildPage.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAInD,wBAAsB,SAAS,CAC7B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,GAAG,EACX,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,EAC7B,mBAAmB,EAAE,GAAG,EACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACxC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,MAAM,EAAE,EAChB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,cAAc,CAAC,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAmL7B"}
1
+ {"version":3,"file":"buildPage.d.ts","sourceRoot":"","sources":["../../src/cli/buildPage.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAInD,wBAAsB,SAAS,CAC7B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,GAAG,EACX,UAAU,EAAE,SAAS,CAAC,MAAM,CAAC,EAC7B,mBAAmB,EAAE,GAAG,EACxB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACxC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAAE,EAClB,MAAM,EAAE,MAAM,EAAE,EAChB,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,cAAc,CAAC,EAAE,MAAM,EAAE,EACzB,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAC3B,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAC3B,WAAW,CAAC,EAAE,OAAO,EACrB,qBAAqB,CAAC,EAAE,OAAO,GAC9B,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CA4N7B"}
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC,eAAO,MAAM,WAAW,SAA2E,CAAA;AACnG,eAAO,MAAM,WAAW,SAA2E,CAAA;AACnG,eAAO,MAAM,QAAQ,SAA0C,CAAA;AAE/D,eAAO,MAAM,SAAS,QAA4E,CAAA;AAElG,eAAO,MAAM,0BAA0B,oBAAoB,CAAA;AAC3D,eAAO,MAAM,8BAA8B,0BAAwC,CAAA;AACnF,eAAO,MAAM,uBAAuB,QAA6C,CAAA;AACjF,eAAO,MAAM,iBAAiB,QAA+C,CAAA;AAE7E,eAAO,MAAM,kBAAkB,QAA6B,CAAA;AAC5D,eAAO,MAAM,sBAAsB,QAAiC,CAAA;AAGpE,eAAO,MAAM,qBAAqB,wBAAwB,CAAA;AAC1D,eAAO,MAAM,oBAAoB,oCAAyC,CAAA;AAE1E,eAAO,MAAM,kBAAkB,2BAA2B,CAAA;AAE1D,eAAO,MAAM,oBAAoB,GAAI,qBAElC;IACD,aAAa,CAAC,EAAE,GAAG,CAAC,aAAa,CAAA;CAC7B,WAIL,CAAA"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC,eAAO,MAAM,WAAW,SAA2E,CAAA;AACnG,eAAO,MAAM,WAAW,SAA2E,CAAA;AACnG,eAAO,MAAM,QAAQ,SAA0C,CAAA;AAE/D,eAAO,MAAM,SAAS,QAA4E,CAAA;AAElG,eAAO,MAAM,0BAA0B,oBAAoB,CAAA;AAC3D,eAAO,MAAM,8BAA8B,0BAAwC,CAAA;AACnF,eAAO,MAAM,uBAAuB,QAA6C,CAAA;AACjF,eAAO,MAAM,iBAAiB,QAA+C,CAAA;AAE7E,eAAO,MAAM,kBAAkB,QAA6B,CAAA;AAC5D,eAAO,MAAM,sBAAsB,QAAiC,CAAA;AAGpE,eAAO,MAAM,qBAAqB,wBAAwB,CAAA;AAC1D,eAAO,MAAM,oBAAoB,oCAAyC,CAAA;AAE1E,eAAO,MAAM,kBAAkB,2BAA2B,CAAA;AAE1D,eAAO,MAAM,oBAAoB,GAAI,qBAElC;IACD,aAAa,CAAC,EAAE,GAAG,CAAC,aAAa,CAAA;CAC7B,WAKL,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"createApp.d.ts","sourceRoot":"","sources":["../src/createApp.tsx"],"names":[],"mappings":"AAAA,OAAO,SAAS,CAAA;AAQhB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAK7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9C,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,GAAG,CAAC,KAAK,CAAA;CAClB,CAAA;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,cAAc;;oBAIrB,cAAc;EAwIzC"}
1
+ {"version":3,"file":"createApp.d.ts","sourceRoot":"","sources":["../src/createApp.tsx"],"names":[],"mappings":"AAAA,OAAO,SAAS,CAAA;AAQhB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAK7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;IAC9C,UAAU,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,GAAG,CAAC,KAAK,CAAA;CAClB,CAAA;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,cAAc;;oBAIrB,cAAc;EAyIzC"}
@@ -1 +1 @@
1
- {"version":3,"file":"filterRootHTML.d.ts","sourceRoot":"","sources":["../../src/router/filterRootHTML.ts"],"names":[],"mappings":"AAEA,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAEhC,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAA;IAC5B,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC,YAAY,CAAA;CAC1B,CAAA;AAED;;;;;;;;GAQG;AAEH,wBAAgB,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,SAAS,GAAG,aAAa,CAyEjE"}
1
+ {"version":3,"file":"filterRootHTML.d.ts","sourceRoot":"","sources":["../../src/router/filterRootHTML.ts"],"names":[],"mappings":"AAEA,KAAK,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;AAEhC,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAA;IAC5B,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,SAAS,CAAC,EAAE,KAAK,CAAA;IACjB,IAAI,CAAC,EAAE,KAAK,CAAC,YAAY,CAAA;CAC1B,CAAA;AAED;;;;;;;;GAQG;AAEH,wBAAgB,cAAc,CAAC,EAAE,EAAE,KAAK,CAAC,SAAS,GAAG,aAAa,CAwEjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"useScreens.d.ts","sourceRoot":"","sources":["../../src/router/useScreens.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,aAAa,EACb,SAAS,EACT,eAAe,EAChB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAgC,MAAM,OAAO,CAAA;AASpD,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,SAAS,CAAA;AAKhB,eAAO,MAAQ,MAAM,OAAE,KAAK,KAAwC,CAAA;AAEpE,MAAM,MAAM,WAAW,CACrB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1D,KAAK,SAAS,eAAe,GAAG,eAAe,EAC/C,QAAQ,SAAS,YAAY,GAAG,YAAY,IAC1C;IACF,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,OAAO,CAAC,EAAE,QAAQ,CAAA;IAElB,SAAS,CAAC,EACN,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,GAChC,CAAC,CAAC,IAAI,EAAE;QACN,KAAK,EAAE,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;QACvC,UAAU,EAAE,GAAG,CAAA;KAChB,KAAK,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE3C,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,KAAK,MAAM,GAAG,SAAS,CAAA;CAC7E,CAAA;AAgED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GACnC,KAAK,CAAC,SAAS,EAAE,CAYnB;AA4BD,mFAAmF;AACnF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,0GAyH1D;AAED,oGAAoG;AACpG,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,CAAC,IAU/D;aAAmC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAAE,YAoBjE"}
1
+ {"version":3,"file":"useScreens.d.ts","sourceRoot":"","sources":["../../src/router/useScreens.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,eAAe,EACf,aAAa,EACb,SAAS,EACT,eAAe,EAChB,MAAM,0BAA0B,CAAA;AACjC,OAAO,KAAgC,MAAM,OAAO,CAAA;AASpD,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,SAAS,CAAA;AAKhB,eAAO,MAAQ,MAAM,OAAE,KAAK,KAAwC,CAAA;AAkBpE,MAAM,MAAM,WAAW,CACrB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1D,KAAK,SAAS,eAAe,GAAG,eAAe,EAC/C,QAAQ,SAAS,YAAY,GAAG,YAAY,IAC1C;IACF,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACnC,OAAO,CAAC,EAAE,QAAQ,CAAA;IAElB,SAAS,CAAC,EACN,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,GAChC,CAAC,CAAC,IAAI,EAAE;QACN,KAAK,EAAE,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;QACvC,UAAU,EAAE,GAAG,CAAA;KAChB,KAAK,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAA;IAE3C,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,KAAK,MAAM,GAAG,SAAS,CAAA;CAC7E,CAAA;AAgED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,EAAE,EACpB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAE,GACnC,KAAK,CAAC,SAAS,EAAE,CAYnB;AA4BD,mFAAmF;AACnF,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,SAAS,0GAgI1D;AAED,oGAAoG;AACpG,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,UAAU,CAAC,IAU/D;aAAmC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC;CAAE,YAoBjE"}
@@ -1 +1 @@
1
- {"version":3,"file":"ServerContextScript.d.ts","sourceRoot":"","sources":["../../src/server/ServerContextScript.tsx"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,mDAqClC"}
1
+ {"version":3,"file":"ServerContextScript.d.ts","sourceRoot":"","sources":["../../src/server/ServerContextScript.tsx"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,mDAiDlC"}
@@ -1 +1 @@
1
- {"version":3,"file":"oneServe.d.ts","sourceRoot":"","sources":["../../src/server/oneServe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAqB,MAAM,MAAM,CAAA;AAoBnD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAKxC,wBAAsB,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,aAAa,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,iBA+ThG"}
1
+ {"version":3,"file":"oneServe.d.ts","sourceRoot":"","sources":["../../src/server/oneServe.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAqB,MAAM,MAAM,CAAA;AAoBnD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAKxC,wBAAsB,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,aAAa,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,GAAG,EAAE,IAAI,iBAgUhG"}
@@ -1,4 +1,16 @@
1
- export declare const renderToString: (app: React.ReactElement, options: {
1
+ export type RenderToStringOptions = {
2
+ /**
3
+ * Critical scripts that need to execute immediately (will use async).
4
+ * These are added to bootstrapModules and generate both modulepreload links and async script tags.
5
+ * Keep this list minimal (typically: setupClient, one-entry, page entry).
6
+ */
2
7
  preloads?: string[];
3
- }) => Promise<string>;
8
+ /**
9
+ * Non-critical scripts that can wait (will only be modulepreload hints).
10
+ * These only generate <link rel="modulepreload"> tags and are loaded when imported.
11
+ * Use this for component libraries, utilities, and other non-essential modules.
12
+ */
13
+ deferredPreloads?: string[];
14
+ };
15
+ export declare const renderToString: (app: React.ReactElement, options: RenderToStringOptions) => Promise<string>;
4
16
  //# sourceMappingURL=server-render.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.tsx"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,GAAU,KAAK,KAAK,CAAC,YAAY,EAAE,SAAS;IAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,oBAO7F,CAAA"}
1
+ {"version":3,"file":"server-render.d.ts","sourceRoot":"","sources":["../src/server-render.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IAEnB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;CAC5B,CAAA;AAED,eAAO,MAAM,cAAc,GAAU,KAAK,KAAK,CAAC,YAAY,EAAE,SAAS,qBAAqB,oBAkB3F,CAAA"}
package/types/types.d.ts CHANGED
@@ -13,7 +13,16 @@ export type LoaderProps<Params extends Object = Record<string, string | string[]
13
13
  export type RenderAppProps = {
14
14
  mode: One.RouteRenderMode;
15
15
  path: string;
16
+ /**
17
+ * Critical scripts that need to execute immediately (will use async).
18
+ * These generate both modulepreload links and async script tags.
19
+ */
16
20
  preloads?: string[];
21
+ /**
22
+ * Non-critical scripts that can wait (will only be modulepreload hints).
23
+ * These only generate <link rel="modulepreload"> tags and load when imported.
24
+ */
25
+ deferredPreloads?: string[];
17
26
  css?: string[];
18
27
  /** When inlineLayoutCSS is enabled, this contains the actual CSS content to inline */
19
28
  cssContents?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC,yFAAyF;AACzF,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AAEhF,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;AAExE,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;AAE1E,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAElE,MAAM,MAAM,WAAW,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI;IACnF,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,GAAG,CAAC,eAAe,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;IACd,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,gBAAgB,CAAC,EAAE,GAAG,CAAA;IACtB,UAAU,CAAC,EAAE,GAAG,CAAA;IAChB,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAA;AAEvC,yFAAyF;AACzF,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;AAEhF,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC,CAAA;AAExE,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;AAE1E,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;AAElE,MAAM,MAAM,WAAW,CAAC,MAAM,SAAS,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,IAAI;IACnF,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,GAAG,CAAC,eAAe,CAAA;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;IACd,sFAAsF;IACtF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;IACtB,gBAAgB,CAAC,EAAE,GAAG,CAAA;IACtB,UAAU,CAAC,EAAE,GAAG,CAAA;IAChB,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACvC,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"one.d.ts","sourceRoot":"","sources":["../../src/vite/one.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AAOhD,OAAO,qBAAqB,CAAA;AAW5B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAoBlC,wBAAgB,GAAG,CAAC,OAAO,GAAE,GAAG,CAAC,aAAkB,GAAG,YAAY,CA8jBjE"}
1
+ {"version":3,"file":"one.d.ts","sourceRoot":"","sources":["../../src/vite/one.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAA;AAOhD,OAAO,qBAAqB,CAAA;AAW5B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAA;AAoBlC,wBAAgB,GAAG,CAAC,OAAO,GAAE,GAAG,CAAC,aAAkB,GAAG,YAAY,CAokBjE"}
@@ -300,6 +300,25 @@ export declare namespace One {
300
300
  * @default false
301
301
  */
302
302
  inlineLayoutCSS?: boolean;
303
+ /**
304
+ * @experimental
305
+ * Controls how scripts are loaded for improved performance.
306
+ *
307
+ * - `'defer-non-critical'`: Critical scripts (framework entry, page, layouts) load immediately.
308
+ * Non-critical scripts (component imports, utilities) become modulepreload hints only,
309
+ * reducing network/CPU contention.
310
+ *
311
+ * - `'after-lcp'`: Scripts download immediately via modulepreload but execution is deferred
312
+ * until after first paint using double requestAnimationFrame. This allows the browser to
313
+ * paint the SSR content before executing JavaScript. Only applies to SSG pages.
314
+ *
315
+ * - `'after-lcp-aggressive'`: Only modulepreloads critical scripts (entry, layouts).
316
+ * Non-critical scripts have no modulepreload hints, reducing network saturation.
317
+ * Best for pages with many chunks or slow networks.
318
+ *
319
+ * @default undefined (all scripts load with async)
320
+ */
321
+ experimental_scriptLoading?: 'defer-non-critical' | 'after-lcp' | 'after-lcp-aggressive';
303
322
  /**
304
323
  * Generate a sitemap.xml file during build.
305
324
  *
@@ -390,7 +409,12 @@ export declare namespace One {
390
409
  serverJsPath: string;
391
410
  params: Object;
392
411
  loaderData: any;
412
+ /** All preloads (for backwards compatibility) */
393
413
  preloads: string[];
414
+ /** Critical preloads that load immediately with async */
415
+ criticalPreloads?: string[];
416
+ /** Non-critical preloads that are modulepreload hints only */
417
+ deferredPreloads?: string[];
394
418
  css: string[];
395
419
  /** When inlineLayoutCSS is enabled, contains the actual CSS content */
396
420
  cssContents?: string[];
@@ -399,6 +423,8 @@ export declare namespace One {
399
423
  css?: string[];
400
424
  /** When inlineLayoutCSS is enabled, this contains the actual CSS content to inline */
401
425
  cssContents?: string[];
426
+ /** Number of inline CSS entries - used for hydration matching when cssContents is stripped */
427
+ cssInlineCount?: number;
402
428
  postRenderData?: any;
403
429
  loaderData?: any;
404
430
  loaderProps?: any;