diff --git a/packages/next/export/index.ts b/packages/next/export/index.ts index 808ce043b060c70..5319e8067557d6d 100644 --- a/packages/next/export/index.ts +++ b/packages/next/export/index.ts @@ -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 diff --git a/packages/next/export/worker.js b/packages/next/export/worker.js index 7f4ec2220cb0350..eeb77a309e44177 100644 --- a/packages/next/export/worker.js +++ b/packages/next/export/worker.js @@ -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({ ) } - await validateAmp(ampHtml, page + '?amp=1') + if (!curRenderOpts.ampSkipValidation) { + await validateAmp(ampHtml, page + '?amp=1') + } await mkdir(ampBaseDir, { recursive: true }) await writeFileP(ampHtmlFilepath, ampHtml, 'utf8') } diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index 4d369bdb7b88b7d..eeeb81c5737c7f1 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -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 @@ -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 diff --git a/packages/next/next-server/server/optimize-amp.ts b/packages/next/next-server/server/optimize-amp.ts index c8c7e291c368282..6e7d3345c9fce69 100644 --- a/packages/next/next-server/server/optimize-amp.ts +++ b/packages/next/next-server/server/optimize-amp.ts @@ -1,10 +1,13 @@ -export default async function optimize(html: string): Promise { +export default async function optimize( + html: string, + config: any +): Promise { 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) } diff --git a/packages/next/next-server/server/render.tsx b/packages/next/next-server/server/render.tsx index 920b6b70e130914..58c3727de54e806 100644 --- a/packages/next/next-server/server/render.tsx +++ b/packages/next/next-server/server/render.tsx @@ -146,6 +146,8 @@ export type RenderOptsPartial = { hybridAmp?: boolean ErrorDebug?: React.ComponentType<{ error: Error }> ampValidator?: (html: string, pathname: string) => Promise + ampSkipValidation?: boolean + ampOptimizerConfig?: { [key: string]: any } documentMiddlewareEnabled?: boolean isDataReq?: boolean params?: ParsedUrlQuery @@ -743,9 +745,9 @@ export async function renderToHTML( html.substring(0, ampRenderIndex) + `${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) } } diff --git a/packages/next/server/next-dev-server.ts b/packages/next/server/next-dev-server.ts index a8450ca036891b0..6b9169800e48be8 100644 --- a/packages/next/server/next-dev-server.ts +++ b/packages/next/server/next-dev-server.ts @@ -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 diff --git a/test/integration/amphtml-custom-optimizer/next.config.js b/test/integration/amphtml-custom-optimizer/next.config.js new file mode 100644 index 000000000000000..44c063c99fe03b6 --- /dev/null +++ b/test/integration/amphtml-custom-optimizer/next.config.js @@ -0,0 +1,12 @@ +module.exports = { + experimental: { + amp: { + optimizer: { + ampRuntimeVersion: '001515617716922', + ampUrlPrefix: '/amp', + verbose: true, + }, + skipValidation: true, + }, + }, +} diff --git a/test/integration/amphtml-custom-optimizer/pages/dynamic.js b/test/integration/amphtml-custom-optimizer/pages/dynamic.js new file mode 100644 index 000000000000000..ed59c6f760b9411 --- /dev/null +++ b/test/integration/amphtml-custom-optimizer/pages/dynamic.js @@ -0,0 +1,20 @@ +export const config = { + amp: true, +} + +const Dynamic = props => ( + +) + +Dynamic.getInitialProps = () => { + return { + src: 'https://amp.dev/static/samples/img/story_dog2_portrait.jpg', + } +} + +export default Dynamic diff --git a/test/integration/amphtml-custom-optimizer/pages/index.js b/test/integration/amphtml-custom-optimizer/pages/index.js new file mode 100644 index 000000000000000..cca9d141c938108 --- /dev/null +++ b/test/integration/amphtml-custom-optimizer/pages/index.js @@ -0,0 +1,12 @@ +export const config = { + amp: true, +} + +export default () => ( + +) diff --git a/test/integration/amphtml-custom-optimizer/test/index.test.js b/test/integration/amphtml-custom-optimizer/test/index.test.js new file mode 100644 index 000000000000000..dd7992ae1d34e60 --- /dev/null +++ b/test/integration/amphtml-custom-optimizer/test/index.test.js @@ -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"') + }) +})