Skip to content

Commit

Permalink
optimize html when shell stream is buffered
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed May 11, 2022
1 parent 7901ae0 commit 4bf5cf0
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 76 deletions.
12 changes: 6 additions & 6 deletions packages/next/server/node-web-streams-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,18 @@ export function decodeText(input?: Uint8Array, textDecoder?: TextDecoder) {
: new TextDecoder().decode(input)
}

export function createBufferedTransformStream(): TransformStream<
Uint8Array,
Uint8Array
> {
export function createBufferedTransformStream(
transform: (v: string) => string | Promise<string> = (v) => v
): TransformStream<Uint8Array, Uint8Array> {
let bufferedString = ''
let pendingFlush: Promise<void> | null = null

const flushBuffer = (controller: TransformStreamDefaultController) => {
if (!pendingFlush) {
pendingFlush = new Promise((resolve) => {
setTimeout(() => {
controller.enqueue(encodeText(bufferedString))
setTimeout(async () => {
const buffered = await transform(bufferedString)
controller.enqueue(encodeText(buffered))
bufferedString = ''
pendingFlush = null
resolve()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,20 @@
import type { RenderOpts } from './render'
import type { FontManifest } from './font-utils'
import { parse, HTMLElement } from 'next/dist/compiled/node-html-parser'
import { OPTIMIZED_FONT_PROVIDERS } from './constants'
import { OPTIMIZED_FONT_PROVIDERS } from '../shared/lib/constants'
import { nonNullable } from '../lib/non-nullable'

// const MIDDLEWARE_TIME_BUDGET = parseInt(process.env.__POST_PROCESS_MIDDLEWARE_TIME_BUDGET || '', 10) || 10
let optimizeAmp: typeof import('./optimize-amp').default | undefined
let getFontDefinitionFromManifest:
| typeof import('./font-utils').getFontDefinitionFromManifest
| undefined

if (process.env.NEXT_RUNTIME !== 'edge') {
require('./node-polyfill-web-streams')
optimizeAmp = require('./optimize-amp').default
getFontDefinitionFromManifest =
require('./font-utils').getFontDefinitionFromManifest
}

type postProcessOptions = {
optimizeFonts: boolean
Expand Down Expand Up @@ -165,6 +178,76 @@ class FontOptimizerMiddleware implements PostProcessMiddleware {
}
}

async function postOptimizeHTML(
pathname: string,
html: string,
renderOpts: RenderOpts,
{ inAmpMode, hybridAmp }: { inAmpMode: boolean; hybridAmp: boolean }
) {
const postProcessors: Array<(html: string) => Promise<string>> = [
process.env.NEXT_RUNTIME !== 'edge' && inAmpMode
? async (html: string) => {
html = await optimizeAmp!(html, renderOpts.ampOptimizerConfig)
if (!renderOpts.ampSkipValidation && renderOpts.ampValidator) {
await renderOpts.ampValidator(html, pathname)
}
return html
}
: null,
process.env.NEXT_RUNTIME !== 'edge' && process.env.__NEXT_OPTIMIZE_FONTS
? async (html: string) => {
const createFontDefinitionGetter =
(fontManifest?: FontManifest) =>
(url: string): string => {
if (fontManifest) {
return getFontDefinitionFromManifest!(url, fontManifest)
}
return ''
}
return await processHTML(
html,
{
getFontDefinition: createFontDefinitionGetter(
renderOpts.fontManifest
),
},
{
optimizeFonts: renderOpts.optimizeFonts,
}
)
}
: null,
process.env.NEXT_RUNTIME !== 'edge' && renderOpts.optimizeCss
? async (html: string) => {
// eslint-disable-next-line import/no-extraneous-dependencies
const Critters = require('critters')
const cssOptimizer = new Critters({
ssrMode: true,
reduceInlineStyles: false,
path: renderOpts.distDir,
publicPath: `${renderOpts.assetPrefix}/_next/`,
preload: 'media',
fonts: false,
...renderOpts.optimizeCss,
})
return await cssOptimizer.process(html)
}
: null,
inAmpMode || hybridAmp
? async (html: string) => {
return html.replace(/&amp;amp=1/g, '&amp=1')
}
: null,
].filter(nonNullable)

for (const postProcessor of postProcessors) {
if (postProcessor) {
html = await postProcessor(html)
}
}
return html
}

// Initialization
registerPostProcessor(
'Inline-Fonts',
Expand All @@ -174,4 +257,4 @@ registerPostProcessor(
(options) => options.optimizeFonts || process.env.__NEXT_OPTIMIZE_FONTS
)

export default processHTML
export { processHTML, postOptimizeHTML }
77 changes: 10 additions & 67 deletions packages/next/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,10 @@ import { FlushEffectsContext } from '../shared/lib/flush-effects'
import { interopDefault } from '../lib/interop-default'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { urlQueryToSearchParams } from '../shared/lib/router/utils/querystring'
import { postOptimizeHTML } from './post-process'

let optimizeAmp: typeof import('./optimize-amp').default
let getFontDefinitionFromManifest: typeof import('./font-utils').getFontDefinitionFromManifest
let tryGetPreviewData: typeof import('./api-utils/node').tryGetPreviewData
let warn: typeof import('../build/output/log').warn
let postProcess: typeof import('../shared/lib/post-process').default

const DOCTYPE = '<!DOCTYPE html>'
const ReactDOMServer = process.env.__NEXT_REACT_ROOT
Expand All @@ -97,12 +95,8 @@ const ReactDOMServer = process.env.__NEXT_REACT_ROOT

if (process.env.NEXT_RUNTIME !== 'edge') {
require('./node-polyfill-web-streams')
optimizeAmp = require('./optimize-amp').default
getFontDefinitionFromManifest =
require('./font-utils').getFontDefinitionFromManifest
tryGetPreviewData = require('./api-utils/node').tryGetPreviewData
warn = require('../build/output/log').warn
postProcess = require('../shared/lib/post-process').default
} else {
warn = console.warn.bind(console)
}
Expand Down Expand Up @@ -535,13 +529,6 @@ export async function renderToHTML(
})
}

const getFontDefinition = (url: string): string => {
if (fontManifest) {
return getFontDefinitionFromManifest(url, fontManifest)
}
return ''
}

let renderServerComponentData = isServerComponent
? query.__flight__ !== undefined
: false
Expand Down Expand Up @@ -1751,62 +1738,18 @@ export async function renderToHTML(
return RenderResult.fromStatic((renderOpts as any).pageData)
}

const postProcessors: Array<((html: string) => Promise<string>) | null> = [
inAmpMode
? async (html: string) => {
html = await optimizeAmp(html, renderOpts.ampOptimizerConfig)
if (!renderOpts.ampSkipValidation && renderOpts.ampValidator) {
await renderOpts.ampValidator(html, pathname)
}
return html
}
: null,
process.env.NEXT_RUNTIME !== 'edge' && process.env.__NEXT_OPTIMIZE_FONTS
? async (html: string) => {
return await postProcess(
html,
{ getFontDefinition },
{
optimizeFonts: renderOpts.optimizeFonts,
}
)
}
: null,
process.env.NEXT_RUNTIME !== 'edge' && renderOpts.optimizeCss
? async (html: string) => {
// eslint-disable-next-line import/no-extraneous-dependencies
const Critters = require('critters')
const cssOptimizer = new Critters({
ssrMode: true,
reduceInlineStyles: false,
path: renderOpts.distDir,
publicPath: `${renderOpts.assetPrefix}/_next/`,
preload: 'media',
fonts: false,
...renderOpts.optimizeCss,
})
return await cssOptimizer.process(html)
}
: null,
inAmpMode || hybridAmp
? async (html: string) => {
return html.replace(/&amp;amp=1/g, '&amp=1')
}
: null,
].filter(Boolean)

if (postProcessors.length > 0) {
let html = await streamToString(chainStreams(streams))
for (const postProcessor of postProcessors) {
if (postProcessor) {
html = await postProcessor(html)
}
}
return new RenderResult(html)
const postOptimize = (html: string) =>
postOptimizeHTML(pathname, html, renderOpts, { inAmpMode, hybridAmp })
if (generateStaticHTML) {
const html = await streamToString(chainStreams(streams))
const optimizedHtml = await postOptimize(html)
return new RenderResult(optimizedHtml)
}

return new RenderResult(
chainStreams(streams).pipeThrough(createBufferedTransformStream())
chainStreams(streams).pipeThrough(
createBufferedTransformStream(postOptimize)
)
)
}

Expand Down

0 comments on commit 4bf5cf0

Please sign in to comment.