Test Prerender in Emulated Serverless Mode (#10660)
* Test Prerender in Emulated Serverless Mode * Clean up between runs
This commit is contained in:
parent
18ecaa9d6b
commit
51b35f1f31
2 changed files with 182 additions and 53 deletions
|
@ -1,15 +1,102 @@
|
|||
const http = require('http')
|
||||
const url = require('url')
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const server = http.createServer((req, res) => {
|
||||
let { pathname } = url.parse(req.url)
|
||||
pathname = pathname.replace(/\/$/, '')
|
||||
let isDataReq = false
|
||||
if (pathname.startsWith('/_next/data')) {
|
||||
isDataReq = true
|
||||
pathname = pathname
|
||||
.replace(`/_next/data/${process.env.BUILD_ID}/`, '/')
|
||||
.replace(/\.json$/, '')
|
||||
}
|
||||
console.log('serving', pathname)
|
||||
require(`./.next/serverless/pages${pathname}`).render(req, res)
|
||||
|
||||
if (pathname === '/favicon.ico') {
|
||||
res.statusCode = 404
|
||||
return res.end()
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/_next/static/')) {
|
||||
res.write(
|
||||
fs.readFileSync(
|
||||
path.join(
|
||||
__dirname,
|
||||
'./.next/static/',
|
||||
decodeURI(pathname.slice('/_next/static/'.length))
|
||||
),
|
||||
'utf8'
|
||||
)
|
||||
)
|
||||
return res.end()
|
||||
} else {
|
||||
const ext = isDataReq ? 'json' : 'html'
|
||||
if (
|
||||
fs.existsSync(
|
||||
path.join(__dirname, `./.next/serverless/pages${pathname}.${ext}`)
|
||||
)
|
||||
) {
|
||||
res.write(
|
||||
fs.readFileSync(
|
||||
path.join(__dirname, `./.next/serverless/pages${pathname}.${ext}`),
|
||||
'utf8'
|
||||
)
|
||||
)
|
||||
return res.end()
|
||||
}
|
||||
|
||||
let re
|
||||
try {
|
||||
re = require(`./.next/serverless/pages${pathname}`)
|
||||
} catch {
|
||||
const d = decodeURI(pathname)
|
||||
if (
|
||||
fs.existsSync(
|
||||
path.join(__dirname, `./.next/serverless/pages${d}.${ext}`)
|
||||
)
|
||||
) {
|
||||
res.write(
|
||||
fs.readFileSync(
|
||||
path.join(__dirname, `./.next/serverless/pages${d}.${ext}`),
|
||||
'utf8'
|
||||
)
|
||||
)
|
||||
return res.end()
|
||||
}
|
||||
|
||||
const routesManifest = require('./.next/routes-manifest.json')
|
||||
const { dynamicRoutes } = routesManifest
|
||||
dynamicRoutes.some(({ page, regex }) => {
|
||||
if (new RegExp(regex).test(pathname)) {
|
||||
if (
|
||||
fs.existsSync(
|
||||
path.join(__dirname, `./.next/serverless/pages${page}.${ext}`)
|
||||
)
|
||||
) {
|
||||
res.write(
|
||||
fs.readFileSync(
|
||||
path.join(__dirname, `./.next/serverless/pages${page}.${ext}`),
|
||||
'utf8'
|
||||
)
|
||||
)
|
||||
res.end()
|
||||
return true
|
||||
}
|
||||
|
||||
re = require(`./.next/serverless/pages${page}`)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
if (!res.finished) {
|
||||
return typeof re.render === 'function'
|
||||
? re.render(req, res)
|
||||
: re.default(req, res)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
server.listen(process.env.PORT, () => {
|
||||
|
|
|
@ -245,7 +245,7 @@ const navigateTest = (dev = false) => {
|
|||
})
|
||||
}
|
||||
|
||||
const runTests = (dev = false) => {
|
||||
const runTests = (dev = false, looseMode = false) => {
|
||||
navigateTest(dev)
|
||||
|
||||
it('should SSR normal page correctly', async () => {
|
||||
|
@ -518,14 +518,16 @@ const runTests = (dev = false) => {
|
|||
expect(curRandom).toBe(initialRandom + '')
|
||||
})
|
||||
} else {
|
||||
it('should should use correct caching headers for a no-revalidate page', async () => {
|
||||
const initialRes = await fetchViaHTTP(appPort, '/something')
|
||||
expect(initialRes.headers.get('cache-control')).toBe(
|
||||
's-maxage=31536000, stale-while-revalidate'
|
||||
)
|
||||
const initialHtml = await initialRes.text()
|
||||
expect(initialHtml).toMatch(/hello.*?world/)
|
||||
})
|
||||
if (!looseMode) {
|
||||
it('should should use correct caching headers for a no-revalidate page', async () => {
|
||||
const initialRes = await fetchViaHTTP(appPort, '/something')
|
||||
expect(initialRes.headers.get('cache-control')).toBe(
|
||||
's-maxage=31536000, stale-while-revalidate'
|
||||
)
|
||||
const initialHtml = await initialRes.text()
|
||||
expect(initialHtml).toMatch(/hello.*?world/)
|
||||
})
|
||||
}
|
||||
|
||||
it('outputs a prerender-manifest correctly', async () => {
|
||||
const manifest = JSON.parse(
|
||||
|
@ -600,20 +602,22 @@ const runTests = (dev = false) => {
|
|||
}
|
||||
})
|
||||
|
||||
it('should handle de-duping correctly', async () => {
|
||||
let vals = new Array(10).fill(null)
|
||||
if (!looseMode) {
|
||||
it('should handle de-duping correctly', async () => {
|
||||
let vals = new Array(10).fill(null)
|
||||
|
||||
// use data route so we don't get the fallback
|
||||
vals = await Promise.all(
|
||||
vals.map(() =>
|
||||
renderViaHTTP(appPort, `/_next/data/${buildId}/blog/post-10.json`)
|
||||
// use data route so we don't get the fallback
|
||||
vals = await Promise.all(
|
||||
vals.map(() =>
|
||||
renderViaHTTP(appPort, `/_next/data/${buildId}/blog/post-10.json`)
|
||||
)
|
||||
)
|
||||
)
|
||||
const val = vals[0]
|
||||
const val = vals[0]
|
||||
|
||||
expect(JSON.parse(val).pageProps.post).toBe('post-10')
|
||||
expect(new Set(vals).size).toBe(1)
|
||||
})
|
||||
expect(JSON.parse(val).pageProps.post).toBe('post-10')
|
||||
expect(new Set(vals).size).toBe(1)
|
||||
})
|
||||
}
|
||||
|
||||
it('should not revalidate when set to false', async () => {
|
||||
const route = '/something'
|
||||
|
@ -628,43 +632,45 @@ const runTests = (dev = false) => {
|
|||
expect(initialHtml).toBe(newHtml)
|
||||
})
|
||||
|
||||
it('should handle revalidating HTML correctly', async () => {
|
||||
const route = '/blog/post-2/comment-2'
|
||||
const initialHtml = await renderViaHTTP(appPort, route)
|
||||
expect(initialHtml).toMatch(/Post:.*?post-2/)
|
||||
expect(initialHtml).toMatch(/Comment:.*?comment-2/)
|
||||
if (!looseMode) {
|
||||
it('should handle revalidating HTML correctly', async () => {
|
||||
const route = '/blog/post-2/comment-2'
|
||||
const initialHtml = await renderViaHTTP(appPort, route)
|
||||
expect(initialHtml).toMatch(/Post:.*?post-2/)
|
||||
expect(initialHtml).toMatch(/Comment:.*?comment-2/)
|
||||
|
||||
let newHtml = await renderViaHTTP(appPort, route)
|
||||
expect(newHtml).toBe(initialHtml)
|
||||
let newHtml = await renderViaHTTP(appPort, route)
|
||||
expect(newHtml).toBe(initialHtml)
|
||||
|
||||
await waitFor(2 * 1000)
|
||||
await renderViaHTTP(appPort, route)
|
||||
await waitFor(2 * 1000)
|
||||
await renderViaHTTP(appPort, route)
|
||||
|
||||
await waitFor(2 * 1000)
|
||||
newHtml = await renderViaHTTP(appPort, route)
|
||||
expect(newHtml === initialHtml).toBe(false)
|
||||
expect(newHtml).toMatch(/Post:.*?post-2/)
|
||||
expect(newHtml).toMatch(/Comment:.*?comment-2/)
|
||||
})
|
||||
await waitFor(2 * 1000)
|
||||
newHtml = await renderViaHTTP(appPort, route)
|
||||
expect(newHtml === initialHtml).toBe(false)
|
||||
expect(newHtml).toMatch(/Post:.*?post-2/)
|
||||
expect(newHtml).toMatch(/Comment:.*?comment-2/)
|
||||
})
|
||||
|
||||
it('should handle revalidating JSON correctly', async () => {
|
||||
const route = `/_next/data/${buildId}/blog/post-2/comment-3.json`
|
||||
const initialJson = await renderViaHTTP(appPort, route)
|
||||
expect(initialJson).toMatch(/post-2/)
|
||||
expect(initialJson).toMatch(/comment-3/)
|
||||
it('should handle revalidating JSON correctly', async () => {
|
||||
const route = `/_next/data/${buildId}/blog/post-2/comment-3.json`
|
||||
const initialJson = await renderViaHTTP(appPort, route)
|
||||
expect(initialJson).toMatch(/post-2/)
|
||||
expect(initialJson).toMatch(/comment-3/)
|
||||
|
||||
let newJson = await renderViaHTTP(appPort, route)
|
||||
expect(newJson).toBe(initialJson)
|
||||
let newJson = await renderViaHTTP(appPort, route)
|
||||
expect(newJson).toBe(initialJson)
|
||||
|
||||
await waitFor(2 * 1000)
|
||||
await renderViaHTTP(appPort, route)
|
||||
await waitFor(2 * 1000)
|
||||
await renderViaHTTP(appPort, route)
|
||||
|
||||
await waitFor(2 * 1000)
|
||||
newJson = await renderViaHTTP(appPort, route)
|
||||
expect(newJson === initialJson).toBe(false)
|
||||
expect(newJson).toMatch(/post-2/)
|
||||
expect(newJson).toMatch(/comment-3/)
|
||||
})
|
||||
await waitFor(2 * 1000)
|
||||
newJson = await renderViaHTTP(appPort, route)
|
||||
expect(newJson === initialJson).toBe(false)
|
||||
expect(newJson).toMatch(/post-2/)
|
||||
expect(newJson).toMatch(/comment-3/)
|
||||
})
|
||||
}
|
||||
|
||||
it('should not fetch prerender data on mount', async () => {
|
||||
const browser = await webdriver(appPort, '/blog/post-100')
|
||||
|
@ -682,7 +688,7 @@ const runTests = (dev = false) => {
|
|||
}
|
||||
}
|
||||
|
||||
describe('SPR Prerender', () => {
|
||||
describe('SSG Prerender', () => {
|
||||
afterAll(() => fs.remove(nextConfig))
|
||||
|
||||
describe('dev mode', () => {
|
||||
|
@ -724,6 +730,7 @@ describe('SPR Prerender', () => {
|
|||
`module.exports = { target: 'serverless' }`,
|
||||
'utf8'
|
||||
)
|
||||
await fs.remove(join(appDir, '.next'))
|
||||
await nextBuild(appDir)
|
||||
stderr = ''
|
||||
appPort = await findPort()
|
||||
|
@ -766,6 +773,7 @@ describe('SPR Prerender', () => {
|
|||
export default () => 'hello world'
|
||||
`
|
||||
)
|
||||
await fs.remove(join(appDir, '.next'))
|
||||
const { stderr } = await nextBuild(appDir, [], { stderr: true })
|
||||
await fs.remove(brokenPage)
|
||||
expect(stderr).toContain(
|
||||
|
@ -777,10 +785,43 @@ describe('SPR Prerender', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('enumlated serverless mode', () => {
|
||||
beforeAll(async () => {
|
||||
const startServerlessEmulator = async (dir, port, buildId) => {
|
||||
const scriptPath = join(dir, 'server.js')
|
||||
const env = Object.assign(
|
||||
{},
|
||||
{ ...process.env },
|
||||
{ PORT: port, BUILD_ID: buildId }
|
||||
)
|
||||
return initNextServerScript(scriptPath, /ready on/i, env)
|
||||
}
|
||||
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = { target: 'experimental-serverless-trace' }`,
|
||||
'utf8'
|
||||
)
|
||||
await fs.remove(join(appDir, '.next'))
|
||||
await nextBuild(appDir)
|
||||
|
||||
distPagesDir = join(appDir, '.next/serverless/pages')
|
||||
buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8')
|
||||
|
||||
stderr = ''
|
||||
appPort = await findPort()
|
||||
app = await startServerlessEmulator(appDir, appPort, buildId)
|
||||
})
|
||||
afterAll(() => killApp(app))
|
||||
|
||||
runTests(false, true)
|
||||
})
|
||||
|
||||
describe('production mode', () => {
|
||||
let buildOutput = ''
|
||||
beforeAll(async () => {
|
||||
await fs.remove(nextConfig)
|
||||
await fs.remove(join(appDir, '.next'))
|
||||
const { stdout } = await nextBuild(appDir, [], { stdout: true })
|
||||
buildOutput = stdout
|
||||
|
||||
|
@ -820,6 +861,7 @@ describe('SPR Prerender', () => {
|
|||
},
|
||||
}`
|
||||
)
|
||||
await fs.remove(join(appDir, '.next'))
|
||||
await nextBuild(appDir)
|
||||
await nextExport(appDir, { outdir: exportDir })
|
||||
app = await startStaticServer(exportDir)
|
||||
|
|
Loading…
Reference in a new issue