rsnext/test/e2e/switchable-runtime/index.test.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

689 lines
21 KiB
TypeScript
Raw Normal View History

/* eslint-env jest */
import webdriver from 'next-webdriver'
import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'e2e-utils'
import { check, fetchViaHTTP, renderViaHTTP, waitFor } from 'next-test-utils'
function splitLines(text) {
return text
.split(/\r?\n/g)
.map((str) => str.trim())
.filter(Boolean)
}
async function testRoute(appPort, url, { isStatic, isEdge }) {
const html1 = await renderViaHTTP(appPort, url)
const renderedAt1 = +html1.match(/Time: (\d+)/)[1]
expect(html1).toContain(`Runtime: ${isEdge ? 'Edge' : 'Node.js'}`)
const html2 = await renderViaHTTP(appPort, url)
const renderedAt2 = +html2.match(/Time: (\d+)/)[1]
expect(html2).toContain(`Runtime: ${isEdge ? 'Edge' : 'Node.js'}`)
if (isStatic) {
// TODO: enable static opt tests
// Should not be re-rendered, some timestamp should be returned.
// expect(renderedAt1).toBe(renderedAt2)
} else {
// Should be re-rendered.
expect(renderedAt1).toBeLessThan(renderedAt2)
}
}
describe('Switchable runtime', () => {
let next: NextInstance
let context
if ((global as any).isNextDeploy) {
// TODO-APP: re-enable after Prerenders are handled on deploy
it('should skip for deploy temporarily', () => {})
return
}
beforeAll(async () => {
next = await createNext({
files: new FileRef(__dirname),
})
context = {
appPort: next.url,
appDir: next.testDir,
stdout: '',
stderr: '',
}
})
afterAll(() => next.destroy())
if ((global as any).isNextDev) {
describe('Switchable runtime (dev)', () => {
it('should not include edge api routes and edge ssr routes into dev middleware manifest', async () => {
const res = await fetchViaHTTP(
next.url,
`/_next/static/${next.buildId}/_devMiddlewareManifest.json`
)
const devMiddlewareManifest = await res.json()
expect(devMiddlewareManifest).toEqual([])
})
it('should sort edge SSR routes correctly', async () => {
const res = await fetchViaHTTP(next.url, `/edge/foo`)
const html = await res.text()
// /edge/foo should be caught before /edge/[id]
expect(html).toContain(`to /edge/[id]`)
})
it('should be able to navigate between edge SSR routes without any errors', async () => {
const res = await fetchViaHTTP(next.url, `/edge/foo`)
const html = await res.text()
// /edge/foo should be caught before /edge/[id]
expect(html).toContain(`to /edge/[id]`)
const browser = await webdriver(context.appPort, '/edge/foo')
await browser.waitForElementByCss('a').click()
// on /edge/[id]
await check(
() => browser.eval('document.documentElement.innerHTML'),
/to \/edge\/foo/
)
await browser.waitForElementByCss('a').click()
// on /edge/foo
await check(
() => browser.eval('document.documentElement.innerHTML'),
/to \/edge\/\[id\]/
)
expect(context.stdout).not.toContain('self is not defined')
expect(context.stderr).not.toContain('self is not defined')
})
it.skip('should support client side navigation to ssr rsc pages', async () => {
let flightRequest = null
const browser = await webdriver(context.appPort, '/node', {
beforePageLoad(page) {
page.on('request', (request) => {
return request.allHeaders().then((headers) => {
if (headers['RSC'.toLowerCase()] === '1') {
flightRequest = request.url()
}
})
})
},
})
await browser
.waitForElementByCss('#link-node-rsc-ssr')
.click()
.waitForElementByCss('.node-rsc-ssr')
await check(
() => browser.eval('document.documentElement.innerHTML'),
/This is a SSR RSC page/
)
expect(flightRequest).toContain('/node-rsc-ssr')
})
it.skip('should support client side navigation to ssg rsc pages', async () => {
const browser = await webdriver(context.appPort, '/node')
await browser
.waitForElementByCss('#link-node-rsc-ssg')
.click()
.waitForElementByCss('.node-rsc-ssg')
await check(
() => browser.eval('document.documentElement.innerHTML'),
/This is a SSG RSC page/
)
})
it.skip('should support client side navigation to static rsc pages', async () => {
const browser = await webdriver(context.appPort, '/node')
await browser
.waitForElementByCss('#link-node-rsc')
.click()
.waitForElementByCss('.node-rsc')
await check(
() => browser.eval('document.documentElement.innerHTML'),
/This is a static RSC page/
)
})
it('should not consume server.js file extension', async () => {
const { status } = await fetchViaHTTP(
context.appPort,
'/legacy-extension'
)
expect(status).toBe(404)
})
it('should build /api/hello and /api/edge as an api route with edge runtime', async () => {
let response = await fetchViaHTTP(context.appPort, '/api/hello')
let text = await response.text()
expect(text).toMatch(/Hello from .+\/api\/hello/)
response = await fetchViaHTTP(context.appPort, '/api/edge')
text = await response.text()
expect(text).toMatch(/Returned by Edge API Route .+\/api\/edge/)
})
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
it('should be possible to switch between runtimes in API routes', async () => {
await check(
() => renderViaHTTP(next.url, '/api/switch-in-dev'),
'server response'
)
// Edge
await next.patchFile(
'pages/api/switch-in-dev.js',
`
export const config = {
runtime: 'edge',
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
}
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
export default () => new Response('edge response')
`
)
await check(
() => renderViaHTTP(next.url, '/api/switch-in-dev'),
'edge response'
)
// Server
await next.patchFile(
'pages/api/switch-in-dev.js',
`
export default function (req, res) {
res.send('server response again')
}
`
)
await check(
() => renderViaHTTP(next.url, '/api/switch-in-dev'),
'server response again'
)
// Edge
await next.patchFile(
'pages/api/switch-in-dev.js',
`
export const config = {
runtime: 'edge',
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
}
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
export default () => new Response('edge response again')
`
)
await check(
() => renderViaHTTP(next.url, '/api/switch-in-dev'),
'edge response again'
)
})
it('should be possible to switch between runtimes in pages', async () => {
await check(
() => renderViaHTTP(next.url, '/switch-in-dev'),
/Hello from edge page/
)
// Server
await next.patchFile(
'pages/switch-in-dev.js',
`
export default function Page() {
return <p>Hello from server page</p>
}
`
)
await check(
() => renderViaHTTP(next.url, '/switch-in-dev'),
/Hello from server page/
)
// Edge
await next.patchFile(
'pages/switch-in-dev.js',
`
export default function Page() {
return <p>Hello from edge page again</p>
}
export const config = {
runtime: 'experimental-edge',
}
`
)
await check(
() => renderViaHTTP(next.url, '/switch-in-dev'),
/Hello from edge page again/
)
// Server
await next.patchFile(
'pages/switch-in-dev.js',
`
export default function Page() {
return <p>Hello from server page again</p>
}
`
)
await check(
() => renderViaHTTP(next.url, '/switch-in-dev'),
/Hello from server page again/
)
})
// Doesn't work, see https://github.com/vercel/next.js/pull/39327
it.skip('should be possible to switch between runtimes with same content', async () => {
const fileContent = await next.readFile(
'pages/api/switch-in-dev-same-content.js'
)
console.log({ fileContent })
await check(
() => renderViaHTTP(next.url, '/api/switch-in-dev-same-content'),
'server response'
)
// Edge
await next.patchFile(
'pages/api/switch-in-dev-same-content.js',
`
export const config = {
runtime: 'edge',
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
}
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
export default () => new Response('edge response')
`
)
await check(
() => renderViaHTTP(next.url, '/api/switch-in-dev-same-content'),
'edge response'
)
// Server - same content as first compilation of the server runtime version
await next.patchFile(
'pages/api/switch-in-dev-same-content.js',
fileContent
)
await check(
() => renderViaHTTP(next.url, '/api/switch-in-dev-same-content'),
'server response'
)
})
// TODO: investigate these failures
it.skip('should recover from syntax error when using edge runtime', async () => {
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
await check(
() => renderViaHTTP(next.url, '/api/syntax-error-in-dev'),
'edge response'
)
// Syntax error
await next.patchFile(
'pages/api/syntax-error-in-dev.js',
`
export const config = {
runtime: 'edge',
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
}
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
export default => new Response('edge response')
`
)
await check(
() => renderViaHTTP(next.url, '/api/syntax-error-in-dev'),
/Unexpected token/
)
// Fix syntax error
await next.patchFile(
'pages/api/syntax-error-in-dev.js',
`
export default () => new Response('edge response again')
export const config = {
runtime: 'edge',
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
}
fix(switchable-runtime): Make it possible to switch between edge and server runtime in dev (#39327) Makes it possible to switch between edge/server runtime in dev without breaking the server. Fixes slack: [1](https://vercel.slack.com/archives/CGU8HUTUH/p1659082535540549) [2](https://vercel.slack.com/archives/C02CDC2ALJH/p1658978287244359) [3](https://vercel.slack.com/archives/C03KAR5DCKC/p1656869427468779) #### middleware-plugin.ts `middlewareManifest` moved from module scope to local scope. Stale state from earlier builds ended up in `middleware-manifest.json`. Functions that changed from edge to server runtime stayed in the manifest as edge functions. #### on-demand-entry-handler.ts When a server or edge entry is added we check if it has switched runtime. If that's the case the old entry is removed. #### Reproduce Create edge API route and visit `/api/hello` ```js // pages/api/hello.js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server api route and visit `/api/hello`, it will explode. ```js // pages/api/hello.js export default function (req, res) { res.send('Hello') } ``` #### Bug not fixed One EDGE case is not fixed. It occurs if you switch between edge and server runtime several times without changing the content of the file: Edge runtime ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` Change it to a server runtime ```js export default function (req, res) { res.send('Hello') } ``` Change back to edge runtime, the content of the file is the same as the first time we compiled the edge runtime version. ```js export const config = { runtime: 'experimental-edge', } export default () => new Response('Hello') ``` The reason is that both the edge and server compiler emits to the same file (/.next/server/pages/api/hello.js) which makes this check fail in webpack: https://github.com/webpack/webpack/blob/main/lib/Compiler.js#L849-L861 Possible solution is to use different output folders for edge and server https://vercel.slack.com/archives/CGU8HUTUH/p1661163106667559 Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-09-07 22:42:32 +02:00
`
)
await check(
() => renderViaHTTP(next.url, '/api/syntax-error-in-dev'),
'edge response again'
)
})
it.skip('should not crash the dev server when invalid runtime is configured', async () => {
await check(
() => renderViaHTTP(next.url, '/invalid-runtime'),
/Hello from page without errors/
)
// Invalid runtime type
await next.patchFile(
'pages/invalid-runtime.js',
`
export default function Page() {
return <p>Hello from page with invalid type</p>
}
export const config = {
runtime: 10,
}
`
)
await check(
() => renderViaHTTP(next.url, '/invalid-runtime'),
/Hello from page with invalid type/
)
expect(next.cliOutput).toInclude(
Redesign nextjs logging (#54713) The current logging styles has been existed for a while, this PR gives a fresh impression for the logging output from Next.js. We want to achieve few new goals that makes the output clean, modernized, sweet 🍫 . Few goals are addressed with this redesign: ## Refresh Impression & Simplification The new design of logging is much more information centralized and streamlined. * Given a `ready` message at the begining when compilers are bootstrapped. * Only show `compiled` event with green check mark indicating succesful compilation, this will merge the unclear `compiling` event which shows `(client and server)` before, now tell you the route compilation info in one line. hello world app ### `next dev` #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/9649b340-8241-4756-a2b3-a989f0b74003" height="120"> <img src="https://github.com/vercel/next.js/assets/4800338/ee181263-3dd4-40d0-9ffc-819a56b45900" height="120"> ### `next build` #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/5db9829a-9ffc-49f0-b030-93ee92f5c248" width="360"> <img src="https://github.com/vercel/next.js/assets/4800338/b9527b83-27c8-4426-9c0d-c0d4072b7d58" width="360"> ### error status #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/00455226-ace7-468b-8d90-0d36bf038489" height="120"> <img src="https://github.com/vercel/next.js/assets/4800338/1be8c451-d3f0-465c-9ef7-6b0dde7cff85" height="120"> ## Streamlization If you have customized envs and experiments Next.js will give the brief in the early summary about your network information, env vars, and enabled experimental features <img src="https://github.com/vercel/next.js/assets/4800338/ca1a7409-1532-46cb-850f-687e61e587b2" width="400"> ## Polish ### fetching logging structure #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/97526397-dffe-4736-88ed-e5cbe5e945bd" width="400"> <img src="https://github.com/vercel/next.js/assets/4800338/ab77c907-5ab5-48bb-8347-6146d2e60932" width="400"> ### Dedupe Duplicates The logging is moved from `@next/env` to `next` itself, `@next/env` will only notify the invoker that the env is reloaded. Then the duplicated logs for the env reloading cases can be avoid. #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/04799295-e739-4035-87aa-61cec962fc39" width="400"> <img src="https://github.com/vercel/next.js/assets/4800338/e29020c9-0031-4bf3-a21b-8b64633f43a2" width="400"> ### Different indicators Use unicode text icons for different situation: * passed -> check mark * warning -> warning * error -> red cross * loading -> circle <img src="https://github.com/vercel/next.js/assets/4800338/715c34bd-298f-4990-a5d7-e12e455ead44" width="400"> Co-authored-by: Tim Neutkens <6324199+timneutkens@users.noreply.github.com>
2023-09-05 13:40:00 +02:00
'The `runtime` config must be a string. Please leave it empty or choose one of:'
)
// Invalid runtime
await next.patchFile(
'pages/invalid-runtime.js',
`
export default function Page() {
return <p>Hello from page with invalid runtime</p>
}
export const config = {
runtime: "asd"
}
`
)
await check(
() => renderViaHTTP(next.url, '/invalid-runtime'),
/Hello from page with invalid runtime/
)
expect(next.cliOutput).toInclude(
Redesign nextjs logging (#54713) The current logging styles has been existed for a while, this PR gives a fresh impression for the logging output from Next.js. We want to achieve few new goals that makes the output clean, modernized, sweet 🍫 . Few goals are addressed with this redesign: ## Refresh Impression & Simplification The new design of logging is much more information centralized and streamlined. * Given a `ready` message at the begining when compilers are bootstrapped. * Only show `compiled` event with green check mark indicating succesful compilation, this will merge the unclear `compiling` event which shows `(client and server)` before, now tell you the route compilation info in one line. hello world app ### `next dev` #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/9649b340-8241-4756-a2b3-a989f0b74003" height="120"> <img src="https://github.com/vercel/next.js/assets/4800338/ee181263-3dd4-40d0-9ffc-819a56b45900" height="120"> ### `next build` #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/5db9829a-9ffc-49f0-b030-93ee92f5c248" width="360"> <img src="https://github.com/vercel/next.js/assets/4800338/b9527b83-27c8-4426-9c0d-c0d4072b7d58" width="360"> ### error status #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/00455226-ace7-468b-8d90-0d36bf038489" height="120"> <img src="https://github.com/vercel/next.js/assets/4800338/1be8c451-d3f0-465c-9ef7-6b0dde7cff85" height="120"> ## Streamlization If you have customized envs and experiments Next.js will give the brief in the early summary about your network information, env vars, and enabled experimental features <img src="https://github.com/vercel/next.js/assets/4800338/ca1a7409-1532-46cb-850f-687e61e587b2" width="400"> ## Polish ### fetching logging structure #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/97526397-dffe-4736-88ed-e5cbe5e945bd" width="400"> <img src="https://github.com/vercel/next.js/assets/4800338/ab77c907-5ab5-48bb-8347-6146d2e60932" width="400"> ### Dedupe Duplicates The logging is moved from `@next/env` to `next` itself, `@next/env` will only notify the invoker that the env is reloaded. Then the duplicated logs for the env reloading cases can be avoid. #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/04799295-e739-4035-87aa-61cec962fc39" width="400"> <img src="https://github.com/vercel/next.js/assets/4800338/e29020c9-0031-4bf3-a21b-8b64633f43a2" width="400"> ### Different indicators Use unicode text icons for different situation: * passed -> check mark * warning -> warning * error -> red cross * loading -> circle <img src="https://github.com/vercel/next.js/assets/4800338/715c34bd-298f-4990-a5d7-e12e455ead44" width="400"> Co-authored-by: Tim Neutkens <6324199+timneutkens@users.noreply.github.com>
2023-09-05 13:40:00 +02:00
'Provided runtime "asd" is not supported. Please leave it empty or choose one of:'
)
// Fix the runtime
await next.patchFile(
'pages/invalid-runtime.js',
`
export default function Page() {
return <p>Hello from page without errors</p>
}
export const config = {
runtime: 'experimental-edge',
}
`
)
await check(
() => renderViaHTTP(next.url, '/invalid-runtime'),
/Hello from page without errors/
)
})
it.skip('should give proper errors for invalid runtime in app dir', async () => {
// Invalid runtime
await next.patchFile(
'app/app-invalid-runtime/page.js',
`
export default function Page() {
return <p>Hello from app</p>
}
export const runtime = 'invalid-runtime'
`
)
await check(
() => renderViaHTTP(next.url, '/app-invalid-runtime'),
/Hello from app/
)
expect(next.cliOutput).toInclude(
Redesign nextjs logging (#54713) The current logging styles has been existed for a while, this PR gives a fresh impression for the logging output from Next.js. We want to achieve few new goals that makes the output clean, modernized, sweet 🍫 . Few goals are addressed with this redesign: ## Refresh Impression & Simplification The new design of logging is much more information centralized and streamlined. * Given a `ready` message at the begining when compilers are bootstrapped. * Only show `compiled` event with green check mark indicating succesful compilation, this will merge the unclear `compiling` event which shows `(client and server)` before, now tell you the route compilation info in one line. hello world app ### `next dev` #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/9649b340-8241-4756-a2b3-a989f0b74003" height="120"> <img src="https://github.com/vercel/next.js/assets/4800338/ee181263-3dd4-40d0-9ffc-819a56b45900" height="120"> ### `next build` #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/5db9829a-9ffc-49f0-b030-93ee92f5c248" width="360"> <img src="https://github.com/vercel/next.js/assets/4800338/b9527b83-27c8-4426-9c0d-c0d4072b7d58" width="360"> ### error status #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/00455226-ace7-468b-8d90-0d36bf038489" height="120"> <img src="https://github.com/vercel/next.js/assets/4800338/1be8c451-d3f0-465c-9ef7-6b0dde7cff85" height="120"> ## Streamlization If you have customized envs and experiments Next.js will give the brief in the early summary about your network information, env vars, and enabled experimental features <img src="https://github.com/vercel/next.js/assets/4800338/ca1a7409-1532-46cb-850f-687e61e587b2" width="400"> ## Polish ### fetching logging structure #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/97526397-dffe-4736-88ed-e5cbe5e945bd" width="400"> <img src="https://github.com/vercel/next.js/assets/4800338/ab77c907-5ab5-48bb-8347-6146d2e60932" width="400"> ### Dedupe Duplicates The logging is moved from `@next/env` to `next` itself, `@next/env` will only notify the invoker that the env is reloaded. Then the duplicated logs for the env reloading cases can be avoid. #### After vs Before <img src="https://github.com/vercel/next.js/assets/4800338/04799295-e739-4035-87aa-61cec962fc39" width="400"> <img src="https://github.com/vercel/next.js/assets/4800338/e29020c9-0031-4bf3-a21b-8b64633f43a2" width="400"> ### Different indicators Use unicode text icons for different situation: * passed -> check mark * warning -> warning * error -> red cross * loading -> circle <img src="https://github.com/vercel/next.js/assets/4800338/715c34bd-298f-4990-a5d7-e12e455ead44" width="400"> Co-authored-by: Tim Neutkens <6324199+timneutkens@users.noreply.github.com>
2023-09-05 13:40:00 +02:00
'Provided runtime "invalid-runtime" is not supported. Please leave it empty or choose one of:'
)
await next.patchFile(
'app/app-invalid-runtime/page.js',
`
export default function Page() {
return <p>Hello from app</p>
}`
)
})
})
} else {
describe('Switchable runtime (prod)', () => {
it('should build /static as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/static', {
isStatic: true,
isEdge: false,
})
})
it.skip('should build /node as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node', {
isStatic: true,
isEdge: false,
})
})
it('should build /node-ssr as a dynamic page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-ssr', {
isStatic: false,
isEdge: false,
})
})
it.skip('should build /node-ssg as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-ssg', {
isStatic: true,
isEdge: false,
})
})
it.skip('should build /node-rsc as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-rsc', {
isStatic: true,
isEdge: false,
})
})
it('should build /app-valid-runtime as a dynamic page with the edge runtime', async () => {
await testRoute(context.appPort, '/app-valid-runtime', {
isStatic: false,
isEdge: true,
})
})
// FIXME: rsc hydration
it.skip('should build /node-rsc-ssr as a dynamic page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-rsc-ssr', {
isStatic: false,
isEdge: false,
})
})
// FIXME: rsc hydration
it.skip('should build /node-rsc-ssg as a static page with the nodejs runtime', async () => {
await testRoute(context.appPort, '/node-rsc-ssg', {
isStatic: true,
isEdge: false,
})
})
// FIXME: rsc hydration
it.skip('should build /node-rsc-isr as an isr page with the nodejs runtime', async () => {
const html1 = await renderViaHTTP(context.appPort, '/node-rsc-isr')
const renderedAt1 = +html1.match(/Time: (\d+)/)[1]
expect(html1).toContain('Runtime: Node.js')
const html2 = await renderViaHTTP(context.appPort, '/node-rsc-isr')
const renderedAt2 = +html2.match(/Time: (\d+)/)[1]
expect(html2).toContain('Runtime: Node.js')
expect(renderedAt1).toBe(renderedAt2)
// Trigger a revalidation after 3s.
await waitFor(4000)
await renderViaHTTP(context.appPort, '/node-rsc-isr')
await check(async () => {
const html3 = await renderViaHTTP(context.appPort, '/node-rsc-isr')
const renderedAt3 = +html3.match(/Time: (\d+)/)[1]
return renderedAt2 < renderedAt3
? 'success'
: `${renderedAt2} should be less than ${renderedAt3}`
}, 'success')
})
it('should build /edge as a dynamic page with the edge runtime', async () => {
await testRoute(context.appPort, '/edge', {
isStatic: false,
isEdge: true,
})
await testRoute(context.appPort, '/rewrite/edge', {
isStatic: false,
isEdge: true,
})
})
// TODO: edge rsc in app dir
it.skip('should build /edge-rsc as a dynamic page with the edge runtime', async () => {
await testRoute(context.appPort, '/edge-rsc', {
isStatic: false,
isEdge: true,
})
})
it('should build /api/hello and /api/edge as an api route with edge runtime', async () => {
let response = await fetchViaHTTP(context.appPort, '/api/hello')
let text = await response.text()
expect(text).toMatch(/Hello from .+\/api\/hello/)
response = await fetchViaHTTP(context.appPort, '/api/edge')
text = await response.text()
expect(text).toMatch(/Returned by Edge API Route .+\/api\/edge/)
// Rewrite should also work
response = await fetchViaHTTP(context.appPort, 'rewrite/api/edge')
text = await response.text()
expect(text).toMatch(/Returned by Edge API Route .+\/api\/edge/)
})
it.skip('should display correct tree view with page types in terminal', async () => {
const stdoutLines = splitLines(context.stdout).filter((line) =>
/^[┌├└/]/.test(line)
)
const expectedOutputLines = splitLines(`
/_app
/404
ƒ /api/hello
ƒ /api/node
ƒ /edge
ƒ /edge-rsc
/node
/node-rsc
/node-rsc-isr
/node-rsc-ssg
ƒ /node-rsc-ssr
/node-ssg
ƒ /node-ssr
/static
`)
const mappedOutputLines = expectedOutputLines.map((_line, index) => {
/** @type {string} */
const str = stdoutLines[index]
const beginningOfPath = str.indexOf('/')
const endOfPath = str.indexOf(' ', beginningOfPath)
return str.slice(0, endOfPath)
})
expect(mappedOutputLines).toEqual(expectedOutputLines)
})
// TODO: static opt
it.skip('should prefetch data for static pages', async () => {
const dataRequests = []
const browser = await webdriver(context.appPort, '/node', {
beforePageLoad(page) {
page.on('request', (request) => {
const url = request.url()
if (/\.json$/.test(url)) {
dataRequests.push(url.split('/').pop())
}
})
},
})
await browser.eval('window.beforeNav = 1')
for (const data of [
'node-rsc.json',
'node-rsc-ssg.json',
'node-rsc-isr.json',
'node-ssg.json',
]) {
expect(dataRequests).toContain(data)
}
})
it.skip('should support client side navigation to ssr rsc pages', async () => {
let flightRequest = null
const browser = await webdriver(context.appPort, '/node', {
beforePageLoad(page) {
page.on('request', (request) => {
request.allHeaders().then((headers) => {
if (headers['RSC'.toLowerCase()] === '1') {
flightRequest = request.url()
}
})
})
},
})
await browser.waitForElementByCss('#link-node-rsc-ssr').click()
expect(await browser.elementByCss('body').text()).toContain(
'This is a SSR RSC page.'
)
expect(flightRequest).toContain('/node-rsc-ssr')
})
it.skip('should support client side navigation to ssg rsc pages', async () => {
const browser = await webdriver(context.appPort, '/node')
await browser.waitForElementByCss('#link-node-rsc-ssg').click()
expect(await browser.elementByCss('body').text()).toContain(
'This is a SSG RSC page.'
)
})
it.skip('should support client side navigation to static rsc pages', async () => {
const browser = await webdriver(context.appPort, '/node')
await browser.waitForElementByCss('#link-node-rsc').click()
expect(await browser.elementByCss('body').text()).toContain(
'This is a static RSC page.'
)
})
it('should support etag header in the web server', async () => {
const res = await fetchViaHTTP(context.appPort, '/edge', '', {
headers: {
// Make sure the result is static so an etag can be generated.
'User-Agent': 'Googlebot',
},
})
expect(res.headers.get('ETag')).toBeDefined()
})
})
}
})