@bgub/fig-server 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
@@ -118,6 +118,44 @@ function nonceAttribute(nonce) {
118
118
  return nonce === void 0 ? "" : ` nonce="${escapeAttribute(nonce)}"`;
119
119
  }
120
120
  //#endregion
121
- export { deferred as a, streamHighWaterMark as c, createStaticDispatcher as i, withContextValue as l, cloneContextValues as n, nonceAttribute as o, componentStack as r, streamFlowBlocked as s, STREAMED_METADATA_ATTRIBUTE as t };
121
+ //#region src/image-preloads.ts
122
+ function imagePreloadFromHostProps(type, props, suppressed) {
123
+ if (suppressed || type.toLowerCase() !== "img") return null;
124
+ if (props.loading === "lazy" || props.fetchpriority === "low") return null;
125
+ const src = stringProp(props.src);
126
+ const srcset = stringProp(props.srcset);
127
+ if (props.src != null && typeof props.src !== "string" || props.srcset != null && typeof props.srcset !== "string" || !src && !srcset || isDataUrl(src) || isDataUrl(srcset)) return null;
128
+ return {
129
+ as: "image",
130
+ crossorigin: imageCrossorigin(props.crossorigin),
131
+ fetchpriority: imageFetchpriority(props.fetchpriority),
132
+ href: src || void 0,
133
+ imagesizes: srcset ? stringProp(props.sizes) : void 0,
134
+ imagesrcset: srcset || void 0,
135
+ kind: "preload",
136
+ referrerpolicy: stringProp(props.referrerpolicy),
137
+ type: stringProp(props.type)
138
+ };
139
+ }
140
+ function suppressesImagePreloads(type) {
141
+ const normalizedType = type.toLowerCase();
142
+ return normalizedType === "picture" || normalizedType === "noscript";
143
+ }
144
+ function stringProp(value) {
145
+ return typeof value === "string" ? value : void 0;
146
+ }
147
+ function isDataUrl(value) {
148
+ return value?.slice(0, 5).toLowerCase() === "data:";
149
+ }
150
+ function imageCrossorigin(value) {
151
+ if (typeof value !== "string") return void 0;
152
+ if (value === "anonymous" || value === "use-credentials" || value === "") return value;
153
+ return "";
154
+ }
155
+ function imageFetchpriority(value) {
156
+ return value === "high" || value === "auto" ? value : void 0;
157
+ }
158
+ //#endregion
159
+ export { componentStack as a, nonceAttribute as c, withContextValue as d, cloneContextValues as i, streamFlowBlocked as l, suppressesImagePreloads as n, createStaticDispatcher as o, STREAMED_METADATA_ATTRIBUTE as r, deferred as s, imagePreloadFromHostProps as t, streamHighWaterMark as u };
122
160
 
