提交 12cbb934 编写于 作者: J JJ Kasper 提交者: Joe Haddad

Add support for creating AMP pages (#6706)

* Add support for .amp.js pages and
resolving /page?amp=1 to page.amp.js

* Update amp tests

* Update example and clean up amp page resolving

* Add nested amp test

* page => normalizedPage

* Add type to page options

* Add handling of amp with all pageExtensions
and normalize page

* Make sure findPageFile only falls back to
amp if enabled
上级 ff5c8465
import Head from 'next/head'
import { useAmp } from 'next/amp'
import Byline from '../components/Byline'
export default () => {
const isAmp = useAmp()
return (
<div>
<Head>
<title>The Cat</title>
</Head>
<h1>The Cat</h1>
<Byline author='Meow Meow Fuzzyface' />
<p>
<a href={isAmp ? '/cat' : '/cat?amp=1'}>
{isAmp ? 'View Non-AMP' : 'View AMP'} Version
</a>
</p>
<p className='caption'>Woooooooooooof</p>
<p>
Wafer donut candy soufflé{' '}
<a href={isAmp ? '/?amp=1' : '/'}>lemon drops</a> icing. Marzipan gummi
bears pie danish lollipop pudding powder gummi bears sweet. Pie sweet
roll sweet roll topping chocolate bar dragée pudding chocolate cake.
Croissant sweet chocolate bar cheesecake candy canes. Tootsie roll icing
macaroon bonbon cupcake apple pie candy canes biscuit candy canes.
Jujubes jelly liquorice toffee gingerbread. Candy tootsie roll macaroon
chocolate bar icing sugar plum pie. Icing gummies chocolate bar
chocolate marzipan bonbon cookie chocolate tart. Caramels danish halvah
croissant. Cheesecake cookie tootsie roll ice cream. Powder dessert
carrot cake muffin tiramisu lemon drops liquorice topping brownie.
Soufflé chocolate cake croissant cupcake jelly.
</p>
<p>
Muffin gummies dessert cheesecake candy canes. Candy canes danish cotton
candy tart dessert powder bear claw marshmallow. Muffin chocolate
marshmallow danish. Chocolate bar biscuit cake tiramisu. Topping sweet
brownie jujubes powder marzipan. Croissant wafer bonbon chupa chups cake
cake marzipan caramels jujubes. Cupcake cheesecake sweet roll
marshmallow lollipop danish jujubes jelly icing. Apple pie chupa chups
lollipop jelly-o cheesecake jelly beans cake dessert. Tootsie roll
tootsie roll bonbon pastry croissant gummi bears cake cake. Fruitcake
sugar plum halvah gingerbread cookie pastry chupa chups wafer lemon
drops. Marshmallow liquorice oat cake lollipop. Lemon drops oat cake
halvah liquorice danish powder cupcake soufflé. Cake tart topping
jelly-o tart sugar plum. Chocolate bar cookie wafer tootsie roll candy
cotton candy toffee pie donut.
</p>
<p>
Ice cream lollipop marshmallow tiramisu jujubes croissant. Bear claw
lemon drops marzipan candy bonbon cupcake powder. Candy canes cheesecake
bear claw pastry cake donut jujubes. Icing tart jelly-o soufflé bonbon
apple pie. Cheesecake pie chupa chups toffee powder. Bonbon lemon drops
carrot cake pudding candy halvah cheesecake lollipop cupcake. Pudding
marshmallow fruitcake. Gummi bears bonbon chupa chups lemon drops. Wafer
dessert gummies gummi bears biscuit donut tiramisu gummi bears brownie.
Tootsie roll liquorice bonbon cookie. Sesame snaps chocolate bar cake
croissant chupa chups cheesecake gingerbread tiramisu jelly. Cheesecake
ice cream muffin lollipop gummies. Sesame snaps jelly beans sweet bear
claw tart.
</p>
<p>
Sweet topping chupa chups chocolate cake jelly-o liquorice danish.
Pastry jelly beans apple pie dessert pastry lemon drops marzipan
gummies. Jelly beans macaroon bear claw cotton candy. Toffee sweet
lollipop toffee oat cake. Jelly-o oat cake fruitcake chocolate bar
sweet. Lemon drops gummies chocolate cake lollipop bear claw croissant
danish icing. Chocolate bar donut brownie chocolate cake lemon drops
chocolate bar. Cake fruitcake pudding chocolate apple pie. Brownie
tiramisu chocolate macaroon lemon drops wafer soufflé jujubes icing.
Cheesecake tiramisu cake macaroon tart lollipop donut. Gummi bears
dragée pudding bear claw. Muffin cake cupcake candy canes. Soufflé candy
canes biscuit. Macaroon gummies danish.
</p>
<p>
Cupcake cupcake tart. Cotton candy danish candy canes oat cake ice cream
candy canes powder wafer. Chocolate sesame snaps oat cake dragée
cheesecake. Sesame snaps marshmallow topping liquorice cookie
marshmallow. Liquorice pudding chocolate bar. Cake powder brownie
fruitcake. Carrot cake dessert marzipan sugar plum cupcake cheesecake
pastry. Apple pie macaroon ice cream fruitcake apple pie cookie. Tootsie
roll ice cream oat cake cheesecake donut cheesecake bear claw. Sesame
snaps marzipan jelly beans chocolate tootsie roll. Chocolate bar donut
dragée ice cream biscuit. Pie candy canes muffin candy canes ice cream
tiramisu.
</p>
</div>
)
}
import Head from 'next/head'
import { useAmp } from 'next/amp'
import Byline from '../../components/Byline'
export default () => {
const isAmp = useAmp()
return (
<div>
<Head>
<title>The Duck</title>
</Head>
<h1>The Duck</h1>
<Byline author='Meow Meow Fuzzyface' />
<p>
<a href={isAmp ? '/duck' : '/duck?amp=1'}>
{isAmp ? 'View Non-AMP' : 'View AMP'} Version
</a>
</p>
<p className='caption'>Woooooooooooof</p>
<p>
Wafer donut candy soufflé{' '}
<a href={isAmp ? '/?amp=1' : '/'}>lemon drops</a> icing. Marzipan gummi
bears pie danish lollipop pudding powder gummi bears sweet. Pie sweet
roll sweet roll topping chocolate bar dragée pudding chocolate cake.
Croissant sweet chocolate bar cheesecake candy canes. Tootsie roll icing
macaroon bonbon cupcake apple pie candy canes biscuit candy canes.
Jujubes jelly liquorice toffee gingerbread. Candy tootsie roll macaroon
chocolate bar icing sugar plum pie. Icing gummies chocolate bar
chocolate marzipan bonbon cookie chocolate tart. Caramels danish halvah
croissant. Cheesecake cookie tootsie roll ice cream. Powder dessert
carrot cake muffin tiramisu lemon drops liquorice topping brownie.
Soufflé chocolate cake croissant cupcake jelly.
</p>
<p>
Muffin gummies dessert cheesecake candy canes. Candy canes danish cotton
candy tart dessert powder bear claw marshmallow. Muffin chocolate
marshmallow danish. Chocolate bar biscuit cake tiramisu. Topping sweet
brownie jujubes powder marzipan. Croissant wafer bonbon chupa chups cake
cake marzipan caramels jujubes. Cupcake cheesecake sweet roll
marshmallow lollipop danish jujubes jelly icing. Apple pie chupa chups
lollipop jelly-o cheesecake jelly beans cake dessert. Tootsie roll
tootsie roll bonbon pastry croissant gummi bears cake cake. Fruitcake
sugar plum halvah gingerbread cookie pastry chupa chups wafer lemon
drops. Marshmallow liquorice oat cake lollipop. Lemon drops oat cake
halvah liquorice danish powder cupcake soufflé. Cake tart topping
jelly-o tart sugar plum. Chocolate bar cookie wafer tootsie roll candy
cotton candy toffee pie donut.
</p>
<p>
Ice cream lollipop marshmallow tiramisu jujubes croissant. Bear claw
lemon drops marzipan candy bonbon cupcake powder. Candy canes cheesecake
bear claw pastry cake donut jujubes. Icing tart jelly-o soufflé bonbon
apple pie. Cheesecake pie chupa chups toffee powder. Bonbon lemon drops
carrot cake pudding candy halvah cheesecake lollipop cupcake. Pudding
marshmallow fruitcake. Gummi bears bonbon chupa chups lemon drops. Wafer
dessert gummies gummi bears biscuit donut tiramisu gummi bears brownie.
Tootsie roll liquorice bonbon cookie. Sesame snaps chocolate bar cake
croissant chupa chups cheesecake gingerbread tiramisu jelly. Cheesecake
ice cream muffin lollipop gummies. Sesame snaps jelly beans sweet bear
claw tart.
</p>
<p>
Sweet topping chupa chups chocolate cake jelly-o liquorice danish.
Pastry jelly beans apple pie dessert pastry lemon drops marzipan
gummies. Jelly beans macaroon bear claw cotton candy. Toffee sweet
lollipop toffee oat cake. Jelly-o oat cake fruitcake chocolate bar
sweet. Lemon drops gummies chocolate cake lollipop bear claw croissant
danish icing. Chocolate bar donut brownie chocolate cake lemon drops
chocolate bar. Cake fruitcake pudding chocolate apple pie. Brownie
tiramisu chocolate macaroon lemon drops wafer soufflé jujubes icing.
Cheesecake tiramisu cake macaroon tart lollipop donut. Gummi bears
dragée pudding bear claw. Muffin cake cupcake candy canes. Soufflé candy
canes biscuit. Macaroon gummies danish.
</p>
<p>
Cupcake cupcake tart. Cotton candy danish candy canes oat cake ice cream
candy canes powder wafer. Chocolate sesame snaps oat cake dragée
cheesecake. Sesame snaps marshmallow topping liquorice cookie
marshmallow. Liquorice pudding chocolate bar. Cake powder brownie
fruitcake. Carrot cake dessert marzipan sugar plum cupcake cheesecake
pastry. Apple pie macaroon ice cream fruitcake apple pie cookie. Tootsie
roll ice cream oat cake cheesecake donut cheesecake bear claw. Sesame
snaps marzipan jelly beans chocolate tootsie roll. Chocolate bar donut
dragée ice cream biscuit. Pie candy canes muffin candy canes ice cream
tiramisu.
</p>
</div>
)
}
......@@ -17,7 +17,9 @@ module.exports = phase => {
const env = {
RESTURL_SPEAKERS: (() => {
if (isDev) return 'http://localhost:4000/speakers'
if (isProd) { return 'https://www.siliconvalley-codecamp.com/rest/speakers/ps' }
if (isProd) {
return 'https://www.siliconvalley-codecamp.com/rest/speakers/ps'
}
if (isStaging) return 'http://localhost:11639'
return 'RESTURL_SPEAKERS:not (isDev,isProd && !isStaging,isProd && isStaging)'
})(),
......
......@@ -22,12 +22,14 @@ function InjectStoreContext ({ children, initialData }) {
let timerInterval = null
store = useObservable(initializeData(initialData))
start = useCallback(action(() => {
timerInterval = setInterval(() => {
store.lastUpdate = Date.now()
store.light = true
}, 1000)
}))
start = useCallback(
action(() => {
timerInterval = setInterval(() => {
store.lastUpdate = Date.now()
store.light = true
}, 1000)
})
)
stop = () => {
if (timerInterval) {
......
import { normalizePagePath } from './normalize-page-path'
import { tryAmp } from './require'
export type BuildManifest = {
devFiles: string[],
......@@ -10,6 +11,9 @@ export type BuildManifest = {
export function getPageFiles(buildManifest: BuildManifest, page: string): string[] {
const normalizedPage = normalizePagePath(page)
const files = buildManifest.pages[normalizedPage]
if (!files) {
page = tryAmp(buildManifest.pages, normalizedPage)
}
if (!files) {
// tslint:disable-next-line
......
import {join} from 'path'
import {CLIENT_STATIC_FILES_PATH, BUILD_MANIFEST, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY} from 'next-server/constants'
import {requirePage} from './require'
import {BUILD_MANIFEST, CLIENT_STATIC_FILES_PATH, REACT_LOADABLE_MANIFEST, SERVER_DIRECTORY} from 'next-server/constants';
import { join } from 'path';
import { PagePathOptions, requirePage } from './require';
function interopDefault(mod: any) {
return mod.default || mod
}
export async function loadComponents(distDir: string, buildId: string, pathname: string) {
export async function loadComponents(distDir: string, buildId: string, pathname: string, opts?: PagePathOptions) {
const documentPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_document')
const appPath = join(distDir, SERVER_DIRECTORY, CLIENT_STATIC_FILES_PATH, buildId, 'pages', '_app')
const [buildManifest, reactLoadableManifest, Component, Document, App] = await Promise.all([
require(join(distDir, BUILD_MANIFEST)),
require(join(distDir, REACT_LOADABLE_MANIFEST)),
interopDefault(requirePage(pathname, distDir)),
interopDefault(requirePage(pathname, distDir, opts)),
interopDefault(require(documentPath)),
interopDefault(require(appPath)),
])
......
......@@ -285,7 +285,7 @@ export default class Server {
query: ParsedUrlQuery = {},
opts: any,
) {
const result = await loadComponents(this.distDir, this.buildId, pathname)
const result = await loadComponents(this.distDir, this.buildId, pathname, { amphtml: !!opts.amphtml })
return renderToHTML(req, res, pathname, query, { ...result, ...opts })
}
......
......@@ -2,18 +2,42 @@ import {join} from 'path'
import {PAGES_MANIFEST, SERVER_DIRECTORY} from 'next-server/constants'
import { normalizePagePath } from './normalize-page-path'
export type PagePathOptions = {
amphtml?: boolean,
}
export function pageNotFoundError(page: string): Error {
const err: any = new Error(`Cannot find module for page: ${page}`)
err.code = 'ENOENT'
return err
}
export function getPagePath(page: string, distDir: string): string {
export const tryAmp = (manifest: any, page: string) => {
const hasAmp = manifest[page + '.amp']
if (hasAmp) {
page += '.amp'
} else if (manifest[page + '/index.amp']) {
page += '/index.amp'
}
return page
}
export function getPagePath(page: string, distDir: string, opts: PagePathOptions = {}): string {
const serverBuildPath = join(distDir, SERVER_DIRECTORY)
const pagesManifest = require(join(serverBuildPath, PAGES_MANIFEST))
try {
page = normalizePagePath(page)
if (opts.amphtml || !pagesManifest[page]) {
page = tryAmp(pagesManifest, page)
// Force .amp to show 404 if set
const isAmp = page.endsWith('.amp')
if (opts.amphtml && !isAmp) {
page += '.amp'
}
opts.amphtml = opts.amphtml || isAmp
}
} catch (err) {
// tslint:disable-next-line
console.error(err)
......@@ -27,7 +51,7 @@ export function getPagePath(page: string, distDir: string): string {
return join(serverBuildPath, pagesManifest[page])
}
export function requirePage(page: string, distDir: string): any {
const pagePath = getPagePath(page, distDir)
export function requirePage(page: string, distDir: string, opts?: PagePathOptions): any {
const pagePath = getPagePath(page, distDir, opts)
return require(pagePath)
}
......@@ -391,12 +391,12 @@ export default class HotReloader {
this.webpackHotMiddleware.publish({ action, data: args })
}
async ensurePage (page) {
async ensurePage (page, amp, ampEnabled) {
// Make sure we don't re-build or dispose prebuilt pages
if (page !== '/_error' && BLOCKED_PAGES.indexOf(page) !== -1) {
return
}
await this.onDemandEntries.ensurePage(page)
return this.onDemandEntries.ensurePage(page, amp, ampEnabled)
}
}
......
import { join } from 'path'
import {isWriteable} from '../../build/is-writeable'
export async function findPageFile(rootDir: string, normalizedPagePath: string, pageExtensions: string[]): Promise<string|null> {
for (const extension of pageExtensions) {
export async function findPageFile(rootDir: string, normalizedPagePath: string, pageExtensions: string[], amp: boolean, ampEnabled: boolean): Promise<string|null> {
if (ampEnabled) {
// Add falling back to .amp.js extension
if (!amp) pageExtensions = pageExtensions.concat(pageExtensions.map((ext) => 'amp.' + ext))
}
for (let extension of pageExtensions) {
if (amp) extension = 'amp.' + extension
const relativePagePath = `${normalizedPagePath}.${extension}`
const pagePath = join(rootDir, relativePagePath)
......
......@@ -123,7 +123,9 @@ export default class DevServer extends Server {
// In dev mode we use on demand entries to compile the page before rendering
try {
await this.hotReloader.ensurePage(pathname)
const result = await this.hotReloader.ensurePage(pathname, options.amphtml, this.nextConfig.experimental.amp)
pathname = result.pathname
options.amphtml = options.amphtml || result.isAmp
} catch (err) {
if (err.code === 'ENOENT') {
res.statusCode = 404
......
import DynamicEntryPlugin from 'webpack/lib/DynamicEntryPlugin'
import { EventEmitter } from 'events'
import { join } from 'path'
import { join, posix } from 'path'
import { parse } from 'url'
import { pageNotFoundError } from 'next-server/dist/server/require'
import { normalizePagePath } from 'next-server/dist/server/normalize-page-path'
......@@ -205,7 +205,7 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
})
},
async ensurePage (page) {
async ensurePage (page, amp, ampEnabled) {
await this.waitUntilReloaded()
let normalizedPagePath
try {
......@@ -215,7 +215,8 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
throw pageNotFoundError(normalizedPagePath)
}
let pagePath = await findPageFile(pagesDir, normalizedPagePath, pageExtensions)
let pagePath = await findPageFile(pagesDir, normalizedPagePath, pageExtensions, amp, ampEnabled)
const isAmp = pagePath && pageExtensions.some(ext => pagePath.endsWith('amp.' + ext))
// Default the /_error route to the Next.js provided default page
if (page === '/_error' && pagePath === null) {
......@@ -232,6 +233,9 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
const name = join('static', buildId, 'pages', bundleFile)
const absolutePagePath = pagePath.startsWith('next/dist/pages') ? require.resolve(pagePath) : join(pagesDir, pagePath)
page = posix.normalize(pageUrl)
const result = { isAmp, pathname: page }
await new Promise((resolve, reject) => {
// Makes sure the page that is being kept in on-demand-entries matches the webpack output
const normalizedPage = normalizePage(page)
......@@ -261,6 +265,8 @@ export default function onDemandEntryHandler (devMiddleware, multiCompiler, {
resolve()
}
})
return result
},
middleware () {
......
export { default } from './index'
import { useAmp } from 'next/amp'
export default () => {
const isAmp = useAmp()
return `Hello ${isAmp ? 'AMP' : 'others'}`
}
export { default } from './use-amp-hook'
......@@ -121,6 +121,17 @@ describe('AMP Usage', () => {
await validateAMP(html)
expect(html).toMatch(/Hello AMP/)
})
it('should render nested normal page with AMP hook', async () => {
const html = await renderViaHTTP(appPort, '/nested')
expect(html).toMatch(/Hello others/)
})
it('should render nested AMP page with AMP hook', async () => {
const html = await renderViaHTTP(appPort, '/nested?amp=1')
await validateAMP(html)
expect(html).toMatch(/Hello AMP/)
})
})
describe('canonical amphtml', () => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册