@@ -139,6 +139,7 @@ export type RenderState = {
139139 // Hoistable chunks
140140 charsetChunks : Array < Chunk | PrecomputedChunk > ,
141141 preconnectChunks : Array < Chunk | PrecomputedChunk > ,
142+ importMapChunks : Array < Chunk | PrecomputedChunk > ,
142143 preloadChunks : Array < Chunk | PrecomputedChunk > ,
143144 hoistableChunks : Array < Chunk | PrecomputedChunk > ,
144145
@@ -205,7 +206,7 @@ const scriptCrossOrigin = stringToPrecomputedChunk('" crossorigin="');
205206const endAsyncScript = stringToPrecomputedChunk ( '" async=""></script>' ) ;
206207
207208/**
208- * This escaping function is designed to work with bootstrapScriptContent only.
209+ * This escaping function is designed to work with bootstrapScriptContent and importMap only.
209210 * because we know we are escaping the entire script. We can avoid for instance
210211 * escaping html comment string sequences that are valid javascript as well because
211212 * if there are no sebsequent <script sequences the html parser will never enter
@@ -214,7 +215,7 @@ const endAsyncScript = stringToPrecomputedChunk('" async=""></script>');
214215 * While untrusted script content should be made safe before using this api it will
215216 * ensure that the script cannot be early terminated or never terminated state
216217 */
217- function escapeBootstrapScriptContent ( scriptText : string ) {
218+ function escapeBootstrapAndImportMapScriptContent ( scriptText : string ) {
218219 if ( __DEV__ ) {
219220 checkHtmlStringCoercion ( scriptText ) ;
220221 }
@@ -237,12 +238,19 @@ export type ExternalRuntimeScript = {
237238 src : string ,
238239 chunks : Array < Chunk | PrecomputedChunk > ,
239240} ;
241+
242+ const importMapScriptStart = stringToPrecomputedChunk (
243+ '<script type="importmap">' ,
244+ ) ;
245+ const importMapScriptEnd = stringToPrecomputedChunk ( '</script>' ) ;
246+
240247// Allows us to keep track of what we've already written so we can refer back to it.
241248// if passed externalRuntimeConfig and the enableFizzExternalRuntime feature flag
242249// is set, the server will send instructions via data attributes (instead of inline scripts)
243250export function createRenderState (
244251 resumableState : ResumableState ,
245252 nonce : string | void ,
253+ importMap : { [ string ] : string } | void ,
246254) : RenderState {
247255 const inlineScriptWithNonce =
248256 nonce === undefined
@@ -251,6 +259,17 @@ export function createRenderState(
251259 '<script nonce="' + escapeTextForBrowser ( nonce ) + '">' ,
252260 ) ;
253261 const idPrefix = resumableState . idPrefix ;
262+ const importMapChunks : Array < Chunk | PrecomputedChunk > = [ ] ;
263+ if ( importMap !== undefined ) {
264+ const map = importMap ;
265+ importMapChunks . push ( importMapScriptStart ) ;
266+ importMapChunks . push (
267+ stringToChunk (
268+ escapeBootstrapAndImportMapScriptContent ( JSON . stringify ( map ) ) ,
269+ ) ,
270+ ) ;
271+ importMapChunks . push ( importMapScriptEnd ) ;
272+ }
254273 return {
255274 placeholderPrefix : stringToPrecomputedChunk ( idPrefix + 'P:' ) ,
256275 segmentPrefix : stringToPrecomputedChunk ( idPrefix + 'S:' ) ,
@@ -260,6 +279,7 @@ export function createRenderState(
260279 headChunks : null ,
261280 charsetChunks : [ ] ,
262281 preconnectChunks : [ ] ,
282+ importMapChunks,
263283 preloadChunks : [ ] ,
264284 hoistableChunks : [ ] ,
265285 nonce,
@@ -290,7 +310,9 @@ export function createResumableState(
290310 ) ;
291311 bootstrapChunks . push (
292312 inlineScriptWithNonce ,
293- stringToChunk ( escapeBootstrapScriptContent ( bootstrapScriptContent ) ) ,
313+ stringToChunk (
314+ escapeBootstrapAndImportMapScriptContent ( bootstrapScriptContent ) ,
315+ ) ,
294316 endInlineScript ,
295317 ) ;
296318 }
@@ -4342,6 +4364,12 @@ export function writePreamble(
43424364 // Flush unblocked stylesheets by precedence
43434365 resumableState . precedences . forEach ( flushAllStylesInPreamble , destination ) ;
43444366
4367+ const importMapChunks = renderState . importMapChunks ;
4368+ for ( i = 0 ; i < importMapChunks . length ; i ++ ) {
4369+ writeChunk ( destination , importMapChunks [ i ] ) ;
4370+ }
4371+ importMapChunks . length = 0 ;
4372+
43454373 resumableState . bootstrapScripts . forEach ( flushResourceInPreamble , destination ) ;
43464374
43474375 resumableState . scripts . forEach ( flushResourceInPreamble , destination ) ;
@@ -4415,6 +4443,11 @@ export function writeHoistables(
44154443 // but we want to kick off preloading as soon as possible
44164444 resumableState . precedences . forEach ( preloadLateStyles , destination ) ;
44174445
4446+ // We only hoist importmaps that are configured through createResponse and that will
4447+ // always flush in the preamble. Generally we don't expect people to render them as
4448+ // tags when using React but if you do they are going to be treated like regular inline
4449+ // scripts and flush after other hoistables which is problematic
4450+
44184451 // bootstrap scripts should flush above script priority but these can only flush in the preamble
44194452 // so we elide the code here for performance
44204453
0 commit comments