Add custom amp optimizer and skip validation mode (#10705)

* Add custom amp optimizer and skip validation mode

* fix: type

* fix worker lint errors
This commit is contained in:
Yosuke Furukawa 2020-03-24 17:31:04 +09:00 committed by GitHub
parent 1690a756e2
commit aadb31fa5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 116 additions and 7 deletions

View file

@ -234,6 +234,8 @@ export default async function(
canonicalBase: nextConfig.amp?.canonicalBase || '',
isModern: nextConfig.experimental.modern,
ampValidatorPath: nextConfig.experimental.amp?.validator || undefined,
ampSkipValidation: nextConfig.experimental.amp?.skipValidation || false,
ampOptimizerConfig: nextConfig.experimental.amp?.optimizer || undefined,
}
const { serverRuntimeConfig, publicRuntimeConfig } = nextConfig

View file

@ -223,7 +223,7 @@ export default async function({
}
}
if (curRenderOpts.inAmpMode) {
if (curRenderOpts.inAmpMode && !curRenderOpts.ampSkipValidation) {
await validateAmp(html, path, curRenderOpts.ampValidatorPath)
} else if (curRenderOpts.hybridAmp) {
// we need to render the AMP version
@ -252,7 +252,9 @@ export default async function({
)
}
if (!curRenderOpts.ampSkipValidation) {
await validateAmp(ampHtml, page + '?amp=1')
}
await mkdir(ampBaseDir, { recursive: true })
await writeFileP(ampHtmlFilepath, ampHtml, 'utf8')
}

View file

@ -119,6 +119,7 @@ export default class Server {
dev?: boolean
previewProps: __ApiPreviewProps
customServer?: boolean
ampOptimizerConfig?: { [key: string]: any }
}
private compression?: Middleware
private onErrorMiddleware?: ({ err }: { err: Error }) => Promise<void>
@ -172,6 +173,7 @@ export default class Server {
generateEtags,
previewProps: this.getPreviewProps(),
customServer: customServer === true ? true : undefined,
ampOptimizerConfig: this.nextConfig.experimental.amp?.optimizer,
}
// Only the `publicRuntimeConfig` key is exposed to the client side

View file

@ -1,10 +1,13 @@
export default async function optimize(html: string): Promise<string> {
export default async function optimize(
html: string,
config: any
): Promise<string> {
let AmpOptimizer
try {
AmpOptimizer = require('@ampproject/toolbox-optimizer')
} catch (_) {
return html
}
const optimizer = AmpOptimizer.create()
return optimizer.transformHtml(html)
const optimizer = AmpOptimizer.create(config)
return optimizer.transformHtml(html, config)
}

View file

@ -146,6 +146,8 @@ export type RenderOptsPartial = {
hybridAmp?: boolean
ErrorDebug?: React.ComponentType<{ error: Error }>
ampValidator?: (html: string, pathname: string) => Promise<void>
ampSkipValidation?: boolean
ampOptimizerConfig?: { [key: string]: any }
documentMiddlewareEnabled?: boolean
isDataReq?: boolean
params?: ParsedUrlQuery
@ -743,9 +745,9 @@ export async function renderToHTML(
html.substring(0, ampRenderIndex) +
`<!-- __NEXT_DATA__ -->${docProps.html}` +
html.substring(ampRenderIndex + AMP_RENDER_TARGET.length)
html = await optimizeAmp(html)
html = await optimizeAmp(html, renderOpts.ampOptimizerConfig)
if (renderOpts.ampValidator) {
if (!renderOpts.ampSkipValidation && renderOpts.ampValidator) {
await renderOpts.ampValidator(html, pathname)
}
}

View file

@ -54,6 +54,8 @@ export default class DevServer extends Server {
this.devReady = new Promise(resolve => {
this.setDevReady = resolve
})
;(this.renderOpts as any).ampSkipValidation =
this.nextConfig.experimental?.amp?.skipValidation ?? false
;(this.renderOpts as any).ampValidator = (
html: string,
pathname: string

View file

@ -0,0 +1,12 @@
module.exports = {
experimental: {
amp: {
optimizer: {
ampRuntimeVersion: '001515617716922',
ampUrlPrefix: '/amp',
verbose: true,
},
skipValidation: true,
},
},
}

View file

@ -0,0 +1,20 @@
export const config = {
amp: true,
}
const Dynamic = props => (
<amp-img
width="500"
height="500"
layout="responsive"
src={props.src}
></amp-img>
)
Dynamic.getInitialProps = () => {
return {
src: 'https://amp.dev/static/samples/img/story_dog2_portrait.jpg',
}
}
export default Dynamic

View file

@ -0,0 +1,12 @@
export const config = {
amp: true,
}
export default () => (
<amp-twitter
width="500"
height="500"
layout="responsive"
data-tweetid="1159145442896166912"
></amp-twitter>
)

View file

@ -0,0 +1,52 @@
/* eslint-env jest */
/* global jasmine */
import { join } from 'path'
import {
nextBuild,
findPort,
nextStart,
killApp,
renderViaHTTP,
} from 'next-test-utils'
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 1
let app
let appPort
const appDir = join(__dirname, '../')
describe('AMP Custom Optimizer', () => {
it('should build and start for static page', async () => {
const { code } = await nextBuild(appDir, undefined)
expect(code).toBe(0)
appPort = await findPort()
app = await nextStart(appDir, appPort)
const html = await renderViaHTTP(appPort, '/')
await killApp(app)
expect(html).toContain(
'amp-twitter width="500" height="500" layout="responsive" data-tweetid="1159145442896166912"'
)
expect(html).toContain('i-amphtml-version="001515617716922"')
expect(html).toContain('script async src="/amp/rtv/001515617716922/v0.js"')
})
it('should build and start for dynamic page', async () => {
const { code } = await nextBuild(appDir, undefined)
expect(code).toBe(0)
appPort = await findPort()
app = await nextStart(appDir, appPort)
const html = await renderViaHTTP(appPort, '/dynamic')
await killApp(app)
expect(html).toContain(
'amp-img width="500" height="500" layout="responsive" src="https://amp.dev/static/samples/img/story_dog2_portrait.jpg"'
)
expect(html).toContain('i-amphtml-version="001515617716922"')
expect(html).toContain('script async src="/amp/rtv/001515617716922/v0.js"')
})
})