@tanstack/redact 0.0.20 → 0.1.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.
Files changed (253) hide show
  1. package/dist/core/attributes.js +2 -1
  2. package/dist/core/attributes.js.map +2 -2
  3. package/dist/core/browser.d.ts +9 -0
  4. package/dist/core/browser.js +35 -0
  5. package/dist/core/browser.js.map +7 -0
  6. package/dist/core/index.d.ts +2 -2
  7. package/dist/core/index.js +3 -3
  8. package/dist/core/internal.d.ts +31 -8
  9. package/dist/core/internal.js +3 -1
  10. package/dist/core/internal.js.map +2 -2
  11. package/dist/core/resource-hints.d.ts +7 -0
  12. package/dist/core/resource-hints.js +19 -0
  13. package/dist/core/resource-hints.js.map +7 -0
  14. package/dist/core/types.js +1 -1
  15. package/dist/dom/_all.d.ts +11 -16
  16. package/dist/dom/_all.js +9 -20
  17. package/dist/dom/_all.js.map +2 -2
  18. package/dist/dom/client.d.ts +4 -4
  19. package/dist/dom/client.js +4 -4
  20. package/dist/dom/commit.d.ts +23 -0
  21. package/dist/dom/commit.js +191 -0
  22. package/dist/dom/commit.js.map +7 -0
  23. package/dist/dom/dispatcher.d.ts +4 -22
  24. package/dist/dom/dispatcher.js +184 -126
  25. package/dist/dom/dispatcher.js.map +3 -3
  26. package/dist/dom/dom.d.ts +2 -0
  27. package/dist/dom/dom.js +72 -52
  28. package/dist/dom/dom.js.map +2 -2
  29. package/dist/dom/error-info.d.ts +2 -0
  30. package/dist/dom/error-info.js +16 -0
  31. package/dist/dom/error-info.js.map +7 -0
  32. package/dist/dom/event-replay.js +1 -1
  33. package/dist/dom/features/activity/full.d.ts +1 -0
  34. package/dist/dom/features/activity/full.js +162 -0
  35. package/dist/dom/features/activity/full.js.map +7 -0
  36. package/dist/dom/features/activity/index.d.ts +1 -0
  37. package/dist/dom/features/activity/index.js +3 -0
  38. package/dist/dom/features/activity/index.js.map +7 -0
  39. package/dist/dom/features/activity/stub.d.ts +1 -0
  40. package/dist/dom/features/activity/stub.js +12 -0
  41. package/dist/dom/features/activity/stub.js.map +7 -0
  42. package/dist/dom/features/class/full.js +87 -21
  43. package/dist/dom/features/class/full.js.map +2 -2
  44. package/dist/dom/features/class/index.d.ts +1 -1
  45. package/dist/dom/features/class/index.js +2 -2
  46. package/dist/dom/features/class/stub.js +3 -3
  47. package/dist/dom/features/context/full.d.ts +4 -1
  48. package/dist/dom/features/context/full.js +69 -9
  49. package/dist/dom/features/context/full.js.map +2 -2
  50. package/dist/dom/features/context/index.d.ts +1 -1
  51. package/dist/dom/features/context/index.js +2 -2
  52. package/dist/dom/features/context/stub.d.ts +4 -1
  53. package/dist/dom/features/context/stub.js +15 -4
  54. package/dist/dom/features/context/stub.js.map +2 -2
  55. package/dist/dom/features/forward-ref/full.js +12 -21
  56. package/dist/dom/features/forward-ref/full.js.map +2 -2
  57. package/dist/dom/features/forward-ref/index.d.ts +1 -1
  58. package/dist/dom/features/forward-ref/index.js +2 -2
  59. package/dist/dom/features/forward-ref/stub.js +4 -4
  60. package/dist/dom/features/fragment-refs/index.d.ts +1 -0
  61. package/dist/dom/features/fragment-refs/index.js +8 -0
  62. package/dist/dom/features/fragment-refs/index.js.map +7 -0
  63. package/dist/dom/features/fragment-refs/stub.d.ts +3 -0
  64. package/dist/dom/features/fragment-refs/stub.js +14 -0
  65. package/dist/dom/features/fragment-refs/stub.js.map +7 -0
  66. package/dist/dom/features/hydration/full.d.ts +11 -6
  67. package/dist/dom/features/hydration/full.js +76 -76
  68. package/dist/dom/features/hydration/full.js.map +2 -2
  69. package/dist/dom/features/hydration/index.d.ts +1 -1
  70. package/dist/dom/features/hydration/index.js +2 -2
  71. package/dist/dom/features/hydration/stub.d.ts +7 -2
  72. package/dist/dom/features/hydration/stub.js +1 -1
  73. package/dist/dom/features/hydration/stub.js.map +1 -1
  74. package/dist/dom/features/index.d.ts +8 -7
  75. package/dist/dom/features/index.js +9 -8
  76. package/dist/dom/features/index.js.map +2 -2
  77. package/dist/dom/features/lazy/full.js +4 -4
  78. package/dist/dom/features/lazy/index.d.ts +1 -1
  79. package/dist/dom/features/lazy/index.js +2 -2
  80. package/dist/dom/features/lazy/stub.js +4 -4
  81. package/dist/dom/features/memo/full.js +8 -6
  82. package/dist/dom/features/memo/full.js.map +2 -2
  83. package/dist/dom/features/memo/index.d.ts +1 -1
  84. package/dist/dom/features/memo/index.js +2 -2
  85. package/dist/dom/features/memo/stub.js +4 -4
  86. package/dist/dom/features/portal/full.js +4 -4
  87. package/dist/dom/features/portal/index.d.ts +1 -1
  88. package/dist/dom/features/portal/index.js +2 -2
  89. package/dist/dom/features/portal/stub.js +4 -4
  90. package/dist/dom/features/suspense/full.js +192 -47
  91. package/dist/dom/features/suspense/full.js.map +2 -2
  92. package/dist/dom/features/suspense/index.d.ts +1 -1
  93. package/dist/dom/features/suspense/index.js +2 -2
  94. package/dist/dom/features/suspense/stub.js +4 -4
  95. package/dist/dom/features/view-transition/full.d.ts +6 -0
  96. package/dist/dom/features/view-transition/full.js +280 -0
  97. package/dist/dom/features/view-transition/full.js.map +7 -0
  98. package/dist/dom/features/view-transition/index.d.ts +1 -0
  99. package/dist/dom/features/view-transition/index.js +3 -0
  100. package/dist/dom/features/view-transition/index.js.map +7 -0
  101. package/dist/dom/features/view-transition/resources.d.ts +11 -0
  102. package/dist/dom/features/view-transition/resources.js +108 -0
  103. package/dist/dom/features/view-transition/resources.js.map +7 -0
  104. package/dist/dom/features/view-transition/stub.d.ts +6 -0
  105. package/dist/dom/features/view-transition/stub.js +22 -0
  106. package/dist/dom/features/view-transition/stub.js.map +7 -0
  107. package/dist/dom/features/view-transition/visual.d.ts +14 -0
  108. package/dist/dom/features/view-transition/visual.js +356 -0
  109. package/dist/dom/features/view-transition/visual.js.map +7 -0
  110. package/dist/dom/fragment-instance.d.ts +40 -0
  111. package/dist/dom/fragment-instance.js +324 -0
  112. package/dist/dom/fragment-instance.js.map +7 -0
  113. package/dist/dom/index.d.ts +11 -11
  114. package/dist/dom/index.js +11 -18
  115. package/dist/dom/index.js.map +3 -3
  116. package/dist/dom/portal.d.ts +2 -2
  117. package/dist/dom/portal.js +2 -2
  118. package/dist/dom/portal.js.map +1 -1
  119. package/dist/dom/reconcile.d.ts +32 -3
  120. package/dist/dom/reconcile.js +686 -222
  121. package/dist/dom/reconcile.js.map +2 -2
  122. package/dist/dom/resource-hints.d.ts +6 -0
  123. package/dist/dom/resource-hints.js +115 -0
  124. package/dist/dom/resource-hints.js.map +7 -0
  125. package/dist/dom/resource-store.d.ts +13 -0
  126. package/dist/dom/resource-store.js +43 -0
  127. package/dist/dom/resource-store.js.map +7 -0
  128. package/dist/dom/resources.d.ts +1 -0
  129. package/dist/dom/resources.js +44 -0
  130. package/dist/dom/resources.js.map +7 -0
  131. package/dist/dom/retained-effects.d.ts +6 -0
  132. package/dist/dom/retained-effects.js +49 -0
  133. package/dist/dom/retained-effects.js.map +7 -0
  134. package/dist/dom/root-internal.d.ts +4 -4
  135. package/dist/dom/root-internal.js +2 -2
  136. package/dist/dom/root-internal.js.map +2 -2
  137. package/dist/dom/root.d.ts +5 -5
  138. package/dist/dom/root.js +11 -11
  139. package/dist/dom/root.js.map +2 -2
  140. package/dist/dom/test-utils.js +1 -1
  141. package/dist/react/activity.d.ts +7 -0
  142. package/dist/react/activity.js +8 -0
  143. package/dist/react/activity.js.map +7 -0
  144. package/dist/react/children.d.ts +1 -1
  145. package/dist/react/children.js +3 -3
  146. package/dist/react/children.js.map +1 -1
  147. package/dist/react/class.d.ts +1 -1
  148. package/dist/react/class.js +3 -3
  149. package/dist/react/class.js.map +1 -1
  150. package/dist/react/compiler-runtime.js +2 -2
  151. package/dist/react/context.d.ts +1 -1
  152. package/dist/react/context.js +3 -3
  153. package/dist/react/context.js.map +1 -1
  154. package/dist/react/element.d.ts +6 -3
  155. package/dist/react/element.js +2 -2
  156. package/dist/react/element.js.map +2 -2
  157. package/dist/react/hooks.d.ts +26 -7
  158. package/dist/react/hooks.js +13 -3
  159. package/dist/react/hooks.js.map +2 -2
  160. package/dist/react/index.d.ts +39 -24
  161. package/dist/react/index.js +39 -19
  162. package/dist/react/index.js.map +3 -3
  163. package/dist/react/jsx-runtime.d.ts +2 -2
  164. package/dist/react/jsx-runtime.js +3 -3
  165. package/dist/react/memo.d.ts +1 -1
  166. package/dist/react/memo.js +1 -1
  167. package/dist/react/portal.js +1 -1
  168. package/dist/react/shared-internals.d.ts +15 -5
  169. package/dist/react/shared-internals.js +2 -3
  170. package/dist/react/shared-internals.js.map +2 -2
  171. package/dist/react/suspense.js +1 -1
  172. package/dist/react/view-transition.d.ts +32 -0
  173. package/dist/react/view-transition.js +13 -0
  174. package/dist/react/view-transition.js.map +7 -0
  175. package/dist/scheduler/index.js +1 -1
  176. package/dist/server/bootstrap-script.d.ts +1 -23
  177. package/dist/server/bootstrap-script.js +7 -6
  178. package/dist/server/bootstrap-script.js.map +2 -2
  179. package/dist/server/dispatcher.d.ts +28 -6
  180. package/dist/server/dispatcher.js +33 -15
  181. package/dist/server/dispatcher.js.map +2 -2
  182. package/dist/server/escape.js +16 -15
  183. package/dist/server/escape.js.map +2 -2
  184. package/dist/server/index.d.ts +6 -6
  185. package/dist/server/index.js +7 -7
  186. package/dist/server/renderToString.d.ts +1 -1
  187. package/dist/server/renderToString.js +12 -8
  188. package/dist/server/renderToString.js.map +2 -2
  189. package/dist/server/resource-hints.d.ts +22 -0
  190. package/dist/server/resource-hints.js +148 -0
  191. package/dist/server/resource-hints.js.map +7 -0
  192. package/dist/server/stream.d.ts +15 -2
  193. package/dist/server/stream.js +155 -63
  194. package/dist/server/stream.js.map +2 -2
  195. package/dist/server/walk.d.ts +15 -2
  196. package/dist/server/walk.js +143 -96
  197. package/dist/server/walk.js.map +2 -2
  198. package/dist/vite/index.d.ts +6 -0
  199. package/dist/vite/index.js +22 -6
  200. package/dist/vite/index.js.map +2 -2
  201. package/package.json +1 -1
  202. package/src/core/attributes.ts +1 -0
  203. package/src/core/browser.ts +33 -0
  204. package/src/core/internal.ts +24 -6
  205. package/src/core/resource-hints.ts +36 -0
  206. package/src/dom/_all.ts +1 -7
  207. package/src/dom/commit.ts +186 -0
  208. package/src/dom/dispatcher.ts +190 -134
  209. package/src/dom/dom.ts +70 -54
  210. package/src/dom/error-info.ts +15 -0
  211. package/src/dom/features/activity/full.ts +166 -0
  212. package/src/dom/features/activity/index.ts +1 -0
  213. package/src/dom/features/activity/stub.ts +14 -0
  214. package/src/dom/features/class/full.ts +72 -21
  215. package/src/dom/features/context/full.ts +66 -4
  216. package/src/dom/features/context/stub.ts +6 -0
  217. package/src/dom/features/forward-ref/full.ts +9 -19
  218. package/src/dom/features/fragment-refs/index.ts +1 -0
  219. package/src/dom/features/fragment-refs/stub.ts +3 -0
  220. package/src/dom/features/hydration/full.ts +84 -82
  221. package/src/dom/features/hydration/stub.ts +1 -1
  222. package/src/dom/features/index.ts +1 -0
  223. package/src/dom/features/memo/full.ts +4 -3
  224. package/src/dom/features/suspense/full.ts +187 -50
  225. package/src/dom/features/view-transition/full.ts +288 -0
  226. package/src/dom/features/view-transition/index.ts +1 -0
  227. package/src/dom/features/view-transition/resources.ts +107 -0
  228. package/src/dom/features/view-transition/stub.ts +7 -0
  229. package/src/dom/features/view-transition/visual.ts +406 -0
  230. package/src/dom/fragment-instance.ts +337 -0
  231. package/src/dom/index.ts +6 -7
  232. package/src/dom/portal.ts +1 -1
  233. package/src/dom/reconcile.ts +691 -244
  234. package/src/dom/resource-hints.ts +115 -0
  235. package/src/dom/resource-store.ts +52 -0
  236. package/src/dom/resources.ts +39 -0
  237. package/src/dom/retained-effects.ts +38 -0
  238. package/src/dom/root-internal.ts +4 -4
  239. package/src/dom/root.ts +12 -14
  240. package/src/react/activity.ts +10 -0
  241. package/src/react/element.ts +6 -2
  242. package/src/react/hooks.ts +12 -3
  243. package/src/react/index.ts +18 -0
  244. package/src/react/shared-internals.ts +3 -3
  245. package/src/react/view-transition.ts +41 -0
  246. package/src/server/bootstrap-script.ts +36 -5
  247. package/src/server/dispatcher.ts +35 -13
  248. package/src/server/escape.ts +15 -12
  249. package/src/server/renderToString.ts +7 -5
  250. package/src/server/resource-hints.ts +165 -0
  251. package/src/server/stream.ts +154 -62
  252. package/src/server/walk.ts +72 -11
  253. package/src/vite/index.ts +30 -13