123
- //# sourceMappingURL=shared-Bna74_aZ.js.map
161
+ //# sourceMappingURL=image-preloads-BIFcrqm9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-preloads-BIFcrqm9.js","names":[],"sources":["../src/shared.ts","../src/image-preloads.ts"],"sourcesContent":["import type {\n ActionStateAction,\n ActionStateRunner,\n DependencyList,\n EffectCallback,\n ExternalStoreSubscribe,\n FigContext,\n StateSetter,\n StartTransition,\n} from \"@bgub/fig\";\nimport type {\n DataResource,\n RenderDispatcher,\n StableEventCallerArgs,\n} from \"@bgub/fig/internal\";\nimport { escapeAttribute } from \"./escaping.ts\";\n\nexport type ContextValues = Map<FigContext<unknown>, unknown[]>;\n\nexport const STREAMED_METADATA_ATTRIBUTE = \"data-fig-streamed-metadata\";\n\nexport interface StackFrame {\n name: string;\n parent: StackFrame | null;\n}\n\ninterface StaticDispatcherOptions {\n contextValues: ContextValues;\n externalStoreError: string;\n preloadData<TArgs extends unknown[], TValue>(\n resource: DataResource<TArgs, TValue>,\n args: TArgs,\n ): void;\n readData<TArgs extends unknown[], TValue>(\n resource: DataResource<TArgs, TValue>,\n args: TArgs,\n ): TValue;\n readPromise<T>(promise: PromiseLike<T>): T;\n updateError: string;\n useId(): string;\n}\n\nfunction contextStack(\n values: ContextValues,\n context: FigContext<unknown>,\n): unknown[] {\n let stack = values.get(context);\n\n if (stack === undefined) {\n stack = [];\n values.set(context, stack);\n }\n\n return stack;\n}\n\nexport function withContextValue<T>(\n values: ContextValues,\n context: FigContext<unknown>,\n value: unknown,\n callback: () => T,\n): T {\n const stack = contextStack(values, context);\n stack.push(value);\n\n try {\n return callback();\n } finally {\n stack.pop();\n }\n}\n\nfunction readContextValue<T>(values: ContextValues, context: FigContext<T>): T {\n const stack = values.get(context as FigContext<unknown>);\n if (stack !== undefined && stack.length > 0) {\n return stack[stack.length - 1] as T;\n }\n\n return context.defaultValue;\n}\n\nexport interface Deferred<T> {\n promise: Promise<T>;\n reject: (reason: unknown) => void;\n resolve: (value: T) => void;\n}\n\nexport function deferred<T>(): Deferred<T> {\n let resolve: Deferred<T>[\"resolve\"] = () => undefined;\n let reject: Deferred<T>[\"reject\"] = () => undefined;\n const promise = new Promise<T>((innerResolve, innerReject) => {\n resolve = innerResolve;\n reject = innerReject;\n });\n return { promise, reject, resolve };\n}\n\nexport function cloneContextValues(values: ContextValues): ContextValues {\n const clone: ContextValues = new Map();\n for (const [context, stack] of values) clone.set(context, [...stack]);\n return clone;\n}\n\n// Encoded bytes a render result stream's internal queue holds before flushing\n// pauses (resumed by consumer pulls). Shared by the HTML and payload\n// renderers. Large enough that a healthy consumer never blocks a flush; small\n// enough to bound per-connection buffering when the consumer stalls.\n// Rendering itself never pauses — only writing to the stream does.\nconst DEFAULT_STREAM_HIGH_WATER_MARK = 65536;\n\n// 0 would deadlock: desiredSize never goes positive, and read requests alone\n// do not make it so. 1 byte is the honest pure-pull minimum.\nexport function streamHighWaterMark(option: number | undefined): number {\n return Math.max(1, option ?? DEFAULT_STREAM_HIGH_WATER_MARK);\n}\n\n// Blocked means the stream's internal queue is at or past its high-water\n// mark; completed work then waits un-enqueued until the consumer pulls.\n// desiredSize is null on an errored stream — never blocked, because fatal\n// paths close the request before any further writes.\nexport function streamFlowBlocked(\n controller: ReadableStreamDefaultController<Uint8Array> | null,\n): boolean {\n const desiredSize = controller?.desiredSize;\n return typeof desiredSize === \"number\" && desiredSize <= 0;\n}\n\nfunction resolveInitialState<S>(initialState: S | (() => S)): S {\n return typeof initialState === \"function\"\n ? (initialState as () => S)()\n : initialState;\n}\n\nexport function createStaticDispatcher(\n options: StaticDispatcherOptions,\n): RenderDispatcher {\n return {\n useState<S>(initialState: S | (() => S)): [S, StateSetter<S>] {\n const value = resolveInitialState(initialState);\n const setState: StateSetter<typeof value> = () => {\n throw new Error(options.updateError);\n };\n return [value, setState];\n },\n useActionState<S, Args extends unknown[]>(\n _action: ActionStateAction<S, Args>,\n initialState: S,\n ): [S, ActionStateRunner<Args>, boolean] {\n const runner: ActionStateRunner<Args> = () => {\n throw new Error(options.updateError);\n };\n return [initialState, runner, false];\n },\n useId(): string {\n return options.useId();\n },\n useDeferredValue<T>(\n value: T,\n _initialValue: T | undefined,\n _hasInitialValue: boolean,\n ): T {\n return value;\n },\n useMemo<T>(calculate: () => T, _deps: DependencyList): T {\n return calculate();\n },\n useTransition(): [boolean, StartTransition] {\n // Server transitions run synchronously to completion; the signal never\n // aborts (there is no supersede/unmount lifecycle during a request).\n const startTransition: StartTransition = (\n callback: (signal: AbortSignal) => void | PromiseLike<void>,\n ) => void callback(new AbortController().signal);\n return [false, startTransition];\n },\n useReactive(_effect: EffectCallback, _deps?: DependencyList): void {},\n useBeforePaint(_effect: EffectCallback, _deps?: DependencyList): void {},\n useBeforeLayout(_effect: EffectCallback, _deps?: DependencyList): void {},\n useSyncExternalStore<T>(\n _subscribe: ExternalStoreSubscribe,\n _getSnapshot: () => T,\n getServerSnapshot?: () => T,\n ): T {\n if (getServerSnapshot === undefined) {\n throw new Error(options.externalStoreError);\n }\n\n return getServerSnapshot();\n },\n useStableEvent<Args extends unknown[], Result>(\n _handler: (...args: Args) => Result,\n ): (...args: StableEventCallerArgs<Args>) => Result {\n return (..._args: StableEventCallerArgs<Args>): Result => {\n throw new Error(\"Stable events cannot be called during server render.\");\n };\n },\n readContext<T>(context: FigContext<T>): T {\n return readContextValue(options.contextValues, context);\n },\n readData<TArgs extends unknown[], TValue>(\n resource: DataResource<TArgs, TValue>,\n args: TArgs,\n ): TValue {\n return options.readData(resource, args);\n },\n preloadData<TArgs extends unknown[], TValue>(\n resource: DataResource<TArgs, TValue>,\n args: TArgs,\n ): void {\n options.preloadData(resource, args);\n },\n readPromise<T>(promise: PromiseLike<T>): T {\n return options.readPromise(promise);\n },\n };\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nexport function componentStack(stack: StackFrame | null): string {\n const frames: string[] = [];\n for (let frame = stack; frame !== null; frame = frame.parent) {\n frames.push(` at ${frame.name}`);\n }\n return frames.length === 0 ? \"\" : `\\n${frames.join(\"\\n\")}`;\n}\n\nexport function nonceAttribute(nonce: string | undefined): string {\n return nonce === undefined ? \"\" : ` nonce=\"${escapeAttribute(nonce)}\"`;\n}\n","import type { PreloadResource, Props } from \"@bgub/fig\";\n\nexport function imagePreloadFromHostProps(\n type: string,\n props: Props,\n suppressed: boolean,\n): PreloadResource | null {\n if (suppressed || type.toLowerCase() !== \"img\") return null;\n if (props.loading === \"lazy\" || props.fetchpriority === \"low\") return null;\n\n const src = stringProp(props.src);\n const srcset = stringProp(props.srcset);\n if (\n (props.src != null && typeof props.src !== \"string\") ||\n (props.srcset != null && typeof props.srcset !== \"string\") ||\n (!src && !srcset) ||\n isDataUrl(src) ||\n isDataUrl(srcset)\n ) {\n return null;\n }\n\n return {\n as: \"image\",\n crossorigin: imageCrossorigin(props.crossorigin),\n fetchpriority: imageFetchpriority(props.fetchpriority),\n href: src || undefined,\n imagesizes: srcset ? stringProp(props.sizes) : undefined,\n imagesrcset: srcset || undefined,\n kind: \"preload\",\n referrerpolicy: stringProp(props.referrerpolicy),\n type: stringProp(props.type),\n };\n}\n\nexport function suppressesImagePreloads(type: string): boolean {\n const normalizedType = type.toLowerCase();\n return normalizedType === \"picture\" || normalizedType === \"noscript\";\n}\n\nfunction stringProp(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction isDataUrl(value: string | undefined): boolean {\n return value?.slice(0, 5).toLowerCase() === \"data:\";\n}\n\nfunction imageCrossorigin(value: unknown): PreloadResource[\"crossorigin\"] {\n if (typeof value !== \"string\") return undefined;\n if (value === \"anonymous\" || value === \"use-credentials\" || value === \"\") {\n return value;\n }\n return \"\";\n}\n\nfunction imageFetchpriority(value: unknown): PreloadResource[\"fetchpriority\"] {\n return value === \"high\" || value === \"auto\" ? value : undefined;\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ServerPreloadHeaderOptions, c as ServerPrerenderResult, d as RenderTreeKind, f as RenderTreeNode, i as ServerFragmentRenderResult, l as ServerRenderOptions, n as ServerErrorInfo, o as ServerPreloadHeaderResource, p as createRenderTreeCollector, r as ServerErrorPayload, s as ServerPrerenderOptions, t as ServerDocumentRenderResult, u as RenderTreeCollector } from "./types-CimwEFsf.js";
1
+ import { a as ServerPreloadHeaderOptions, c as ServerPrerenderResult, d as RenderTreeKind, f as RenderTreeNode, i as ServerFragmentRenderResult, l as ServerRenderOptions, n as ServerErrorInfo, o as ServerPreloadHeaderResource, p as createRenderTreeCollector, r as ServerErrorPayload, s as ServerPrerenderOptions, t as ServerDocumentRenderResult, u as RenderTreeCollector } from "./types-mrjoQDOc.js";
2
2
  import { FigNode } from "@bgub/fig";
