@@ -2204,8 +2204,22 @@ var ReactDOMServerRenderer = function () {
2204
2204
// TODO: type this more strictly:
2205
2205
2206
2206
2207
- ReactDOMServerRenderer . prototype . read = function read ( bytes , cache ) {
2208
- const start = { } ;
2207
+ ReactDOMServerRenderer . prototype . read = function read ( bytes , cache ) {
2208
+ /*
2209
+ --- Component caching variables ---
2210
+ start: Tracks start index in output string and templatization data for cached components
2211
+ saveTemplates: Tracks templatized components so props can be restored in output string
2212
+ restoreProps: Restores actual props to a templatized component
2213
+ */
2214
+ const start = { } ;
2215
+ let saveTemplates ;
2216
+ const restoreProps = ( template , props , lookup ) => {
2217
+ return template . replace ( / \{ \{ [ 0 - 9 ] + \} \} / g, match => props [ lookup [ match ] ] ) ;
2218
+ } ;
2219
+
2220
+ /*
2221
+ --- Begin React 16 source code with addition of component caching logic ---
2222
+ */
2209
2223
if ( this . exhausted ) {
2210
2224
return null ;
2211
2225
}
@@ -2233,17 +2247,68 @@ var ReactDOMServerRenderer = function () {
2233
2247
{
2234
2248
setCurrentDebugStack ( this . stack ) ;
2235
2249
}
2236
-
2237
- // IF THE CHILD HAS A CACHEKEY PROPERTY ON IT
2238
- if ( child . props && child . props . cache ) {
2239
- const cacheKey = child . type . name + JSON . stringify ( child . props ) ;
2240
- if ( ! cache . get ( cacheKey ) ) {
2241
- start [ cacheKey ] = out . length ;
2242
- out += this . render ( child , frame . context , frame . domNamespace ) ;
2250
+
2251
+ /*
2252
+ --- Component caching logic ---
2253
+ */
2254
+ if ( child . props && child . props . cache ) {
2255
+ let cacheKey ;
2256
+ let isTemplate = child . props . templatized ? true : false ;
2257
+ let lookup ;
2258
+ let modifiedChild ;
2259
+ let realProps ;
2260
+
2261
+ if ( isTemplate ) {
2262
+ // Generate templatized version of props to generate appropriate cache key
2263
+ let cacheProps = Object . assign ( { } , child . props ) ;
2264
+ let templatizedProps = child . props . templatized ;
2265
+ lookup = { } ;
2266
+
2267
+ if ( typeof templatizedProps === 'string' ) templatizedProps = [ templatizedProps ] ;
2268
+
2269
+ // Generate template placeholders and lookup object for referencing prop names from placeholders
2270
+ templatizedProps . forEach ( ( templatizedProp , index ) => {
2271
+ const placeholder = `{{${ index } }}` ;
2272
+ cacheProps [ templatizedProp ] = placeholder ;
2273
+ lookup [ placeholder ] = templatizedProp ; // Move down to next if statement? (Extra work/space if not generating template)
2274
+ } ) ;
2275
+
2276
+ cacheKey = child . type . name + JSON . stringify ( cacheProps ) ;
2277
+
2278
+ // Generate modified child with placeholder props to render template
2279
+ if ( ! start [ cacheKey ] ) {
2280
+ modifiedChild = Object . assign ( { } , child ) ;
2281
+ modifiedChild . props = cacheProps ;
2282
+ realProps = child . props ;
2283
+ }
2243
2284
} else {
2244
- out += cache . get ( cacheKey ) ;
2285
+ // Generate cache key for non-templatized component from its name and props
2286
+ cacheKey = child . type . name + JSON . stringify ( child . props ) ;
2245
2287
}
2246
- } else {
2288
+
2289
+ if ( ! cache . get ( cacheKey ) ) { // Component not found in cache
2290
+ let r ;
2291
+
2292
+ // If templatized component and template hasn't been generated, render a template
2293
+ if ( ! start [ cacheKey ] && isTemplate ) {
2294
+ r = this . render ( modifiedChild , frame . context , frame . domNamespace ) ;
2295
+ start [ cacheKey ] = { startIndex : out . length , realProps, lookup } ;
2296
+ }
2297
+ // Otherwise, render with actual props
2298
+ else r = this . render ( child , frame . context , frame . domNamespace ) ;
2299
+
2300
+ // For simple (non-template) caching, save start index of component in output string
2301
+ if ( ! isTemplate ) start [ cacheKey ] = out . length ;
2302
+
2303
+ out += r ;
2304
+ } else { // Component found in cache
2305
+ let r = cache . get ( cacheKey ) ;
2306
+ let restoredTemplate ;
2307
+ if ( isTemplate ) restoredTemplate = restoreProps ( r , realProps , lookup ) ;
2308
+ out += restoredTemplate ? restoredTemplate : r ;
2309
+ }
2310
+ } else {
2311
+ // Normal rendering for non-cached components
2247
2312
out += this . render ( child , frame . context , frame . domNamespace ) ;
2248
2313
}
2249
2314
{
@@ -2252,25 +2317,51 @@ var ReactDOMServerRenderer = function () {
2252
2317
}
2253
2318
}
2254
2319
2320
+ /*
2321
+ --- After initial render of cacheable components, recover from output string and store in cache ---
2322
+ */
2255
2323
for ( let component in start ) {
2256
2324
let tagStack = [ ] ;
2257
2325
let tagStart ;
2258
2326
let tagEnd ;
2327
+ let componentStart = ( typeof start [ component ] === 'object' ) ? start [ component ] . startIndex : start [ component ] ;
2259
2328
2260
2329
do {
2261
- if ( ! tagStart ) tagStart = start [ component ] ;
2262
- else tagStart = ( out [ tagEnd ] === '<' ) ? tagEnd : out . indexOf ( '<' , tagEnd )
2330
+ if ( ! tagStart ) tagStart = componentStart ;
2331
+ else tagStart = ( out [ tagEnd ] === '<' ) ? tagEnd : out . indexOf ( '<' , tagEnd ) ;
2263
2332
tagEnd = out . indexOf ( '>' , tagStart ) + 1 ;
2264
- // Skip stack logic for void/self-closing elements
2265
- if ( out [ tagEnd - 2 ] !== '/' ) {
2333
+ // Skip stack logic for void/self-closing elements and HTML comments
2334
+ // Note: Does not account for tags inside HTML comments
2335
+ if ( out [ tagEnd - 2 ] !== '/' && out [ tagStart + 1 ] !== '!' ) {
2266
2336
// Push opening tags onto stack; pop closing tags off of stack
2267
2337
if ( out [ tagStart + 1 ] !== '/' ) tagStack . push ( out . slice ( tagStart , tagEnd ) ) ;
2268
2338
else tagStack . pop ( ) ;
2269
2339
}
2270
2340
} while ( tagStack . length !== 0 ) ;
2271
-
2272
- // cache component by slicing 'out'
2273
- cache . set ( component , out . slice ( start [ component ] , tagEnd ) ) ;
2341
+ // Cache component by slicing 'out'
2342
+ const cachedComponent = out . slice ( componentStart , tagEnd ) ;
2343
+ if ( typeof start [ component ] === 'object' ) {
2344
+ if ( ! saveTemplates ) saveTemplates = [ ] ;
2345
+ saveTemplates . push ( start [ component ] ) ;
2346
+ start [ component ] . endIndex = tagEnd ;
2347
+ }
2348
+ cache . set ( component , cachedComponent ) ;
2349
+ }
2350
+
2351
+ // After caching all cacheable components, restore props to templates
2352
+ if ( saveTemplates ) {
2353
+ let outCopy = out ;
2354
+ out = '' ;
2355
+ let bookmark = 0 ;
2356
+ saveTemplates . sort ( ( a , b ) => a . startIndex - b . startIndex ) ;
2357
+ // Rebuild output string with actual props
2358
+ saveTemplates . forEach ( savedTemplate => {
2359
+ out += outCopy . substring ( bookmark , savedTemplate . startIndex )
2360
+ bookmark = savedTemplate . endIndex ;
2361
+ out += restoreProps ( outCopy . slice ( savedTemplate . startIndex , savedTemplate . endIndex ) ,
2362
+ savedTemplate . realProps , savedTemplate . lookup ) ;
2363
+ } ) ;
2364
+ out += outCopy . substring ( bookmark , outCopy . length ) ;
2274
2365
}
2275
2366
return out ;
2276
2367
} ;
@@ -2647,4 +2738,4 @@ var server_node = ReactDOMServer['default'] ? ReactDOMServer['default'] : ReactD
2647
2738
2648
2739
module . exports = server_node ;
2649
2740
} ) ( ) ;
2650
- }
2741
+ }
0 commit comments