From 3129e28505eb3be646c4cae5d84b2d22b5384c19 Mon Sep 17 00:00:00 2001 From: gnoff Date: Thu, 24 Aug 2023 20:53:38 +0000 Subject: [PATCH] [Float][Fizz][Static] add `importMap` option to Fizz and Static server renderers (#27260) Import maps need to be emitted before any scripts or preloads so the browser can properly locate these resources. Unlike most scripts, importmaps are singletons meaning you can only have one per document and they must appear before any modules are loaded or preloaded. In the future there may be a way to dynamically add more mappings however the proposed API for this seems likely to be a javascript API and not an html tag. Given the unique constraints here this PR implements React's support of importMaps as the following 1. an `importMap` option accepting a plain object mapping module specifier to path is accepted in any API that renders a preamble (head content). Notably this precludes resume rendering because in resume cases the preamble should have already been produced as part of the prerender step. 2. the importMap is stringified and emitted as a `` in the preamble. 3. the importMap is escaped identically to how bootstrapScriptContent is escaped, notably, isntances of `` are escaped to avoid breaking out of the script context Users can still render importmap tags however with Float enabled this is rather pointless as most modules will be hoisted above the importmap that is rendered. In practice this means the only functional way to use import maps with React is to use this config API. DiffTrain build for [9d4582dffdea5b4dcb6a6093ea848d15423c7701](https://github.com/facebook/react/commit/9d4582dffdea5b4dcb6a6093ea848d15423c7701) --- compiled/facebook-www/REVISION | 2 +- .../ReactDOMServer-dev.classic.js | 47 ++++++++++++++++--- .../facebook-www/ReactDOMServer-dev.modern.js | 47 ++++++++++++++++--- .../ReactDOMServer-prod.classic.js | 11 ++++- .../ReactDOMServer-prod.modern.js | 11 ++++- .../ReactDOMServerStreaming-dev.modern.js | 44 ++++++++++++++--- .../ReactDOMServerStreaming-prod.modern.js | 9 ++++ 7 files changed, 148 insertions(+), 23 deletions(-) diff --git a/compiled/facebook-www/REVISION b/compiled/facebook-www/REVISION index 4edf1b4903..03a2c73226 100644 --- a/compiled/facebook-www/REVISION +++ b/compiled/facebook-www/REVISION @@ -1 +1 @@ -b4cdd3e8922713f8c9817b004a0dc51be47bc5df +9d4582dffdea5b4dcb6a6093ea848d15423c7701 diff --git a/compiled/facebook-www/ReactDOMServer-dev.classic.js b/compiled/facebook-www/ReactDOMServer-dev.classic.js index 36aeb132a9..4f269a415d 100644 --- a/compiled/facebook-www/ReactDOMServer-dev.classic.js +++ b/compiled/facebook-www/ReactDOMServer-dev.classic.js @@ -19,7 +19,7 @@ if (__DEV__) { var React = require("react"); var ReactDOM = require("react-dom"); -var ReactVersion = "18.3.0-www-classic-1747ba9c"; +var ReactVersion = "18.3.0-www-classic-dbbf9e79"; // This refers to a WWW module. var warningWWW = require("warning"); @@ -1910,7 +1910,7 @@ var scriptIntegirty = stringToPrecomputedChunk('" integrity="'); var scriptCrossOrigin = stringToPrecomputedChunk('" crossorigin="'); var endAsyncScript = stringToPrecomputedChunk('" async="">'); /** - * This escaping function is designed to work with bootstrapScriptContent only. + * This escaping function is designed to work with bootstrapScriptContent and importMap only. * because we know we are escaping the entire script. We can avoid for instance * escaping html comment string sequences that are valid javascript as well because * if there are no sebsequent '); * ensure that the script cannot be early terminated or never terminated state */ -function escapeBootstrapScriptContent(scriptText) { +function escapeBootstrapAndImportMapScriptContent(scriptText) { { checkHtmlStringCoercion(scriptText); } @@ -1932,11 +1932,16 @@ var scriptRegex = /(<\/|<)(s)(cript)/gi; var scriptReplacer = function (match, prefix, s, suffix) { return "" + prefix + (s === "s" ? "\\u0073" : "\\u0053") + suffix; -}; // Allows us to keep track of what we've already written so we can refer back to it. +}; + +var importMapScriptStart = stringToPrecomputedChunk( + '"); // Allows us to keep track of what we've already written so we can refer back to it. // if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag // is set, the server will send instructions via data attributes (instead of inline scripts) -function createRenderState$1(resumableState, nonce) { +function createRenderState$1(resumableState, nonce, importMap) { var inlineScriptWithNonce = nonce === undefined ? startInlineScript @@ -1944,6 +1949,19 @@ function createRenderState$1(resumableState, nonce) { ''); /** - * This escaping function is designed to work with bootstrapScriptContent only. + * This escaping function is designed to work with bootstrapScriptContent and importMap only. * because we know we are escaping the entire script. We can avoid for instance * escaping html comment string sequences that are valid javascript as well because * if there are no sebsequent '); * ensure that the script cannot be early terminated or never terminated state */ -function escapeBootstrapScriptContent(scriptText) { +function escapeBootstrapAndImportMapScriptContent(scriptText) { { checkHtmlStringCoercion(scriptText); } @@ -1932,11 +1932,16 @@ var scriptRegex = /(<\/|<)(s)(cript)/gi; var scriptReplacer = function (match, prefix, s, suffix) { return "" + prefix + (s === "s" ? "\\u0073" : "\\u0053") + suffix; -}; // Allows us to keep track of what we've already written so we can refer back to it. +}; + +var importMapScriptStart = stringToPrecomputedChunk( + '"); // Allows us to keep track of what we've already written so we can refer back to it. // if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag // is set, the server will send instructions via data attributes (instead of inline scripts) -function createRenderState$1(resumableState, nonce) { +function createRenderState$1(resumableState, nonce, importMap) { var inlineScriptWithNonce = nonce === undefined ? startInlineScript @@ -1944,6 +1949,19 @@ function createRenderState$1(resumableState, nonce) { ''); /** - * This escaping function is designed to work with bootstrapScriptContent only. + * This escaping function is designed to work with bootstrapScriptContent and importMap only. * because we know we are escaping the entire script. We can avoid for instance * escaping html comment string sequences that are valid javascript as well because * if there are no sebsequent '); * ensure that the script cannot be early terminated or never terminated state */ -function escapeBootstrapScriptContent(scriptText) { +function escapeBootstrapAndImportMapScriptContent(scriptText) { { checkHtmlStringCoercion(scriptText); } @@ -1929,11 +1929,16 @@ var scriptRegex = /(<\/|<)(s)(cript)/gi; var scriptReplacer = function (match, prefix, s, suffix) { return "" + prefix + (s === "s" ? "\\u0073" : "\\u0053") + suffix; -}; // Allows us to keep track of what we've already written so we can refer back to it. +}; + +var importMapScriptStart = stringToPrecomputedChunk( + '"); // Allows us to keep track of what we've already written so we can refer back to it. // if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag // is set, the server will send instructions via data attributes (instead of inline scripts) -function createRenderState(resumableState, nonce) { +function createRenderState(resumableState, nonce, importMap) { var inlineScriptWithNonce = nonce === undefined ? startInlineScript @@ -1941,6 +1946,19 @@ function createRenderState(resumableState, nonce) { '