3
3
  //#region src/index.d.ts
4
4
  declare function renderToStream(node: FigNode, options?: ServerRenderOptions): ServerFragmentRenderResult;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { i as escapeText, n as escapeScriptJson, t as escapeAttribute } from "./escaping-DAot8sFu.js";
2
- import { a as deferred, c as streamHighWaterMark, i as createStaticDispatcher, l as withContextValue, n as cloneContextValues, o as nonceAttribute, r as componentStack, s as streamFlowBlocked, t as STREAMED_METADATA_ATTRIBUTE } from "./shared-Bna74_aZ.js";
2
+ import { a as componentStack, c as nonceAttribute, d as withContextValue, i as cloneContextValues, l as streamFlowBlocked, n as suppressesImagePreloads, o as createStaticDispatcher, r as STREAMED_METADATA_ATTRIBUTE, s as deferred, t as imagePreloadFromHostProps, u as streamHighWaterMark } from "./image-preloads-BIFcrqm9.js";
3
3
  import { Fragment, createElement, isValidElement } from "@bgub/fig";
4
4
  import { ACTIVITY_TEMPLATE_ATTRIBUTE, EARLY_EVENT_HANDLER_PROPERTY, EARLY_EVENT_QUEUE_PROPERTY, HYDRATION_SKIP_ATTRIBUTE, REPLAYABLE_EVENT_TYPES, SUSPENSE_CLIENT_MARKER, SUSPENSE_COMPLETED_MARKER, SUSPENSE_END_MARKER, SUSPENSE_MARKER_PREFIX, SUSPENSE_PENDING_PREFIX, TEXT_SEPARATOR_DATA, VIEW_TRANSITION_CLASS_ATTRIBUTE, VIEW_TRANSITION_NAME_ATTRIBUTE, VIEW_TRANSITION_PENDING_PROPERTY, VIEW_TRANSITION_TIMEOUT_MS, assetResourceFromHostProps, assetResourceHostAttributes, assetResourceKey, attachDataStore, collectChildren, createRendererDataStore, invalidChildError, isActivity, isAssets, isClientReference, isContext, isErrorBoundary, isFigAssetResource, isPortal, isSuspense, isThenable, isViewTransition, readThenable, setCurrentDataStore, setCurrentDispatcher } from "@bgub/fig/internal";
