mirror of
https://github.com/LukeHagar/vercel.git
synced 2025-12-09 12:57:46 +00:00
[now-next] Add loading of custom routes (#3279)
This adds loading of `redirects` and `rewrites` from the `routes-manifest` in supported Next.js versions
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { ChildProcess, fork } from 'child_process';
|
import { ChildProcess, fork } from 'child_process';
|
||||||
|
import url from 'url'
|
||||||
import {
|
import {
|
||||||
pathExists,
|
pathExists,
|
||||||
readFile,
|
readFile,
|
||||||
@@ -54,8 +55,14 @@ import {
|
|||||||
syncEnvVars,
|
syncEnvVars,
|
||||||
validateEntrypoint,
|
validateEntrypoint,
|
||||||
getSourceFilePathFromPage,
|
getSourceFilePathFromPage,
|
||||||
|
getRoutesManifest,
|
||||||
} from './utils';
|
} from './utils';
|
||||||
|
|
||||||
|
import {
|
||||||
|
convertRedirects,
|
||||||
|
convertRewrites
|
||||||
|
} from '@now/routing-utils/dist/superstatic'
|
||||||
|
|
||||||
interface BuildParamsMeta {
|
interface BuildParamsMeta {
|
||||||
isDev: boolean | undefined;
|
isDev: boolean | undefined;
|
||||||
env?: EnvConfig;
|
env?: EnvConfig;
|
||||||
@@ -825,10 +832,14 @@ export const build = async ({
|
|||||||
let dynamicPrefix = path.join('/', entryDirectory);
|
let dynamicPrefix = path.join('/', entryDirectory);
|
||||||
dynamicPrefix = dynamicPrefix === '/' ? '' : dynamicPrefix;
|
dynamicPrefix = dynamicPrefix === '/' ? '' : dynamicPrefix;
|
||||||
|
|
||||||
|
const routesManifest = await getRoutesManifest(entryPath, realNextVersion)
|
||||||
|
|
||||||
const dynamicRoutes = await getDynamicRoutes(
|
const dynamicRoutes = await getDynamicRoutes(
|
||||||
entryPath,
|
entryPath,
|
||||||
entryDirectory,
|
entryDirectory,
|
||||||
dynamicPages
|
dynamicPages,
|
||||||
|
false,
|
||||||
|
routesManifest
|
||||||
).then(arr =>
|
).then(arr =>
|
||||||
arr.map(route => {
|
arr.map(route => {
|
||||||
// make sure .html is added to dest for now until
|
// make sure .html is added to dest for now until
|
||||||
@@ -841,6 +852,26 @@ export const build = async ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const rewrites: Route[] = []
|
||||||
|
const redirects: Route[] = []
|
||||||
|
|
||||||
|
if (routesManifest) {
|
||||||
|
switch(routesManifest.version) {
|
||||||
|
case 1: {
|
||||||
|
redirects.push(...convertRedirects(routesManifest.redirects))
|
||||||
|
rewrites.push(...convertRewrites(routesManifest.rewrites))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// update MIN_ROUTES_MANIFEST_VERSION in ./utils.ts
|
||||||
|
throw new Error(
|
||||||
|
'This version of `@now/next` does not support the version of Next.js you are trying to deploy.\n' +
|
||||||
|
'Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
output: {
|
output: {
|
||||||
...publicDirectoryFiles,
|
...publicDirectoryFiles,
|
||||||
@@ -852,6 +883,8 @@ export const build = async ({
|
|||||||
...staticDirectoryFiles,
|
...staticDirectoryFiles,
|
||||||
},
|
},
|
||||||
routes: [
|
routes: [
|
||||||
|
...redirects,
|
||||||
|
...rewrites,
|
||||||
// Static exported pages (.html rewrites)
|
// Static exported pages (.html rewrites)
|
||||||
...exportedPageRoutes,
|
...exportedPageRoutes,
|
||||||
// Before we handle static files we need to set proper caching headers
|
// Before we handle static files we need to set proper caching headers
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import zlib from 'zlib';
|
import zlib from 'zlib';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import semver from 'semver';
|
||||||
import { ZipFile } from 'yazl';
|
import { ZipFile } from 'yazl';
|
||||||
import crc32 from 'buffer-crc32';
|
import crc32 from 'buffer-crc32';
|
||||||
import { Sema } from 'async-sema';
|
import { Sema } from 'async-sema';
|
||||||
@@ -291,15 +292,38 @@ async function getRoutes(
|
|||||||
return routes;
|
return routes;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getDynamicRoutes(
|
export type Rewrite = {
|
||||||
|
source: string,
|
||||||
|
destination: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Redirect = Rewrite & {
|
||||||
|
statusCode?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
type RoutesManifestRegex = {
|
||||||
|
regex: string,
|
||||||
|
regexKeys: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RoutesManifest = {
|
||||||
|
redirects: (Redirect & RoutesManifestRegex)[],
|
||||||
|
rewrites: (Rewrite & RoutesManifestRegex)[],
|
||||||
|
dynamicRoutes: {
|
||||||
|
page: string,
|
||||||
|
regex: string,
|
||||||
|
}[],
|
||||||
|
version: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getRoutesManifest(
|
||||||
entryPath: string,
|
entryPath: string,
|
||||||
entryDirectory: string,
|
nextVersion?: string,
|
||||||
dynamicPages: string[],
|
): Promise< RoutesManifest | undefined> {
|
||||||
isDev?: boolean
|
const shouldHaveManifest = (
|
||||||
): Promise<{ src: string; dest: string }[]> {
|
nextVersion && semver.gte(nextVersion, '9.1.4-canary.0')
|
||||||
if (!dynamicPages.length) {
|
)
|
||||||
return [];
|
if (!shouldHaveManifest) return
|
||||||
}
|
|
||||||
|
|
||||||
const pathRoutesManifest = path.join(
|
const pathRoutesManifest = path.join(
|
||||||
entryPath,
|
entryPath,
|
||||||
@@ -311,11 +335,30 @@ export async function getDynamicRoutes(
|
|||||||
.then(() => true)
|
.then(() => true)
|
||||||
.catch(() => false);
|
.catch(() => false);
|
||||||
|
|
||||||
if (hasRoutesManifest) {
|
if (shouldHaveManifest && !hasRoutesManifest) {
|
||||||
const routesManifest = await fs.readJSON(pathRoutesManifest);
|
throw new Error(
|
||||||
|
`A routes-manifest.json couldn't be found. This could be due to a failure during the build`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const routesManifest: RoutesManifest = require(pathRoutesManifest)
|
||||||
|
|
||||||
|
return routesManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDynamicRoutes(
|
||||||
|
entryPath: string,
|
||||||
|
entryDirectory: string,
|
||||||
|
dynamicPages: string[],
|
||||||
|
isDev?: boolean,
|
||||||
|
routesManifest?: RoutesManifest
|
||||||
|
): Promise<{ src: string; dest: string }[]> {
|
||||||
|
if (!dynamicPages.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (routesManifest) {
|
||||||
switch (routesManifest.version) {
|
switch (routesManifest.version) {
|
||||||
case 0:
|
|
||||||
case 1: {
|
case 1: {
|
||||||
return routesManifest.dynamicRoutes.map(
|
return routesManifest.dynamicRoutes.map(
|
||||||
({ page, regex }: { page: string; regex: string }) => {
|
({ page, regex }: { page: string; regex: string }) => {
|
||||||
@@ -327,6 +370,7 @@ export async function getDynamicRoutes(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
// update MIN_ROUTES_MANIFEST_VERSION
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'This version of `@now/next` does not support the version of Next.js you are trying to deploy.\n' +
|
'This version of `@now/next` does not support the version of Next.js you are trying to deploy.\n' +
|
||||||
'Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
|
'Please upgrade your `@now/next` builder and try again. Contact support if this continues to happen.'
|
||||||
@@ -338,9 +382,6 @@ export async function getDynamicRoutes(
|
|||||||
// FALLBACK:
|
// FALLBACK:
|
||||||
// When `routes-manifest.json` does not exist (old Next.js versions), we'll try to
|
// When `routes-manifest.json` does not exist (old Next.js versions), we'll try to
|
||||||
// require the methods we need from Next.js' internals.
|
// require the methods we need from Next.js' internals.
|
||||||
//
|
|
||||||
// TODO: implement this branch behind a Next.js version check so we don't "fallback"
|
|
||||||
// to this behavior blindly.
|
|
||||||
let getRouteRegex:
|
let getRouteRegex:
|
||||||
| ((pageName: string) => { re: RegExp })
|
| ((pageName: string) => { re: RegExp })
|
||||||
| undefined = undefined;
|
| undefined = undefined;
|
||||||
|
|||||||
42
packages/now-next/test/fixtures/07-custom-routes/next.config.js
vendored
Normal file
42
packages/now-next/test/fixtures/07-custom-routes/next.config.js
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
module.exports = {
|
||||||
|
experimental: {
|
||||||
|
async redirects() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/redir1',
|
||||||
|
destination: '/redir2',
|
||||||
|
statusCode: 301
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/redir2',
|
||||||
|
destination: '/hello',
|
||||||
|
statusCode: 307
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/redir/:path',
|
||||||
|
destination: '/:path'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/rewrite1',
|
||||||
|
destination: '/rewrite2'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/rewrite2',
|
||||||
|
destination: '/hello'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/rewrite/:first/:second',
|
||||||
|
destination: '/rewrite-2/hello/:second'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/rewrite-2/:first/:second',
|
||||||
|
destination: '/params'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
packages/now-next/test/fixtures/07-custom-routes/now.json
vendored
Normal file
52
packages/now-next/test/fixtures/07-custom-routes/now.json
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"builds": [{ "src": "package.json", "use": "@now/next" }],
|
||||||
|
"probes": [
|
||||||
|
{
|
||||||
|
"path": "/redir1",
|
||||||
|
"fetchOptions": {
|
||||||
|
"redirect": "manual"
|
||||||
|
},
|
||||||
|
"status": 301,
|
||||||
|
"responseHeaders": {
|
||||||
|
"location": "/redir2/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/redir2",
|
||||||
|
"fetchOptions": {
|
||||||
|
"redirect": "manual"
|
||||||
|
},
|
||||||
|
"status": 307,
|
||||||
|
"responseHeaders": {
|
||||||
|
"location": "/hello/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/redir/hello",
|
||||||
|
"fetchOptions": {
|
||||||
|
"redirect": "manual"
|
||||||
|
},
|
||||||
|
"responseHeadersCo": {
|
||||||
|
"location": "/hello/"
|
||||||
|
},
|
||||||
|
"status": 307
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rewrite1",
|
||||||
|
"mustContain": "hello world!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rewrite2",
|
||||||
|
"mustContain": "hello world!"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rewrite/THIS_SHOULD_BE_GONE/another",
|
||||||
|
"mustContain": "hello"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "/rewrite/THIS_SHOULD_BE_GONE/another",
|
||||||
|
"mustContain": "another"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
packages/now-next/test/fixtures/07-custom-routes/package.json
vendored
Normal file
7
packages/now-next/test/fixtures/07-custom-routes/package.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"next": "^9.1.4-canary.1",
|
||||||
|
"react": "^16.8.6",
|
||||||
|
"react-dom": "^16.8.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
packages/now-next/test/fixtures/07-custom-routes/pages/hello.js
vendored
Normal file
1
packages/now-next/test/fixtures/07-custom-routes/pages/hello.js
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default () => 'hello world!'
|
||||||
10
packages/now-next/test/fixtures/07-custom-routes/pages/params.js
vendored
Normal file
10
packages/now-next/test/fixtures/07-custom-routes/pages/params.js
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useRouter } from 'next/router'
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
const { query } = useRouter()
|
||||||
|
return <p>{JSON.stringify(query)}</p>
|
||||||
|
}
|
||||||
|
|
||||||
|
Page.getInitialProps = () => ({ a: 'b' })
|
||||||
|
|
||||||
|
export default Page
|
||||||
@@ -89,7 +89,11 @@ async function testDeployment (
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const probeUrl = `https://${deploymentUrl}${probe.path}`;
|
const probeUrl = `https://${deploymentUrl}${probe.path}`;
|
||||||
const fetchOpts = { method: probe.method, headers: { ...probe.headers } };
|
const fetchOpts = {
|
||||||
|
...probe.fetchOptions,
|
||||||
|
method: probe.method,
|
||||||
|
headers: { ...probe.headers },
|
||||||
|
};
|
||||||
if (probe.body) {
|
if (probe.body) {
|
||||||
fetchOpts.headers['content-type'] = 'application/json';
|
fetchOpts.headers['content-type'] = 'application/json';
|
||||||
fetchOpts.body = JSON.stringify(probe.body);
|
fetchOpts.body = JSON.stringify(probe.body);
|
||||||
|
|||||||
Reference in New Issue
Block a user