Skip to content

Commit f9c5fab

Browse files
committed
refactor: improve React setup and error handling across runtime
- Refactor React.createElement to explicitly handle key prop separation from props object - Update _jsx and _jsxs fallbacks to use globalThis.jsx/jsxs or createElement as final fallback - Add defensive head element check before appending spinner keyframes style - Simplify fetch cache tags extraction logic and remove redundant array check - Fix timeout option handling to properly coalesce undefined values with OR operator - Enhance module reload error collection to capture and return first error from batch - Add default parameter to getRariVersion function for process.cwd() fallback - Simplify image lazy load state management by removing ref-based visibility tracking - Add useDprDescriptors parameter to buildSrcSetString for device pixel ratio support - Update image srcset generation to support both width and DPR descriptor formats - Improve props extractor and sitemap generator type safety and error handling
1 parent 616fe8d commit f9c5fab

11 files changed

Lines changed: 107 additions & 66 deletions

File tree

crates/rari/src/rsc/rendering/core/js/component_eval_setup.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
if (!globalThis.React) {
33
globalThis.React = {
44
createElement(type, props, ...children) {
5+
const propsWithoutKey = props ? { ...props } : {}
6+
const key = props && Object.hasOwn(props, 'key') ? props.key : null
7+
delete propsWithoutKey.key
8+
59
const element = {
610
$typeof: Symbol.for('react.transitional.element'),
711
type,
8-
props: props || {},
9-
key: props?.key || null,
12+
props: propsWithoutKey,
13+
key,
1014
}
1115
if (children.length > 0)
1216
element.props = { ...element.props, children: children.length === 1 ? children[0] : children }
@@ -19,9 +23,9 @@ if (!globalThis.React) {
1923
}
2024

2125
if (typeof _jsx === 'undefined')
22-
var _jsx = globalThis['~react']?.jsxRuntime?.jsx || (() => null)
26+
var _jsx = globalThis['~react']?.jsxRuntime?.jsx || globalThis.jsx || ((...args) => globalThis.React.createElement(...args))
2327
if (typeof _jsxs === 'undefined')
24-
var _jsxs = globalThis['~react']?.jsxRuntime?.jsxs || (() => null)
28+
var _jsxs = globalThis['~react']?.jsxRuntime?.jsxs || globalThis.jsxs || ((...args) => globalThis.React.createElement(...args))
2529

2630
if (typeof globalThis.jsx === 'undefined') {
2731
globalThis.jsx = function (type, props, key) {
@@ -43,10 +47,13 @@ if (typeof globalThis.jsxs === 'undefined') {
4347

4448
if (typeof globalThis.LoadingSpinner === 'undefined') {
4549
if (typeof document !== 'undefined' && !document.getElementById('spinner-keyframes')) {
46-
const style = document.createElement('style')
47-
style.id = 'spinner-keyframes'
48-
style.textContent = '@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }'
49-
document.head.appendChild(style)
50+
const head = document.head || document.getElementsByTagName('head')[0]
51+
if (head) {
52+
const style = document.createElement('style')
53+
style.id = 'spinner-keyframes'
54+
style.textContent = '@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }'
55+
head.appendChild(style)
56+
}
5057
}
5158

5259
globalThis.LoadingSpinner = function () {

crates/rari/src/runtime/ext/web/init_fetch.js

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,12 @@ async function fetchWithRustCache(input, init, meta) {
131131
options.cacheTTLMs = String(revalidate * 1000)
132132
}
133133

134-
const tags = init?.rari?.tags ?? init?.next?.tags
135-
if (tags && Array.isArray(tags)) {
136-
const validTags = extractValidTags(init)
137-
if (validTags.length > 0) {
138-
options.tags = JSON.stringify(validTags)
139-
}
134+
const validTags = extractValidTags(init)
135+
if (validTags.length > 0) {
136+
options.tags = JSON.stringify(validTags)
140137
}
141138

142-
const timeoutMs = init?.rari?.timeout ?? init?.fetchOptions?.timeout ?? 5000
139+
const timeoutMs = (init?.rari?.timeout ?? init?.fetchOptions?.timeout) || 5000
143140
options.timeout = String(typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : 5000)
144141

145142
try {

crates/rari/src/runtime/module_reload/manager.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,17 +233,28 @@ impl ModuleReloadManager {
233233
handles.push(handle);
234234
}
235235

236+
let mut errors = Vec::new();
236237
for handle in handles {
237238
match handle.await {
238239
Ok(Ok(())) => {}
239240
Ok(Err(e)) => {
240241
error!(error = %e, "Module reload failed in batch");
242+
errors.push(e);
241243
}
242244
Err(e) => {
243245
error!(error = %e, "Batch reload task failed");
246+
errors.push(RariError::module_reload(
247+
ModuleReloadError::RuntimeNotAvailable {
248+
message: format!("Task join error: {}", e),
249+
},
250+
));
244251
}
245252
}
246253
}
254+
255+
if !errors.is_empty() {
256+
return Err(errors.into_iter().next().expect("errors vec is non-empty"));
257+
}
247258
}
248259

249260
Ok(())

packages/deploy/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export function ensureMinimumNodeEngine(packageJson: any, minVersion: string = M
144144
return false
145145
}
146146

147-
export function getRariVersion(cwd: string): string {
147+
export function getRariVersion(cwd: string = process.cwd()): string {
148148
const rariPackageJsonPath = join(cwd, 'node_modules/rari/package.json')
149149

150150
if (!existsSync(rariPackageJsonPath)) {

packages/rari/src/image/Image.tsx

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -101,22 +101,7 @@ function useImageLazyLoad(
101101
loading: 'lazy' | 'eager',
102102
) {
103103
const shouldLoadImmediately = shouldPreload || unoptimized || loading === 'eager'
104-
const prevShouldLoadImmediatelyRef = useRef(shouldLoadImmediately)
105-
106-
const getInitialVisibility = () => {
107-
if (shouldLoadImmediately && !prevShouldLoadImmediatelyRef.current) {
108-
prevShouldLoadImmediatelyRef.current = true
109-
return true
110-
}
111-
112-
return shouldLoadImmediately
113-
}
114-
115-
const [isVisible, setIsVisible] = useState(getInitialVisibility)
116-
117-
useEffect(() => {
118-
prevShouldLoadImmediatelyRef.current = shouldLoadImmediately
119-
}, [shouldLoadImmediately])
104+
const [hasIntersected, setHasIntersected] = useState(false)
120105

121106
useEffect(() => {
122107
if (shouldLoadImmediately)
@@ -130,7 +115,7 @@ function useImageLazyLoad(
130115
(entries) => {
131116
entries.forEach((entry) => {
132117
if (entry.isIntersecting) {
133-
setIsVisible(true)
118+
setHasIntersected(true)
134119
observer.unobserve(img)
135120
}
136121
})
@@ -147,7 +132,7 @@ function useImageLazyLoad(
147132
}
148133
}, [imgRef, shouldLoadImmediately])
149134

150-
return isVisible
135+
return shouldLoadImmediately || hasIntersected
151136
}
152137

153138
function buildImageStyle(
@@ -186,9 +171,17 @@ function buildSrcSetString(
186171
quality: number,
187172
format: ImageFormat | undefined,
188173
loader: ImageProps['loader'],
174+
useDprDescriptors: boolean = false,
189175
): string {
190-
if (loader)
176+
if (loader) {
177+
if (useDprDescriptors)
178+
return sizesArray.map((w, i) => `${loader({ src: finalSrc, width: w, quality })} ${i + 1}x`).join(', ')
179+
191180
return sizesArray.map(w => `${loader({ src: finalSrc, width: w, quality })} ${w}w`).join(', ')
181+
}
182+
183+
if (useDprDescriptors)
184+
return sizesArray.map((w, i) => `${buildImageUrl(finalSrc, w, quality, format)} ${i + 1}x`).join(', ')
192185

193186
return sizesArray.map(w => `${buildImageUrl(finalSrc, w, quality, format)} ${w}w`).join(', ')
194187
}
@@ -290,18 +283,21 @@ function OptimizedImage({
290283
isVisible: boolean
291284
}) {
292285
const defaultWidth = imgWidth || 1920
293-
const sizesArray = imgWidth ? [imgWidth] : DEFAULT_DEVICE_SIZES
286+
const hasFixedWidth = !!imgWidth
287+
const sizesArray = hasFixedWidth ? [imgWidth, imgWidth * 2, imgWidth * 3] : DEFAULT_DEVICE_SIZES
288+
294289
const mainSrc = loader
295290
? loader({ src: finalSrc, width: defaultWidth, quality })
296291
: buildImageUrl(finalSrc, defaultWidth, quality)
297-
const shouldUseSrcSet = !imgWidth || sizesArray.length > 1 || sizesArray[0] !== defaultWidth
292+
293+
const shouldUseSrcSet = true
298294

299295
const imgElement = (
300296
<img
301297
ref={imgRef}
302298
src={isVisible ? mainSrc : undefined}
303-
srcSet={isVisible && shouldUseSrcSet ? buildSrcSetString(sizesArray, finalSrc, quality, undefined, loader) : undefined}
304-
sizes={shouldUseSrcSet ? sizes : undefined}
299+
srcSet={isVisible && shouldUseSrcSet ? buildSrcSetString(sizesArray, finalSrc, quality, undefined, loader, hasFixedWidth) : undefined}
300+
sizes={hasFixedWidth ? undefined : sizes}
305301
alt={alt}
306302
width={fill ? undefined : imgWidth}
307303
height={fill ? undefined : imgHeight}
@@ -323,15 +319,15 @@ function OptimizedImage({
323319
{isVisible && !loader && DEFAULT_FORMATS.includes('avif') && (
324320
<source
325321
type="image/avif"
326-
srcSet={buildSrcSetString(sizesArray, finalSrc, quality, 'avif', loader)}
327-
sizes={sizes}
322+
srcSet={buildSrcSetString(sizesArray, finalSrc, quality, 'avif', loader, hasFixedWidth)}
323+
sizes={hasFixedWidth ? undefined : sizes}
328324
/>
329325
)}
330326
{isVisible && !loader && DEFAULT_FORMATS.includes('webp') && (
331327
<source
332328
type="image/webp"
333-
srcSet={buildSrcSetString(sizesArray, finalSrc, quality, 'webp', loader)}
334-
sizes={sizes}
329+
srcSet={buildSrcSetString(sizesArray, finalSrc, quality, 'webp', loader, hasFixedWidth)}
330+
sizes={hasFixedWidth ? undefined : sizes}
335331
/>
336332
)}
337333
{imgElement}

packages/rari/src/proxy/execute-proxy.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ export async function executeProxy(simpleRequest: SimpleRequest): Promise<Simple
2323

2424
const event = {
2525
waitUntil: (promise: Promise<unknown>) => {
26-
waitUntilPromises.push(promise)
26+
const handledPromise = promise.catch((err) => {
27+
return err
28+
})
29+
waitUntilPromises.push(handledPromise)
2730
},
2831
}
2932

packages/rari/src/proxy/executor.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,24 @@ export class ProxyExecutor {
88
private proxyFn: ProxyFunction | null = null
99
private config: ProxyConfig | null = null
1010
private initialized = false
11+
private initializationPromise: Promise<void> | null = null
1112

1213
async loadProxy(proxyModulePath: string): Promise<void> {
14+
if (this.initializationPromise) {
15+
return this.initializationPromise
16+
}
17+
18+
this.initializationPromise = this.doLoadProxy(proxyModulePath)
19+
20+
try {
21+
await this.initializationPromise
22+
}
23+
finally {
24+
this.initializationPromise = null
25+
}
26+
}
27+
28+
private async doLoadProxy(proxyModulePath: string): Promise<void> {
1329
try {
1430
const module = await import(proxyModulePath) as ProxyModule
1531

@@ -132,6 +148,7 @@ export class ProxyExecutor {
132148
this.proxyFn = null
133149
this.config = null
134150
this.initialized = false
151+
this.initializationPromise = null
135152

136153
if (typeof require !== 'undefined' && require.cache)
137154
delete require.cache[require.resolve(proxyModulePath)]

packages/rari/src/proxy/shared/utils.ts

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ export interface SimpleProxyResult {
1111
permanent: boolean
1212
}
1313
rewrite?: string
14-
requestHeaders?: Record<string, string>
15-
responseHeaders?: Record<string, string>
14+
requestHeaders?: Record<string, string | string[]>
15+
responseHeaders?: Record<string, string | string[]>
1616
response?: {
1717
status: number
1818
headers: Record<string, string | string[]>
@@ -65,18 +65,34 @@ export function checkForRedirect(result: ResponseLike | null): SimpleProxyResult
6565
return null
6666
}
6767

68-
export function extractProxyHeaders(headers: ResponseLike['headers']): { requestHeaders?: Record<string, string>, responseHeaders?: Record<string, string> } {
69-
const requestHeaders: Record<string, string> = {}
70-
const responseHeaders: Record<string, string> = {}
68+
export function extractProxyHeaders(headers: ResponseLike['headers']): { requestHeaders?: Record<string, string | string[]>, responseHeaders?: Record<string, string | string[]> } {
69+
const requestHeaders: Record<string, string | string[]> = {}
70+
const responseHeaders: Record<string, string | string[]> = {}
7171

7272
if (headers?.forEach) {
7373
headers.forEach((value: string, key: string) => {
7474
if (key.startsWith('x-rari-proxy-request-')) {
7575
const headerName = key.replace('x-rari-proxy-request-', '')
76-
requestHeaders[headerName] = value
76+
const lowerKey = headerName.toLowerCase()
77+
78+
if (Object.hasOwn(requestHeaders, lowerKey)) {
79+
const existing = requestHeaders[lowerKey]
80+
requestHeaders[lowerKey] = Array.isArray(existing) ? [...existing, value] : [existing, value]
81+
}
82+
else {
83+
requestHeaders[lowerKey] = value
84+
}
7785
}
7886
else if (!key.startsWith('x-rari-proxy-')) {
79-
responseHeaders[key] = value
87+
const lowerKey = key.toLowerCase()
88+
89+
if (Object.hasOwn(responseHeaders, lowerKey)) {
90+
const existing = responseHeaders[lowerKey]
91+
responseHeaders[lowerKey] = Array.isArray(existing) ? [...existing, value] : [existing, value]
92+
}
93+
else {
94+
responseHeaders[lowerKey] = value
95+
}
8096
}
8197
})
8298
}
@@ -102,7 +118,8 @@ export async function handleDirectResponse(result: ResponseLike): Promise<Simple
102118
if (result.headers?.forEach) {
103119
result.headers.forEach((value: string, key: string) => {
104120
const lowerKey = key.toLowerCase()
105-
if (headers[lowerKey]) {
121+
122+
if (Object.hasOwn(headers, lowerKey)) {
106123
const existing = headers[lowerKey]
107124
headers[lowerKey] = Array.isArray(existing) ? [...existing, value] : [existing, value]
108125
}

packages/rari/src/router/props-extractor.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,11 +182,7 @@ function mergeTitleField(parentTitle: MetadataResult['title'], childTitle: Metad
182182
if (typeof childTitle === 'string') {
183183
if (typeof parentTitle === 'object' && parentTitle?.template) {
184184
const hasPlaceholder = parentTitle.template.includes('%s')
185-
const resolvedTitle = hasPlaceholder ? parentTitle.template.replace('%s', childTitle) : childTitle
186-
return {
187-
...parentTitle,
188-
default: resolvedTitle,
189-
}
185+
return hasPlaceholder ? parentTitle.template.replace('%s', childTitle) : childTitle
190186
}
191187

192188
return childTitle

packages/rari/src/router/sitemap-generator.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,10 @@ async function generateMultipleSitemaps(module: any, outDir: string): Promise<vo
309309

310310
for (const { id } of sitemapIds) {
311311
try {
312-
const sanitizedId = String(id).replace(SANITIZE_ID_REGEX, '_')
312+
let sanitizedId = String(id).replace(SANITIZE_ID_REGEX, '_')
313+
314+
if (!sanitizedId || sanitizedId.length === 0)
315+
sanitizedId = '_'
313316

314317
const existingId = seenSanitizedIds.get(sanitizedId)
315318
if (existingId !== undefined) {

0 commit comments

Comments
 (0)