5
5
  //#region src/html.ts
@@ -158,13 +158,13 @@ const DEFAULT_LENGTH = 2e3;
158
158
  const HEX_ESCAPE = /^[0-9A-Fa-f]{2}$/;
159
159
  const PARAMETER_TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
160
160
  const URI_REFERENCE_PUNCTUATION = "-._~:/?#[]@!$&'()*+,;=";
161
- function createPreloadHeaderEntries(resources) {
161
+ function createPreloadHeaderEntries(resources, isEarlyImage) {
162
162
  const preconnects = [];
163
163
  const criticalPreloads = [];
164
164
  const stylesheets = [];
165
165
  const remaining = [];
166
166
  for (const resource of resources) {
167
- if (resource.kind === "script") continue;
167
+ if (!isServerPreloadHeaderResource(resource)) continue;
168
168
  const value = preloadHeaderValue(resource);
169
169
  if (value === null) continue;
170
170
  const entry = {
@@ -172,7 +172,7 @@ function createPreloadHeaderEntries(resources) {
172
172
  value
173
173
  };
174
174
  if (resource.kind === "preconnect") preconnects.push(entry);
175
- else if (resource.kind === "font" || resource.kind === "preload" && (resource.as === "font" || resource.as === "image" && resource.fetchpriority === "high")) criticalPreloads.push(entry);
175
+ else if (resource.kind === "font" || resource.kind === "preload" && (resource.as === "font" || resource.as === "image" && (resource.fetchpriority === "high" || isEarlyImage?.(resource) === true))) criticalPreloads.push(entry);
176
176
  else if (resource.kind === "stylesheet") stylesheets.push(entry);
177
177
  else remaining.push(entry);
178
178
  }
@@ -213,7 +213,8 @@ function preloadHeaderValue(resource) {
213
213
  ["as", resource.as],
214
214
  ["crossorigin", resource.crossorigin],
215
215
  ["type", resource.type],
216
- ["fetchpriority", resource.fetchpriority]
216
+ ["fetchpriority", resource.fetchpriority],
217
+ ["referrerpolicy", resource.referrerpolicy]
217
218
  ]);
218
219
  case "modulepreload": return serializeLink(resource.href, [
219
220
  ["rel", "modulepreload"],
@@ -231,6 +232,10 @@ function preloadHeaderValue(resource) {
231
232
  case "preconnect": return serializeLink(resource.href, [["rel", "preconnect"], ["crossorigin", resource.crossorigin]]);
232
233
  }
233
234
  }
235
+ function isServerPreloadHeaderResource(resource) {
236
+ if (resource.kind === "script") return false;
237
+ return resource.kind !== "preload" || resource.href !== void 0 && (resource.as !== "image" || !resource.imagesrcset);
238
+ }
234
239
  function serializeLink(target, parameters) {
235
240
  const encodedTarget = encodeLinkTarget(target);
236
241
  if (encodedTarget === null) return null;
@@ -292,6 +297,7 @@ function normalizedLength(value) {
292
297
  //#region src/asset-registry.ts
293
298
  var AssetResourceRegistry = class {
294
299
  identifierPrefix;
300
+ earlyImageKeys = /* @__PURE__ */ new Set();
295
301
  emittedResources = /* @__PURE__ */ new Set();
296
302
  deliveryResources = /* @__PURE__ */ new Map();
297
303
  metadataByOwner = /* @__PURE__ */ new Map();
@@ -304,6 +310,13 @@ var AssetResourceRegistry = class {
304
310
  if (isMetadataResource(resource)) return;
305
311
  this.canonical(resource);
306
312
  }
313
+ registerAutomaticImage(resource) {
314
+ const { key, resource: current } = this.canonical(resource);
315
+ if (this.earlyImageKeys.size < 10 || current.kind === "preload" && current.as === "image" && current.fetchpriority === "high") this.earlyImageKeys.add(key);
316
+ }
317
+ isEarlyImage(resource) {
318
+ return this.earlyImageKeys.has(assetResourceKey(resource));
319
+ }
307
320
  activateMetadata(owner, resources) {
308
321
  const metadata = resources.filter(isMetadataResource);
309
322
  if (metadata.length === 0) {
@@ -367,7 +380,7 @@ var AssetResourceRegistry = class {
367
380
  return snapshot;
368
381
  }
369
382
  preloadHeaderEntries() {
370
- return createPreloadHeaderEntries(this.deliveryResources.values());
383
+ return createPreloadHeaderEntries(this.deliveryResources.values(), (resource) => this.isEarlyImage(resource));
371
384
  }
372
385
  visibleMetadata() {
373
386
  const visible = /* @__PURE__ */ new Map();
@@ -378,7 +391,15 @@ var AssetResourceRegistry = class {
378
391
  const key = assetResourceKey(resource);
379
392
  const current = this.deliveryResources.get(key);
380
393
  if (current !== void 0) {
381
- if (assetSignature(current) !== assetSignature(resource)) throw new AssetResourceConflictError(key, current, resource);
394
+ if (assetSignature(current) !== assetSignature(resource)) {
395
+ const promoted = promoteCompatibleImagePreload(current, resource);
396
+ if (promoted === null) throw new AssetResourceConflictError(key, current, resource);
397
+ this.deliveryResources.set(key, promoted);
398
+ return {
399
+ key,
400
+ resource: promoted
401
+ };
402
+ }
382
403
  return {
383
404
  key,
384
405
  resource: current
@@ -475,9 +496,12 @@ function withNonce(sink, props) {
475
496
  function assetSignature(resource) {
476
497
  switch (resource.kind) {
477
498
  case "stylesheet": return signature(resource.kind, resource.href, resource.media ?? "", resource.precedence ?? "", resource.crossorigin ?? "", resource.blocking ?? "reveal");
478
- case "preload": return signature(resource.kind, resource.href, resource.as, resource.type ?? "", resource.crossorigin ?? "", resource.fetchpriority ?? "");
499
+ case "preload": {
500
+ const imagesrcset = resource.as === "image" ? resource.imagesrcset || void 0 : void 0;
501
+ return signature(resource.kind, imagesrcset === void 0 ? resource.href ?? "" : "", resource.as, resource.type ?? "", resource.crossorigin ?? "", resource.fetchpriority ?? "", imagesrcset ?? "", imagesrcset === void 0 ? "" : resource.imagesizes ?? "", resource.referrerpolicy ?? "");
502
+ }
479
503
  case "modulepreload": return signature(resource.kind, resource.href, resource.crossorigin ?? "", resource.fetchpriority ?? "");
480
- case "font": return signature("preload", resource.href, "font", resource.type, resource.crossorigin ?? "anonymous", resource.fetchpriority ?? "");
504
+ case "font": return signature("preload", resource.href, "font", resource.type, resource.crossorigin ?? "anonymous", resource.fetchpriority ?? "", "", "", "");
481
505
  case "preconnect": return signature(resource.kind, resource.href, resource.crossorigin ?? "");
482
506
  case "script": return signature(resource.kind, resource.src, resource.module === true, resource.async !== false, resource.defer === true, resource.crossorigin ?? "");
483
507
  case "title": return signature(resource.kind, resource.value);
@@ -485,6 +509,20 @@ function assetSignature(resource) {
485
509
  }
486
510
  return unsupportedAssetResource(resource);
487
511
  }
512
+ function promoteCompatibleImagePreload(current, incoming) {
513
+ if (current.kind !== "preload" || incoming.kind !== "preload" || current.as !== "image" || incoming.as !== "image" || assetSignature({
514
+ ...current,
515
+ fetchpriority: incoming.fetchpriority
516
+ }) !== assetSignature(incoming)) return null;
517
+ const currentPriority = current.fetchpriority === "auto" ? void 0 : current.fetchpriority;
518
+ const incomingPriority = incoming.fetchpriority === "auto" ? void 0 : incoming.fetchpriority;
519
+ if (currentPriority === incomingPriority) return current;
520
+ if (currentPriority === "low" || incomingPriority === "low") return null;
521
+ return currentPriority === "high" ? current : {
522
+ ...current,
523
+ fetchpriority: "high"
524
+ };
525
+ }
488
526
  function unsupportedAssetResource(resource) {
489
527
  throw new Error(`Unsupported asset resource kind: ${resource.kind}`);
490
528
  }
@@ -771,20 +809,20 @@ function writeChunk(request, chunk, segment) {
771
809
  }
772
810
  if (request.document === null) return;
773
811
  const metadata = request.headSnapshot ?? request.assetRegistry.headMetadataHtml(request.nonce);
774
- const [beforeMetadata, afterMetadata] = partitionHeadAssets(segment.assetResources);
812
+ const [beforeMetadata, afterMetadata] = partitionHeadAssets(request, segment.assetResources);
775
813
  const blockingIds = /* @__PURE__ */ new Set();
776
814
  request.write(metadata.preamble);
777
815
  flushAssetList(request, beforeMetadata, blockingIds);
778
816
  request.write(metadata.metadata);
779
817
  flushAssetList(request, afterMetadata, blockingIds);
780
818
  }
781
- function partitionHeadAssets(resources) {
819
+ function partitionHeadAssets(request, resources) {
782
820
  const preconnects = [];
783
821
  const criticalPreloads = [];
784
822
  const stylesheets = [];
785
823
  const afterMetadata = [];
786
824
  for (const resource of resources) if (resource.kind === "preconnect") preconnects.push(resource);
787
- else if (resource.kind === "font" || resource.kind === "preload" && (resource.as === "font" || resource.as === "image" && resource.fetchpriority === "high")) criticalPreloads.push(resource);
825
+ else if (resource.kind === "font" || resource.kind === "preload" && (resource.as === "font" || resource.as === "image" && (resource.fetchpriority === "high" || request.assetRegistry.isEarlyImage(resource)))) criticalPreloads.push(resource);
788
826
  else if (resource.kind === "stylesheet") stylesheets.push(resource);
789
827
  else afterMetadata.push(resource);
790
828
  return [[
@@ -878,6 +916,7 @@ function createServerRenderRequest(node, options = {}, mode = {}) {
878
916
  hiddenActivityId: null,
879
917
  hostAncestors: [],
880
918
  hostNamespace: "html",
919
+ imagePreloadsSuppressed: false,
881
920
  idPath: "",
882
921
  pendingLeadingNewline: false,
883
922
  selectProps: null,
@@ -953,6 +992,7 @@ function forkScope(scope) {
953
992
  hiddenActivityId: scope.hiddenActivityId,
954
993
  hostAncestors: scope.hostAncestors,
955
994
  hostNamespace: scope.hostNamespace,
995
+ imagePreloadsSuppressed: scope.imagePreloadsSuppressed,
956
996
  idPath: scope.idPath,
957
997
  pendingLeadingNewline: scope.pendingLeadingNewline,
958
998
  selectProps: scope.selectProps,
@@ -1265,6 +1305,15 @@ function renderAssetValue(value, frame) {
1265
1305
  frame.segment.assetResources.push(resource);
1266
1306
  }
1267
1307
  }
1308
+ function renderAutomaticImagePreload(resource, frame) {
1309
+ try {
1310
+ frame.request.assetRegistry.registerAutomaticImage(resource);
1311
+ } catch (error) {
1312
+ recordErrorStack(error, frame.stack);
1313
+ throw error;
1314
+ }
1315
+ frame.segment.assetResources.push(resource);
1316
+ }
1268
1317
  function renderSuspense(props, frame) {
1269
1318
  consumePendingLeadingNewline(frame);
1270
1319
  const fallbackAbortableTasks = /* @__PURE__ */ new Set();
@@ -1360,6 +1409,10 @@ function renderHostElement(type, props, frame) {
1360
1409
  if (isVoid && hasRenderableChild(props.children)) throw new Error(`Void element <${type}> cannot have children.`);
1361
1410
  if (isVoid && unsafeHTML !== null) throw new Error(`Void element <${type}> cannot have unsafeHTML.`);
1362
1411
  if (unsafeHTML !== null && hasRenderableChild(props.children)) throw new Error("Host elements cannot have both unsafeHTML and children.");
1412
+ if (namespace === "html") {
1413
+ const imagePreload = imagePreloadFromHostProps(type, props, frame.imagePreloadsSuppressed);
1414
+ if (imagePreload !== null) renderAutomaticImagePreload(imagePreload, frame);
1415
+ }
1363
1416
  consumePendingLeadingNewline(frame);
1364
1417
  const viewTransition = frame.viewTransition;
1365
1418
  writeElementStart(type, viewTransition === null ? props : viewTransitionHostProps(props, viewTransition), frame.segment, frame.selectProps ?? {});
@@ -1385,9 +1438,11 @@ function renderHostElement(type, props, frame) {
1385
1438
  if (namespace === "html" && type === "select") frame.selectProps = props;
1386
1439
  const previousHostAncestors = frame.hostAncestors;
1387
1440
  const previousHostNamespace = frame.hostNamespace;
1441
+ const previousImagePreloadsSuppressed = frame.imagePreloadsSuppressed;
1388
1442
  const previousViewTransition = frame.viewTransition;
1389
1443
  if (viewTransition !== null) frame.viewTransition = null;
1390
1444
  frame.hostNamespace = childHostNamespace(type, namespace);
1445
+ frame.imagePreloadsSuppressed = previousImagePreloadsSuppressed || namespace === "html" && suppressesImagePreloads(type);
1391
1446
  if (tracksLeadingNewline) frame.segment.chunks.push(leadingNewlineStartMarker);
1392
1447
  try {
1393
1448
  renderChildren(props.children, frame);
@@ -1395,6 +1450,7 @@ function renderHostElement(type, props, frame) {
1395
1450
  frame.selectProps = previousSelectProps;
1396
1451
  frame.hostAncestors = previousHostAncestors;
1397
1452
  frame.hostNamespace = previousHostNamespace;
1453
+ frame.imagePreloadsSuppressed = previousImagePreloadsSuppressed;
1398
1454
  frame.viewTransition = previousViewTransition;
1399
1455
  frame.pendingLeadingNewline = previousPendingLeadingNewline;
1400
1456
  }