b2d1d87e7f
This adds the initial changes outlined in the [i18n routing RFC](https://github.com/vercel/next.js/discussions/17078). This currently treats the locale prefix on routes similar to how the basePath is treated in that the config doesn't require any changes to your pages directory and is automatically stripped/added based on the detected locale that should be used. Currently redirecting occurs on the `/` route if a locale is detected regardless of if an optional catch-all route would match the `/` route or not we may want to investigate whether we want to disable this redirection automatically if an `/index.js` file isn't present at root of the pages directory. TODO: - [x] ensure locale detection/populating works in serverless mode correctly - [x] add tests for locale handling in different modes, fallback/getStaticProps/getServerSideProps To be continued in fall-up PRs - [ ] add tests for revalidate, auto-export, basePath + i18n - [ ] add mapping of domains with locales - [ ] investigate detecting locale against non-index routes and populating the locale in a cookie x-ref: https://github.com/vercel/next.js/issues/17110
165 lines
5 KiB
TypeScript
165 lines
5 KiB
TypeScript
import chalk from 'next/dist/compiled/chalk'
|
|
import { posix, join } from 'path'
|
|
import { stringify } from 'querystring'
|
|
import { API_ROUTE, DOT_NEXT_ALIAS, PAGES_DIR_ALIAS } from '../lib/constants'
|
|
import { __ApiPreviewProps } from '../next-server/server/api-utils'
|
|
import { isTargetLikeServerless } from '../next-server/server/config'
|
|
import { normalizePagePath } from '../next-server/server/normalize-page-path'
|
|
import { warn } from './output/log'
|
|
import { ClientPagesLoaderOptions } from './webpack/loaders/next-client-pages-loader'
|
|
import { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader'
|
|
import { LoadedEnvFiles } from '@next/env'
|
|
|
|
type PagesMapping = {
|
|
[page: string]: string
|
|
}
|
|
|
|
export function createPagesMapping(
|
|
pagePaths: string[],
|
|
extensions: string[]
|
|
): PagesMapping {
|
|
const previousPages: PagesMapping = {}
|
|
const pages: PagesMapping = pagePaths.reduce(
|
|
(result: PagesMapping, pagePath): PagesMapping => {
|
|
let page = `${pagePath
|
|
.replace(new RegExp(`\\.+(${extensions.join('|')})$`), '')
|
|
.replace(/\\/g, '/')}`.replace(/\/index$/, '')
|
|
|
|
const pageKey = page === '' ? '/' : page
|
|
|
|
if (pageKey in result) {
|
|
warn(
|
|
`Duplicate page detected. ${chalk.cyan(
|
|
join('pages', previousPages[pageKey])
|
|
)} and ${chalk.cyan(
|
|
join('pages', pagePath)
|
|
)} both resolve to ${chalk.cyan(pageKey)}.`
|
|
)
|
|
} else {
|
|
previousPages[pageKey] = pagePath
|
|
}
|
|
result[pageKey] = join(PAGES_DIR_ALIAS, pagePath).replace(/\\/g, '/')
|
|
return result
|
|
},
|
|
{}
|
|
)
|
|
|
|
pages['/_app'] = pages['/_app'] || 'next/dist/pages/_app'
|
|
pages['/_error'] = pages['/_error'] || 'next/dist/pages/_error'
|
|
pages['/_document'] = pages['/_document'] || 'next/dist/pages/_document'
|
|
|
|
return pages
|
|
}
|
|
|
|
export type WebpackEntrypoints = {
|
|
[bundle: string]: string | string[]
|
|
}
|
|
|
|
type Entrypoints = {
|
|
client: WebpackEntrypoints
|
|
server: WebpackEntrypoints
|
|
}
|
|
|
|
export function createEntrypoints(
|
|
pages: PagesMapping,
|
|
target: 'server' | 'serverless' | 'experimental-serverless-trace',
|
|
buildId: string,
|
|
previewMode: __ApiPreviewProps,
|
|
config: any,
|
|
loadedEnvFiles: LoadedEnvFiles
|
|
): Entrypoints {
|
|
const client: WebpackEntrypoints = {}
|
|
const server: WebpackEntrypoints = {}
|
|
|
|
const hasRuntimeConfig =
|
|
Object.keys(config.publicRuntimeConfig).length > 0 ||
|
|
Object.keys(config.serverRuntimeConfig).length > 0
|
|
|
|
const defaultServerlessOptions = {
|
|
absoluteAppPath: pages['/_app'],
|
|
absoluteDocumentPath: pages['/_document'],
|
|
absoluteErrorPath: pages['/_error'],
|
|
distDir: DOT_NEXT_ALIAS,
|
|
buildId,
|
|
assetPrefix: config.assetPrefix,
|
|
generateEtags: config.generateEtags,
|
|
poweredByHeader: config.poweredByHeader,
|
|
canonicalBase: config.canonicalBase,
|
|
basePath: config.basePath,
|
|
runtimeConfig: hasRuntimeConfig
|
|
? JSON.stringify({
|
|
publicRuntimeConfig: config.publicRuntimeConfig,
|
|
serverRuntimeConfig: config.serverRuntimeConfig,
|
|
})
|
|
: '',
|
|
previewProps: JSON.stringify(previewMode),
|
|
// base64 encode to make sure contents don't break webpack URL loading
|
|
loadedEnvFiles: Buffer.from(JSON.stringify(loadedEnvFiles)).toString(
|
|
'base64'
|
|
),
|
|
i18n: config.experimental.i18n
|
|
? JSON.stringify(config.experimental.i18n)
|
|
: '',
|
|
}
|
|
|
|
Object.keys(pages).forEach((page) => {
|
|
const absolutePagePath = pages[page]
|
|
const bundleFile = normalizePagePath(page)
|
|
const isApiRoute = page.match(API_ROUTE)
|
|
|
|
const clientBundlePath = posix.join('pages', bundleFile)
|
|
const serverBundlePath = posix.join('pages', bundleFile)
|
|
|
|
const isLikeServerless = isTargetLikeServerless(target)
|
|
|
|
if (isApiRoute && isLikeServerless) {
|
|
const serverlessLoaderOptions: ServerlessLoaderQuery = {
|
|
page,
|
|
absolutePagePath,
|
|
...defaultServerlessOptions,
|
|
}
|
|
server[serverBundlePath] = `next-serverless-loader?${stringify(
|
|
serverlessLoaderOptions
|
|
)}!`
|
|
} else if (isApiRoute || target === 'server') {
|
|
server[serverBundlePath] = [absolutePagePath]
|
|
} else if (isLikeServerless && page !== '/_app' && page !== '/_document') {
|
|
const serverlessLoaderOptions: ServerlessLoaderQuery = {
|
|
page,
|
|
absolutePagePath,
|
|
...defaultServerlessOptions,
|
|
}
|
|
server[serverBundlePath] = `next-serverless-loader?${stringify(
|
|
serverlessLoaderOptions
|
|
)}!`
|
|
}
|
|
|
|
if (page === '/_document') {
|
|
return
|
|
}
|
|
|
|
if (!isApiRoute) {
|
|
const pageLoaderOpts: ClientPagesLoaderOptions = {
|
|
page,
|
|
absolutePagePath,
|
|
}
|
|
const pageLoader = `next-client-pages-loader?${stringify(
|
|
pageLoaderOpts
|
|
)}!`
|
|
|
|
// Make sure next/router is a dependency of _app or else chunk splitting
|
|
// might cause the router to not be able to load causing hydration
|
|
// to fail
|
|
|
|
client[clientBundlePath] =
|
|
page === '/_app'
|
|
? [pageLoader, require.resolve('../client/router')]
|
|
: pageLoader
|
|
}
|
|
})
|
|
|
|
return {
|
|
client,
|
|
server,
|
|
}
|
|
}
|