From d8143bf8027ba5d1765e06f32e245c4683f51a2c Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 21 Feb 2025 16:53:15 +0100 Subject: [PATCH 01/16] React.promise --- src/React.res | 1 + 1 file changed, 1 insertion(+) diff --git a/src/React.res b/src/React.res index 0beaa59..c738631 100644 --- a/src/React.res +++ b/src/React.res @@ -5,6 +5,7 @@ type element = Jsx.element external float: float => element = "%identity" external int: int => element = "%identity" external string: string => element = "%identity" +external promise: promise => element = "%identity" external array: array => element = "%identity" From 16b11dcdce4d22e753e4c0f612278622079f84a1 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 21 Feb 2025 17:05:23 +0100 Subject: [PATCH 02/16] React.useTransition --- src/React.res | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/React.res b/src/React.res index c738631..a18e8e5 100644 --- a/src/React.res +++ b/src/React.res @@ -312,9 +312,6 @@ external useImperativeHandle7: ( @module("react") external useDeferredValue: 'value => 'value = "useDeferredValue" -@module("react") -external useTransition: unit => (bool, (unit => unit) => unit) = "useTransition" - @module("react") external useInsertionEffectOnEveryRender: (unit => option unit>) => unit = "useInsertionEffect" @@ -406,3 +403,13 @@ external setDisplayName: (component<'props>, string) => unit = "displayName" @get @return(nullable) external displayName: component<'props> => option = "displayName" + +// Actions + +type transitionFunction = unit => promise + +type transitionStartFunction = transitionFunction => unit + +/** `useTransition` is a React Hook that lets you render a part of the UI in the background. */ +@module("react") +external useTransition: unit => (bool, transitionStartFunction) = "useTransition" From fddcca6997ee65b1cf44d8bff0af6e39def0bd59 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 21 Feb 2025 17:20:51 +0100 Subject: [PATCH 03/16] React.useActionState --- src/React.res | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/React.res b/src/React.res index a18e8e5..f64ca39 100644 --- a/src/React.res +++ b/src/React.res @@ -413,3 +413,15 @@ type transitionStartFunction = transitionFunction => unit /** `useTransition` is a React Hook that lets you render a part of the UI in the background. */ @module("react") external useTransition: unit => (bool, transitionStartFunction) = "useTransition" + +type action<'state, 'payload> = ('state, 'payload) => promise<'state> + +type formAction<'formData> = 'formData => promise + +/** `useActionState` is a Hook that allows you to update state based on the result of a form action. */ +@module("react") +external useActionState: ( + action<'state, 'payload>, + 'state, + ~permalink: string=?, +) => ('state, formAction<'payload>, bool) = "useActionState" From 0f0466571b4632b6a8cba798aab05715202c5656 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 21 Feb 2025 17:27:58 +0100 Subject: [PATCH 04/16] React.useOptimistic --- src/React.res | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/React.res b/src/React.res index f64ca39..b6b0a79 100644 --- a/src/React.res +++ b/src/React.res @@ -425,3 +425,8 @@ external useActionState: ( 'state, ~permalink: string=?, ) => ('state, formAction<'payload>, bool) = "useActionState" + +/** `useOptimistic` is a React Hook that lets you optimistically update the UI. */ +@module("react") +external useOptimistic: ('state, ('state, 'action) => 'state) => ('state, 'action => unit) = + "useOptimistic" From 235bfd0773af63d863d7af7aa78dff22d9b89627 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 21 Feb 2025 17:36:48 +0100 Subject: [PATCH 05/16] React.use --- src/React.res | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/React.res b/src/React.res index b6b0a79..68527ae 100644 --- a/src/React.res +++ b/src/React.res @@ -430,3 +430,14 @@ external useActionState: ( @module("react") external useOptimistic: ('state, ('state, 'action) => 'state) => ('state, 'action => unit) = "useOptimistic" + +module Usable = { + type t<'value> + + external context: Context.t<'value> => t<'value> = "%identity" + external promise: promise<'value> => t<'value> = "%identity" +} + +/** `use` is a React API that lets you read the value of a resource like a Promise or context. */ +@module("react") +external use: Usable.t<'value> => 'value = "use" From dd90512a792d2a2bd77cdafead166c2556bf8964 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 27 Apr 2025 17:43:43 +0200 Subject: [PATCH 06/16] React.act --- src/React.res | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/React.res b/src/React.res index 68527ae..960007a 100644 --- a/src/React.res +++ b/src/React.res @@ -441,3 +441,7 @@ module Usable = { /** `use` is a React API that lets you read the value of a resource like a Promise or context. */ @module("react") external use: Usable.t<'value> => 'value = "use" + +/** `act` is a test helper to apply pending React updates before making assertions. */ +@module("react") +external act: (unit => promise) => promise = "act" From 34422a1e81e4fee96d92427aa15d336d7a8923c4 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 13 Apr 2025 14:45:23 +0200 Subject: [PATCH 07/16] React.useDeferredValue now takes initial value --- src/React.res | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/React.res b/src/React.res index 960007a..9c5e997 100644 --- a/src/React.res +++ b/src/React.res @@ -310,7 +310,9 @@ external useImperativeHandle7: ( @module("react") external useId: unit => string = "useId" -@module("react") external useDeferredValue: 'value => 'value = "useDeferredValue" +/** `useDeferredValue` is a React Hook that lets you defer updating a part of the UI. */ +@module("react") +external useDeferredValue: ('value, ~initialValue: 'value=?) => 'value = "useDeferredValue" @module("react") external useInsertionEffectOnEveryRender: (unit => option unit>) => unit = From d742924422bc63bb1681603b40466c807b672eb5 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 21 Feb 2025 17:36:58 +0100 Subject: [PATCH 08/16] ReactDOM: ref cleanup function --- src/ReactDOM.res | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ReactDOM.res b/src/ReactDOM.res index 04c39c2..4206e74 100644 --- a/src/ReactDOM.res +++ b/src/ReactDOM.res @@ -37,7 +37,7 @@ type domRef = JsxDOM.domRef module Ref = { type t = domRef type currentDomRef = React.ref> - type callbackDomRef = Js.nullable => unit + type callbackDomRef = Js.nullable => option unit> external domRef: currentDomRef => domRef = "%identity" external callbackDomRef: callbackDomRef => domRef = "%identity" From f74a62d866a4e55f6b8fa48388a5bca851762672 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 27 Apr 2025 17:05:42 +0200 Subject: [PATCH 09/16] ReactDOM: Resource Preloading APIse --- src/ReactDOM.res | 111 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/src/ReactDOM.res b/src/ReactDOM.res index 4206e74..3ef3900 100644 --- a/src/ReactDOM.res +++ b/src/ReactDOM.res @@ -43,6 +43,117 @@ module Ref = { external callbackDomRef: callbackDomRef => domRef = "%identity" } +// Resource Preloading APIs + +/** The CORS policy to use. */ +type crossOrigin = [ + | #anonymous + | #"use-credentials" +] + +/** The Referrer header to send when fetching. */ +type referrerPolicy = [ + | #"referrer-when-downgrade" + | #"no-referrer" + | #origin + | #"origin-when-cross-origin" + | #"unsafe-url" +] + +/** Suggests a relative priority for fetching the resource. */ +type fetchPriority = [#auto | #high | #low] + +/** `prefetchDNS` lets you eagerly look up the IP of a server that you expect to load resources from. */ +@module("react-dom") +external prefetchDNS: string => unit = "prefetchDNS" + +/** `preconnect` lets you eagerly connect to a server that you expect to load resources from. */ +@module("react-dom") +external preconnect: string => unit = "preconnect" + +type preloadOptions = { + /** The type of resource. */ + @as("as") + as_: [ + | #audio + | #document + | #embed + | #fetch + | #font + | #image + | #object + | #script + | #style + | #track + | #video + | #worker + ], + /** The CORS policy to use. It is required when as is set to "fetch". */ + crossOrigin?: crossOrigin, + /** The Referrer header to send when fetching. */ + referrerPolicy?: referrerPolicy, + /** A cryptographic hash of the resource, to verify its authenticity. */ + integrity?: string, + /** The MIME type of the resource. */ + @as("type") + type_?: string, + /** A cryptographic nonce to allow the resource when using a strict Content Security Policy. */ + nonce?: string, + /** Suggests a relative priority for fetching the resource. */ + fetchPriority?: fetchPriority, + /** For use only with as: "image". Specifies the source set of the image. */ + imageSrcSet?: string, + /** For use only with as: "image". Specifies the sizes of the image. */ + imageSizes?: string, +} + +/** `preload` lets you eagerly fetch a resource such as a stylesheet, font, or external script that you expect to use. */ +@module("react-dom") +external preload: (string, preloadOptions) => unit = "preload" + +type preloadModuleOptions = { + /** The type of resource. */ + @as("as") + as_: [#script], + /** The CORS policy to use. It is required when as is set to "fetch". */ + crossOrigin?: crossOrigin, + /** A cryptographic hash of the resource, to verify its authenticity. */ + integrity?: string, + /** A cryptographic nonce to allow the resource when using a strict Content Security Policy. */ + nonce?: string, +} + +/** `preloadModule` lets you eagerly fetch an ESM module that you expect to use. */ +@module("react-dom") +external preloadModule: (string, preloadModuleOptions) => unit = "preloadModule" + +type preinitOptions = { + /** The type of resource. */ + @as("as") + as_: [#script | #style], + /** Required with stylesheets. Says where to insert the stylesheet relative to others. Stylesheets with higher precedence can override those with lower precedence. */ + precedence?: [#reset | #low | #medium | #high], + /** The CORS policy to use. It is required when as is set to "fetch". */ + crossOrigin?: crossOrigin, + /** The Referrer header to send when fetching. */ + referrerPolicy?: referrerPolicy, + /** A cryptographic hash of the resource, to verify its authenticity. */ + integrity?: string, + nonce?: string, + /** Suggests a relative priority for fetching the resource. */ + fetchPriority?: fetchPriority, +} + +/** `preinit` lets you eagerly fetch and evaluate a stylesheet or external script. */ +@module("react-dom") +external preinit: (string, preinitOptions) => unit = "preinit" + +/** To preinit an ESM module, call the `preinitModule` function from react-dom. */ +@module("react-dom") +external preinitModule: (string, preloadModuleOptions) => unit = "preinitModule" + +// Runtime + type domProps = JsxDOM.domProps @variadic @module("react") From af4bddb0c87c3f8bebb78bf0d92ca751fecbd92b Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 21 Feb 2025 17:28:18 +0100 Subject: [PATCH 10/16] ReactDOM.useFormStatus --- src/ReactDOM.res | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/ReactDOM.res b/src/ReactDOM.res index 3ef3900..fc226f3 100644 --- a/src/ReactDOM.res +++ b/src/ReactDOM.res @@ -25,6 +25,23 @@ module Client = { external hydrateRoot: (Dom.element, React.element) => Root.t = "hydrateRoot" } +// Very rudimentary form data bindings +module FormData = { + type t + type value + + @new external make: unit => t = "FormData" + + @send external append: (t, string, ~filename: string=?) => unit = "append" + @send external delete: (t, string) => unit = "delete" + @send external get: (t, string) => option = "get" + @send external getAll: (t, string) => array = "getAll" + @send external set: (string, string) => unit = "set" + @send external has: string => bool = "has" + // @send external keys: t => Iterator.t = "keys"; + // @send external values: t => Iterator.t = "values"; +} + @module("react-dom") external createPortal: (React.element, Dom.element) => React.element = "createPortal" @@ -43,6 +60,23 @@ module Ref = { external callbackDomRef: callbackDomRef => domRef = "%identity" } +// Hooks + +type formStatus<'state> = { + /** If true, this means the parent
is pending submission. Otherwise, false. */ + pending: bool, + /** An object implementing the FormData interface that contains the data the parent is submitting. If there is no active submission or no parent , it will be null. */ + data: FormData.t, + /** This represents whether the parent is submitting with either a GET or POST HTTP method. By default, a will use the GET method and can be specified by the method property. */ + method: [#get | #post], + /** A reference to the function passed to the action prop on the parent . If there is no parent , the property is null. If there is a URI value provided to the action prop, or no action prop specified, status.action will be null. */ + action: React.action<'state, FormData.t>, +} + +/** `useFormStatus` is a Hook that gives you status information of the last form submission. */ +@module("react-dom") +external useFormStatus: unit => formStatus<'state> = "useFormStatus" + // Resource Preloading APIs /** The CORS policy to use. */ From c99755c82b9d78dcebb47e9c7e242c5a4ec00788 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sun, 27 Apr 2025 18:06:05 +0200 Subject: [PATCH 11/16] JS output changes --- src/React.bs.js | 6 +++--- src/ReactDOM.bs.js | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/React.bs.js b/src/React.bs.js index c2471b6..3eb8131 100644 --- a/src/React.bs.js +++ b/src/React.bs.js @@ -3,8 +3,6 @@ var React = require("react"); -var Ref = {}; - var Children = {}; var Context = {}; @@ -25,7 +23,8 @@ function lazy_(load) { var Uncurried = {}; -exports.Ref = Ref; +var Usable = {}; + exports.Children = Children; exports.Context = Context; exports.Fragment = Fragment; @@ -33,4 +32,5 @@ exports.StrictMode = StrictMode; exports.Suspense = Suspense; exports.lazy_ = lazy_; exports.Uncurried = Uncurried; +exports.Usable = Usable; /* react Not a pure module */ diff --git a/src/ReactDOM.bs.js b/src/ReactDOM.bs.js index b65084d..06f42ca 100644 --- a/src/ReactDOM.bs.js +++ b/src/ReactDOM.bs.js @@ -8,14 +8,14 @@ var Client = { Root: Root }; -var Ref = {}; +var $$FormData = {}; -var Props = {}; +var Ref = {}; var Style; exports.Client = Client; +exports.$$FormData = $$FormData; exports.Ref = Ref; -exports.Props = Props; exports.Style = Style; /* No side effect */ From 3a539345ad3fa265030e7d01ed63c34fcd2bb98a Mon Sep 17 00:00:00 2001 From: Matthias Le Brun Date: Sat, 3 May 2025 14:38:39 +0200 Subject: [PATCH 12/16] Add external for basic support for formAction --- src/ReactDOM.res | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ReactDOM.res b/src/ReactDOM.res index fc226f3..160bb55 100644 --- a/src/ReactDOM.res +++ b/src/ReactDOM.res @@ -73,6 +73,8 @@ type formStatus<'state> = { action: React.action<'state, FormData.t>, } +external formAction: React.formAction => string = "%identity" + /** `useFormStatus` is a Hook that gives you status information of the last form submission. */ @module("react-dom") external useFormStatus: unit => formStatus<'state> = "useFormStatus" From 1694f228fdcee4bdf57ec05111ea6a857aa6e33d Mon Sep 17 00:00:00 2001 From: Matthias Le Brun Date: Sat, 3 May 2025 15:35:52 +0200 Subject: [PATCH 13/16] Add `usePromise` for `use(promise)` `use(context)` seems to exactly replicate the `useContext(context)` logic, and we want to maximize retro-compability (ie. not switch from `useContext` to `use` for that under the hood). simply adding `usePromise(promise)` seems to be the simplest, least invasive way to add the functionality. --- src/React.bs.js | 3 --- src/React.res | 14 +++----------- 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/React.bs.js b/src/React.bs.js index 3eb8131..ef827dd 100644 --- a/src/React.bs.js +++ b/src/React.bs.js @@ -23,8 +23,6 @@ function lazy_(load) { var Uncurried = {}; -var Usable = {}; - exports.Children = Children; exports.Context = Context; exports.Fragment = Fragment; @@ -32,5 +30,4 @@ exports.StrictMode = StrictMode; exports.Suspense = Suspense; exports.lazy_ = lazy_; exports.Uncurried = Uncurried; -exports.Usable = Usable; /* react Not a pure module */ diff --git a/src/React.res b/src/React.res index 9c5e997..0ab5809 100644 --- a/src/React.res +++ b/src/React.res @@ -251,6 +251,9 @@ external useCallback7: ('callback, ('a, 'b, 'c, 'd, 'e, 'f, 'g)) => 'callback = @module("react") external useContext: Context.t<'any> => 'any = "useContext" +@module("react") +external usePromise: promise<'a> => 'a = "use" + @module("react") external useRef: 'value => ref<'value> = "useRef" @module("react") @@ -433,17 +436,6 @@ external useActionState: ( external useOptimistic: ('state, ('state, 'action) => 'state) => ('state, 'action => unit) = "useOptimistic" -module Usable = { - type t<'value> - - external context: Context.t<'value> => t<'value> = "%identity" - external promise: promise<'value> => t<'value> = "%identity" -} - -/** `use` is a React API that lets you read the value of a resource like a Promise or context. */ -@module("react") -external use: Usable.t<'value> => 'value = "use" - /** `act` is a test helper to apply pending React updates before making assertions. */ @module("react") external act: (unit => promise) => promise = "act" From 1106232379000b1a73e3351986d7bee0c14870e0 Mon Sep 17 00:00:00 2001 From: Matthias Le Brun Date: Sat, 3 May 2025 16:26:47 +0200 Subject: [PATCH 14/16] proposal: make FormData more usable to get values this **kinda** goes against the zero-cost philosophy, but I don't see a world where users would not have to reimplement those. --- src/ReactDOM.bs.js | 39 ++++++++++++++++++++++++++++++++++++++- src/ReactDOM.res | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/ReactDOM.bs.js b/src/ReactDOM.bs.js index 06f42ca..fa51640 100644 --- a/src/ReactDOM.bs.js +++ b/src/ReactDOM.bs.js @@ -1,6 +1,7 @@ // Generated by ReScript, PLEASE EDIT WITH CARE 'use strict'; +var Caml_option = require("rescript/lib/js/caml_option.js"); var Root = {}; @@ -8,7 +9,43 @@ var Client = { Root: Root }; -var $$FormData = {}; +function getString(formData, name) { + var value = formData.get(name); + if (!(value == null) && typeof value === "string") { + return Caml_option.some(value); + } + +} + +function getFile(formData, name) { + var value = formData.get(name); + if (!(value == null) && typeof value !== "string") { + return Caml_option.some(value); + } + +} + +function getAll(t, string) { + return t.getAll(string).map(function (value) { + if (typeof value === "string") { + return { + TAG: "String", + _0: value + }; + } else { + return { + TAG: "File", + _0: value + }; + } + }); +} + +var $$FormData = { + getString: getString, + getFile: getFile, + getAll: getAll +}; var Ref = {}; diff --git a/src/ReactDOM.res b/src/ReactDOM.res index 160bb55..fbd564b 100644 --- a/src/ReactDOM.res +++ b/src/ReactDOM.res @@ -28,14 +28,43 @@ module Client = { // Very rudimentary form data bindings module FormData = { type t - type value + type file + + type formValue = + | String(string) + | File(file) @new external make: unit => t = "FormData" @send external append: (t, string, ~filename: string=?) => unit = "append" @send external delete: (t, string) => unit = "delete" - @send external get: (t, string) => option = "get" - @send external getAll: (t, string) => array = "getAll" + @return(nullable) @send external getUnsafe: (t, string) => option<'a> = "get" + @send external getAllUnsafe: (t, string) => array<'a> = "getAll" + + let getString = (formData, name) => { + switch formData->getUnsafe(name) { + | Some(value) => Js.typeof(value) === "string" ? Some(value) : None + | _ => None + } + } + + external _asFile: 'a => file = "%identity" + + let getFile = (formData, name) => { + switch formData->getUnsafe(name) { + | Some(value) => Js.typeof(value) === "string" ? None : Some(value->_asFile) + | _ => None + } + } + + let getAll = (t, string) => { + t + ->getAllUnsafe(string) + ->Js.Array2.map(value => { + Js.typeof(value) === "string" ? String(value) : File(value->_asFile) + }) + } + @send external set: (string, string) => unit = "set" @send external has: string => bool = "has" // @send external keys: t => Iterator.t = "keys"; From 5a5916153b44a0b7db484be34a4965182df7e84c Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 4 May 2025 10:11:54 +0200 Subject: [PATCH 15/16] useOptimistic optionnal updateFn --- src/React.res | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/React.res b/src/React.res index 0ab5809..f1f6831 100644 --- a/src/React.res +++ b/src/React.res @@ -433,8 +433,10 @@ external useActionState: ( /** `useOptimistic` is a React Hook that lets you optimistically update the UI. */ @module("react") -external useOptimistic: ('state, ('state, 'action) => 'state) => ('state, 'action => unit) = - "useOptimistic" +external useOptimistic: ( + 'state, + ~updateFn: ('state, 'action) => 'state=?, +) => ('state, 'action => unit) = "useOptimistic" /** `act` is a test helper to apply pending React updates before making assertions. */ @module("react") From ca5ce8f66f1ea7596410add3eb1f5b06eaff0b6e Mon Sep 17 00:00:00 2001 From: Freddy Harris Date: Sun, 4 May 2025 11:34:56 +0200 Subject: [PATCH 16/16] dom static prerender and prerenderToNodeStream --- src/ReactDOMStatic.bs.js | 2 ++ src/ReactDOMStatic.res | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/ReactDOMStatic.bs.js create mode 100644 src/ReactDOMStatic.res diff --git a/src/ReactDOMStatic.bs.js b/src/ReactDOMStatic.bs.js new file mode 100644 index 0000000..d856702 --- /dev/null +++ b/src/ReactDOMStatic.bs.js @@ -0,0 +1,2 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE +/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */ diff --git a/src/ReactDOMStatic.res b/src/ReactDOMStatic.res new file mode 100644 index 0000000..7988fbb --- /dev/null +++ b/src/ReactDOMStatic.res @@ -0,0 +1,30 @@ +type abortSignal // WebAPI.EventAPI.abortSignal + +type nodeStream // NodeJs.Stream.stream + +type readableStream // WebAPI.FileAPI.readableStream + +type prerenderOptions<'error> = { + bootstrapScriptContent?: string, + bootstrapScripts?: array, + bootstrapModules?: array, + identifierPrefix?: string, + namespaceURI?: string, + onError?: 'error => unit, + progressiveChunkSize?: int, + signal?: abortSignal, +} + +type staticResult = {prelude: readableStream} + +@module("react-dom/static") +external prerender: (React.element, ~options: prerenderOptions<'error>=?) => promise = + "prerender" + +type staticResultNode = {prelude: nodeStream} + +@module("react-dom/static") +external prerenderToNodeStream: ( + React.element, + ~options: prerenderOptions<'error>=?, +) => promise = "prerenderToNodeStream"