@@ -1,18 +1,41 @@
1
1
  import { ReactSharedInternals, REACT_CONTEXT_TYPE } from '../react'
2
+ import { isBrowserToken, browserError } from '../core/browser'
3
+ import { resourceHintDispatcher } from '../core/resource-hints'
4
+ import { createResourceHints, type ResourceScope } from './resource-hints'
2
5
 
3
- interface SSRFrame {
6
+ export interface SSRFrame {
4
7
  idCounter: number
5
8
  identifierPrefix: string
6
9
  contextStack: Array<{ context: any; prev: any }>
10
+ resources?: ReturnType<typeof createResourceHints>
11
+ shellFlushed?: boolean
12
+ styleNonce?: string | undefined
7
13
  }
8
14
 
9
15
  let frame: SSRFrame | null = null
10
16
 
11
- export function beginSSR(identifierPrefix = ':R'): void {
12
- frame = { idCounter: 0, identifierPrefix, contextStack: [] }
17
+ resourceHintDispatcher.emit = (kind, href, options) => {
18
+ if (!frame) return false
19
+ ;(frame.resources ||= createResourceHints(!!frame.shellFlushed, frame.styleNonce)).add(kind, href, options)
20
+ return true
13
21
  }
14
22
 
15
- export function endSSR(): void {
23
+ export function beginSSR(identifierPrefix = ':R', nextFrame?: SSRFrame) {
24
+ const previous = { frame, dispatcher: ReactSharedInternals.H }
25
+ frame = nextFrame ?? { idCounter: 0, identifierPrefix, contextStack: [] }
26
+ ReactSharedInternals.H = ssrDispatcher as any
27
+ return previous
28
+ }
29
+
30
+ export function currentSSRFrame(): SSRFrame {
31
+ return frame!
32
+ }
33
+
34
+ export function recordHostResource(type: string, props: any, scope?: ResourceScope): boolean {
35
+ return !!frame && (frame.resources ||= createResourceHints(!!frame.shellFlushed, frame.styleNonce)).addHost(type, props, scope)
36
+ }
37
+
38
+ export function endSSR(previous?: ReturnType<typeof beginSSR>): void {
16
39
  // Restore any remaining pushed contexts (defensive)
17
40
  if (frame) {
18
41
  for (let i = frame.contextStack.length - 1; i >= 0; i--) {
@@ -20,7 +43,10 @@ export function endSSR(): void {
20
43
  context._currentValue = prev
21
44
  }
22
45
  }
23
- frame = null
46
+ // Only synchronous renders restore their caller. A stream can finish
47
+ // after its caller has returned, so it must not reinstall that dispatcher.
48
+ ReactSharedInternals.H = previous?.dispatcher ?? null
49
+ frame = previous?.frame ?? null
24
50
  }
25
51
 
26
52
  export function pushContext(context: any, value: any): void {
@@ -59,6 +85,9 @@ export function applyContextSnapshot(snapshot: ContextSnapshot): () => void {
59
85
  }
60
86
 
61
87
  export const ssrDispatcher = {
88
+ useCacheRefresh() {
89
+ return () => { throw new Error('Refreshing the cache is not supported during server rendering.') }
90
+ },
62
91
  useState<S>(initial: S | (() => S)) {
63
92
  const v = typeof initial === 'function' ? (initial as () => S)() : initial
64
93
  return [v, (() => {}) as any] as [S, any]
@@ -113,6 +142,7 @@ export const ssrDispatcher = {
113
142
  },
114
143
  use<T>(resource: any): T {
115
144
  if (resource == null) throw new Error('use() received null or undefined')
145
+ if (isBrowserToken(resource)) throw browserError(resource)
116
146
  if (resource.$$typeof === REACT_CONTEXT_TYPE) {
117
147
  return resource._currentValue
118
148
  }
@@ -148,11 +178,3 @@ export const ssrDispatcher = {
148
178
  throw new Error('use() expected a Promise or Context')
149
179
  },
150
180
  }
151
-
152
- export function installSSRDispatcher(): void {
153
- ReactSharedInternals.H = ssrDispatcher as any
154
- }
155
-
156
- export function uninstallSSRDispatcher(): void {
157
- ReactSharedInternals.H = null
158
- }
@@ -1,23 +1,25 @@
1
1
  import { attributeName, STRING_BOOLEAN_ATTRS } from '../core/attributes'
2
2
 
3
- const ATTR_MAP: Record<string, string> = {
4
- '&': '&amp;',
5
- '"': '&quot;',
6
- '<': '&lt;',
7
- '>': '&gt;',
8
- }
9
- const TEXT_MAP: Record<string, string> = {
10
- '&': '&amp;',
11
- '<': '&lt;',
12
- '>': '&gt;',
3
+ function escape(value: string, pattern: RegExp): string {
4
+ let out = ''
5
+ let start = 0
6
+ while (pattern.test(value)) {
7
+ const end = pattern.lastIndex
8
+ const char = value[end - 1]
9
+ out += value.slice(start, end - 1) + (
10
+ char === '&' ? '&amp;' : char === '<' ? '&lt;' : char === '>' ? '&gt;' : '&quot;'
11
+ )
12
+ start = end
13
+ }
14
+ return out + value.slice(start)
13
15
  }
14
16
 
15
17
  export function escapeAttr(value: string): string {
16
- return value.replace(/[&"<>]/g, (c) => ATTR_MAP[c]!)
18
+ return /[&"<>]/.test(value) ? escape(value, /[&"<>]/g) : value
17
19
  }
18
20
 
19
21
  export function escapeText(value: string): string {
20
- return value.replace(/[&<>]/g, (c) => TEXT_MAP[c]!)
22
+ return /[&<>]/.test(value) ? escape(value, /[&<>]/g) : value
21
23
  }
22
24
 
23
25
  // Raw-text element body escaping: prevent any closing tag for the raw-text
@@ -57,6 +59,7 @@ const BOOLEAN_ATTRS = new Set([
57
59
  'autoplay',
58
60
  'checked',
59
61
  'controls',
62
+ 'credentialless',
60
63
  'default',
61
64
  'defer',
62
65
  'disabled',
@@ -1,25 +1,27 @@
1
1
  import type { ReactNode } from '../core'
2
- import { beginSSR, endSSR, installSSRDispatcher, uninstallSSRDispatcher } from './dispatcher'
2
+ import { beginSSR, currentSSRFrame, endSSR } from './dispatcher'
3
3
  import { walk } from './walk'
4
4
 
5
5
  export function renderToString(
6
6
  children: ReactNode,
7
7
  options: { identifierPrefix?: string } = {},
8
8
  ): string {
9
- installSSRDispatcher()
10
- beginSSR(options.identifierPrefix)
9
+ const previous = beginSSR(options.identifierPrefix)
11
10
  let output = ''
12
11
  let id = 0
12
+ let head = 0
13
13
  try {
14
14
  walk(children, {
15
15
  emit: (s) => {
16
16
  output += s
17
17
  },
18
18
  nextBoundaryId: () => id++,
19
+ onHead: () => { head = output.length },
19
20
  })
21
+ const hints = currentSSRFrame().resources?.drain()
22
+ if (hints) output = output.slice(0, head) + hints + output.slice(head)
20
23
  } finally {
21
- endSSR()
22
- uninstallSSRDispatcher()
24
+ endSSR(previous)
23
25
  }
24
26
  return output
25
27
  }
@@ -0,0 +1,165 @@
1
+ import { resourceKind, resourceURL, type ResourceHintKind, type ResourceHintOptions } from '../core/resource-hints'
2
+ import { attrToHtml, escapeAttr } from './escape'
3
+ import { attributeName } from '../core/attributes'
4
+
5
+ interface Resource {
6
+ kind: ResourceHintKind | 'style'
7
+ href: string
8
+ options: Record<string, any>
9
+ sent?: boolean
10
+ cancelled?: boolean
11
+ preloaded?: boolean
12
+ css?: string
13
+ }
14
+
15
+ export interface ResourceScope {
16
+ stylesheets?: Map<string, string[]>
17
+ inline?: Set<string>
18
+ }
19
+
20
+ function styleText(props: any): string {
21
+ const html = props.dangerouslySetInnerHTML
22
+ if (html != null) {
23
+ if (typeof html !== 'object' || !('__html' in html)) throw new Error('dangerouslySetInnerHTML must be an object with an __html field.')
24
+ if (props.children != null) throw new Error('Can only set one of children or dangerouslySetInnerHTML.')
25
+ return html.__html == null ? '' : '' + html.__html
26
+ }
27
+ const child = Array.isArray(props.children) ? props.children.length < 2 ? props.children[0] : null : props.children
28
+ return child == null || typeof child === 'function' || typeof child === 'symbol' ? ''
29
+ : ('' + child).replace(/(<\/?)(s)(tyle)/gi, (_, prefix: string, s: string, suffix: string) => prefix + '\\' + s.charCodeAt(0).toString(16) + ' ' + suffix)
30
+ }
31
+
32
+ function markup(resource: Resource, late: boolean): string {
33
+ const { kind, href, options } = resource
34
+ const script = kind === 'script' || kind === 'module-script'
35
+ const style = kind === 'stylesheet'
36
+ const props: Record<string, any> = script
37
+ ? { src: href, ...(kind === 'module-script' ? { type: 'module' } : {}), async: true, ...options }
38
+ : style
39
+ ? late
40
+ ? { rel: 'preload', as: 'style', href, crossOrigin: options.crossOrigin, fetchPriority: options.fetchPriority, integrity: options.integrity, media: options.media, hrefLang: options.hrefLang, referrerPolicy: options.referrerPolicy }
41
+ : { rel: 'stylesheet', href, 'data-precedence': options.precedence!, ...options }
42
+ : { rel: kind, ...(options.as === 'image' && options.imageSrcSet ? {} : { href }), ...options }
43
+ let output = script ? '<script' : '<link'
44
+ for (const name in props) {
45
+ if (name === 'precedence' || late && name === 'data-precedence') continue
46
+ const value = name === 'href' || name === 'src' ? resourceURL(props[name]!) : props[name]!
47
+ output += attrToHtml(name, value).replace(/'/g, '&#x27;')
48
+ }
49
+ return output + (script ? '></script>' : '/>')
50
+ }
51
+
52
+ export function createResourceHints(shellFlushed = false, styleNonce?: string) {
53
+ const resources = new Map<string, Resource>()
54
+ const groups: Resource[][] = [[], [], [], [], []]
55
+ const styles = new Map<string, Resource[]>()
56
+ const dependencies = new Map<string, string[]>()
57
+ const visibleInline = new Set<string>()
58
+ const collector = {
59
+ add(kind: ResourceHintKind | 'style', href: string, options: ResourceHintOptions): Resource {
60
+ let type: string = kind
61
+ let source = href
62
+ const init = kind === 'style' || kind === 'stylesheet' || kind === 'script' || kind === 'module-script'
63
+ if (kind === 'preload') {
64
+ type = options.as!
65
+ if (type === 'image' && options.imageSrcSet) source = options.imageSrcSet + '\n' + (options.imageSizes || '')
66
+ } else if (kind === 'modulepreload') type = 'module-' + (options.as || 'script')
67
+ else if (kind === 'stylesheet') type = 'style'
68
+ else if (kind === 'preconnect') type += ':' + (options.crossOrigin ?? 'default')
69
+ const key = JSON.stringify([type, source])
70
+ const prior = resources.get(key)
71
+ if (prior && (!init || prior.kind !== 'preload' && prior.kind !== 'modulepreload')) return prior
72
+
73
+ const resource: Resource = { kind, href, options: { ...options } }
74
+ if (kind === 'style' && (!styleNonce || options.nonce === styleNonce)) resource.css = styleText(options)
75
+ if (prior) {
76
+ prior.cancelled = true
77
+ resource.preloaded = !!prior.sent
78
+ for (const name of ['crossOrigin', 'integrity']) {
79
+ if (resource.options[name] == null && prior.options[name] != null) resource.options[name] = prior.options[name]!
80
+ }
81
+ }
82
+ resources.set(key, resource)
83
+ if (kind === 'stylesheet' || kind === 'style') {
84
+ const precedence = options.precedence!
85
+ let group = styles.get(precedence)
86
+ if (!group) styles.set(precedence, group = [])
87
+ group.push(resource)
88
+ } else {
89
+ const group = kind === 'dns-prefetch' || kind === 'preconnect' ? 0
90
+ : kind === 'preload' && options.as === 'font' ? 1
91
+ : kind === 'preload' && options.as === 'image' && options.fetchPriority === 'high' ? 2
92
+ : init ? 3 : 4
93
+ groups[group]!.push(resource)
94
+ }
95
+ return resource
96
+ },
97
+ addHost(type: string, props: any, scope?: ResourceScope): boolean {
98
+ const kind = resourceKind(type, props)
99
+ if (!kind) return false
100
+ const resource = collector.add(kind === 'script' && props.type === 'module' ? 'module-script' : kind, kind === 'script' ? props.src : props.href, props)
101
+ if (shellFlushed && kind === 'stylesheet') {
102
+ const descriptor = [resourceURL(resource.href), resource.options.precedence]
103
+ for (const name in resource.options) {
104
+ if (name === 'href' || name === 'rel' || name === 'precedence' || name === 'data-precedence' || name === 'children' || name === 'dangerouslySetInnerHTML' || name === 'innerHTML' || name === 'style' || name === 'suppressHydrationWarning' || name === 'suppressContentEditableWarning' || name === 'ref' || name === 'key' || /^on/i.test(name) || !/^[a-zA-Z_:][a-zA-Z0-9:_.-]*$/.test(name)) continue
105
+ const value = resource.options[name]
106
+ if (value == null || typeof value === 'function' || typeof value === 'symbol' || name === 'hidden' && value === false) continue
107
+ descriptor.push(attributeName(name), name === 'hidden' ? '' : name === 'src' ? resourceURL('' + value) : '' + value)
108
+ }
109
+ ;(scope ? scope.stylesheets ||= new Map() : dependencies).set(resource.href, descriptor)
110
+ } else if (shellFlushed && kind === 'style') {
111
+ ;(scope ? scope.inline ||= new Set() : visibleInline).add(resource.options.precedence)
112
+ }
113
+ return true
114
+ },
115
+ mergeScope(scope: ResourceScope, parent?: ResourceScope): void {
116
+ if (scope.stylesheets) for (const [href, descriptor] of scope.stylesheets) {
117
+ ;(parent ? parent.stylesheets ||= new Map() : dependencies).set(href, descriptor)
118
+ }
119
+ if (scope.inline) for (const precedence of scope.inline) {
120
+ ;(parent ? parent.inline ||= new Set() : visibleInline).add(precedence)
121
+ }
122
+ },
123
+ takeStylesheets(): string[][] {
124
+ const result = Array.from(dependencies.values())
125
+ dependencies.clear()
126
+ return result
127
+ },
128
+ drain(): string {
129
+ let output = ''
130
+ function flush(group: Resource[], precedence?: string) {
131
+ let remaining = 0
132
+ let inline = false, hasStylesheets = false
133
+ let hrefs = '', rules = ''
134
+ for (const resource of group) {
135
+ if (!resource.sent && !resource.cancelled) {
136
+ if (resource.kind === 'style') {
137
+ if (shellFlushed && !visibleInline.has(precedence!)) { group[remaining++] = resource; continue }
138
+ inline = true
139
+ if (resource.css !== undefined) {
140
+ hrefs += (hrefs ? ' ' : '') + resource.href
141
+ rules += resource.css
142
+ }
143
+ } else {
144
+ if (resource.kind === 'stylesheet') hasStylesheets = true
145
+ if (!(shellFlushed && resource.kind === 'stylesheet' && resource.preloaded)) output += markup(resource, shellFlushed)
146
+ }
147
+ resource.sent = true
148
+ }
149
+ }
150
+ group.length = remaining
151
+ if (hrefs || inline && !shellFlushed && !hasStylesheets) {
152
+ output += `<style${styleNonce !== undefined ? ` nonce="${escapeAttr(styleNonce)}"` : ''}${shellFlushed ? ' media="not all"' : ''} data-precedence="${escapeAttr(precedence!)}"${hrefs ? ` data-href="${escapeAttr(hrefs)}"` : ''}>${rules}</style>`
153
+ }
154
+ }
155
+ for (let index = 0; index < groups.length; index++) {
156
+ if (index === 3) for (const [precedence, group] of styles) flush(group, precedence)
157
+ flush(groups[index]!)
158
+ }
159
+ shellFlushed = true
160
+ visibleInline.clear()
161
+ return output
162
+ },
163
+ }
164
+ return collector
165
+ }