Skip to content

Commit

Permalink
refactor: make locales array immutable (#74037)
Browse files Browse the repository at this point in the history
To improve memory efficiency, this marks all the locale arrays as
readonly. This ensures that there's no undefined behaviour from
accidentally mutating the array, and allows us to use the locales array
as a stable reference for a future optimization PR.
  • Loading branch information
wyattjoh authored Dec 17, 2024
1 parent d5fc05b commit 4f5df6a
Show file tree
Hide file tree
Showing 16 changed files with 52 additions and 39 deletions.
14 changes: 9 additions & 5 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,13 @@ export type RoutesManifest = {
dynamicRoutes: Array<ManifestRoute>
dataRoutes: Array<ManifestDataRoute>
i18n?: {
domains?: Array<{
domains?: ReadonlyArray<{
http?: true
domain: string
locales?: string[]
locales?: readonly string[]
defaultLocale: string
}>
locales: string[]
locales: readonly string[]
defaultLocale: string
localeDetection?: false
}
Expand Down Expand Up @@ -473,7 +473,11 @@ async function writeClientSsgManifest(
buildId,
distDir,
locales,
}: { buildId: string; distDir: string; locales: string[] }
}: {
buildId: string
distDir: string
locales: readonly string[] | undefined
}
) {
const ssgPages = new Set<string>(
[
Expand Down Expand Up @@ -3622,7 +3626,7 @@ export default async function build(
await writeClientSsgManifest(prerenderManifest, {
distDir,
buildId,
locales: config.i18n?.locales || [],
locales: config.i18n?.locales,
})
} else {
await writePrerenderManifest(distDir, {
Expand Down
10 changes: 8 additions & 2 deletions packages/next/src/build/static-paths/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function buildPagesStaticPaths({
page: string
getStaticPaths: GetStaticPaths
configFileName: string
locales?: string[]
locales?: readonly string[]
defaultLocale?: string
}): Promise<StaticPathsResult> {
const prerenderedRoutes: PrerenderedRoute[] = []
Expand All @@ -28,7 +28,13 @@ export async function buildPagesStaticPaths({

// Get the default list of allowed params.
const routeParameterKeys = Object.keys(_routeMatcher(page))
const staticPathsResult = await getStaticPaths({ locales, defaultLocale })
const staticPathsResult = await getStaticPaths({
// We create a copy here to avoid having the types of `getStaticPaths`
// change. This ensures that users can't mutate this array and have it
// poison the reference.
locales: [...(locales ?? [])],
defaultLocale,
})

const expectedReturnVal =
`Expected: { paths: [], fallback: boolean }\n` +
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ export async function isPageStatic({
configFileName: string
runtimeEnvConfig: any
httpAgentOptions: NextConfigComplete['httpAgentOptions']
locales?: string[]
locales?: readonly string[]
defaultLocale?: string
parentId?: any
edgeInfo?: any
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/client/get-domain-locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ const basePath = (process.env.__NEXT_ROUTER_BASEPATH as string) || ''
export function getDomainLocale(
path: string,
locale?: string | false,
locales?: string[],
domainLocales?: DomainLocale[]
locales?: readonly string[],
domainLocales?: readonly DomainLocale[]
) {
if (process.env.__NEXT_I18N_SUPPORT) {
const normalizeLocalePath: typeof NormalizeFn =
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/server/accept-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ interface Options {

function parse(
raw: string,
preferences: string[] | undefined,
preferences: readonly string[] | undefined,
options: Options
) {
const lowers = new Map<string, { orig: string; pos: number }>()
Expand Down Expand Up @@ -127,7 +127,7 @@ function parse(
return preferred
}

export function acceptLanguage(header = '', preferences?: string[]) {
export function acceptLanguage(header = '', preferences?: readonly string[]) {
return (
parse(header, preferences, {
type: 'accept-language',
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1437,7 +1437,7 @@ export default abstract class Server<
}
const normalizeResult = normalizeLocalePath(
removePathPrefix(parsedUrl.pathname, this.nextConfig.basePath || ''),
this.nextConfig.i18n?.locales || []
this.nextConfig.i18n?.locales
)

if (normalizeResult.detectedLocale) {
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@ export type NextConfigComplete = Required<NextConfig> & {
configFileName: string
}

export type I18NDomains = DomainLocale[]
export type I18NDomains = readonly DomainLocale[]

export interface I18NConfig {
defaultLocale: string
domains?: I18NDomains
localeDetection?: false
locales: string[]
locales: readonly string[]
}

export interface DomainLocale {
defaultLocale: string
domain: string
http?: true
locales?: string[]
locales?: readonly string[]
}

export interface ESLintConfig {
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/dev/static-paths-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export async function loadStaticPaths({
pathname: string
config: RuntimeConfig
httpAgentOptions: NextConfigComplete['httpAgentOptions']
locales?: string[]
locales?: readonly string[]
defaultLocale?: string
isAppPath: boolean
page: string
Expand Down
19 changes: 11 additions & 8 deletions packages/next/src/server/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,9 @@ class ServerRouter implements NextRouter {
isFallback: boolean
locale?: string
isReady: boolean
locales?: string[]
locales?: readonly string[]
defaultLocale?: string
domainLocales?: DomainLocale[]
domainLocales?: readonly DomainLocale[]
isPreview: boolean
isLocaleDomain: boolean

Expand All @@ -156,9 +156,9 @@ class ServerRouter implements NextRouter {
isReady: boolean,
basePath: string,
locale?: string,
locales?: string[],
locales?: readonly string[],
defaultLocale?: string,
domainLocales?: DomainLocale[],
domainLocales?: readonly DomainLocale[],
isPreview?: boolean,
isLocaleDomain?: boolean
) {
Expand Down Expand Up @@ -261,9 +261,9 @@ export type RenderOptsPartial = {
nextFontManifest?: DeepReadonly<NextFontManifest>
distDir?: string
locale?: string
locales?: string[]
locales?: readonly string[]
defaultLocale?: string
domainLocales?: DomainLocale[]
domainLocales?: readonly DomainLocale[]
disableOptimizedLoading?: boolean
supportsDynamicResponse: boolean
isBot?: boolean
Expand Down Expand Up @@ -848,7 +848,7 @@ export async function renderToHTMLImpl(
...(isPreview
? { draftMode: true, preview: true, previewData: previewData }
: undefined),
locales: renderOpts.locales,
locales: [...(renderOpts.locales ?? [])],
locale: renderOpts.locale,
defaultLocale: renderOpts.defaultLocale,
revalidateReason: renderOpts.isOnDemandRevalidate
Expand Down Expand Up @@ -1071,7 +1071,10 @@ export async function renderToHTMLImpl(
...(previewData !== false
? { draftMode: true, preview: true, previewData: previewData }
: undefined),
locales: renderOpts.locales,
// We create a copy here to avoid having the types of
// `getServerSideProps` change. This ensures that users can't
// mutate this array and have it poison the reference.
locales: [...(renderOpts.locales ?? [])],
locale: renderOpts.locale,
defaultLocale: renderOpts.defaultLocale,
})
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/require.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const pagePathCache = !isDev ? new LRUCache<string | null>(1000) : null
export function getMaybePagePath(
page: string,
distDir: string,
locales: string[] | undefined,
locales: readonly string[] | undefined,
isAppPath: boolean
): string | null {
const cacheKey = `${page}:${distDir}:${locales}:${isAppPath}`
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/shared/lib/i18n/detect-domain-locale.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { DomainLocale } from '../../../server/config-shared'

export function detectDomainLocale(
domainItems?: DomainLocale[],
domainItems?: readonly DomainLocale[],
hostname?: string,
detectedLocale?: string
) {
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/shared/lib/i18n/normalize-locale-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface PathLocale {
*/
export function normalizeLocalePath(
pathname: string,
locales?: string[]
locales?: readonly string[]
): PathLocale {
let detectedLocale: string | undefined
// first item will be empty string from splitting at first char
Expand Down
12 changes: 6 additions & 6 deletions packages/next/src/shared/lib/router/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,9 @@ export type BaseRouter = {
asPath: string
basePath: string
locale?: string | undefined
locales?: string[] | undefined
locales?: readonly string[] | undefined
defaultLocale?: string | undefined
domainLocales?: DomainLocale[] | undefined
domainLocales?: readonly DomainLocale[] | undefined
isLocaleDomain: boolean
}

Expand Down Expand Up @@ -681,9 +681,9 @@ export default class Router implements BaseRouter {
isSsr: boolean
_inFlightRoute?: string | undefined
_shallow?: boolean | undefined
locales?: string[] | undefined
locales?: readonly string[] | undefined
defaultLocale?: string | undefined
domainLocales?: DomainLocale[] | undefined
domainLocales?: readonly DomainLocale[] | undefined
isReady: boolean
isLocaleDomain: boolean
isFirstPopStateEvent = true
Expand Down Expand Up @@ -735,9 +735,9 @@ export default class Router implements BaseRouter {
err?: Error
isFallback: boolean
locale?: string
locales?: string[]
locales?: readonly string[]
defaultLocale?: string
domainLocales?: DomainLocale[]
domainLocales?: readonly DomainLocale[]
isPreview?: boolean
}
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ interface Options {
*/
nextConfig?: {
basePath?: string
i18n?: { locales?: string[] } | null
i18n?: { locales?: readonly string[] } | null
trailingSlash?: boolean
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default function resolveRewrites(
},
query: ParsedUrlQuery,
resolveHref: (path: string) => string,
locales?: string[]
locales?: readonly string[]
): {
matchedPage: boolean
parsedAs: ParsedRelativeUrl
Expand Down
6 changes: 3 additions & 3 deletions packages/next/src/shared/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ export type NEXT_DATA = {
gip?: boolean
appGip?: boolean
locale?: string
locales?: string[]
locales?: readonly string[]
defaultLocale?: string
domainLocales?: DomainLocale[]
domainLocales?: readonly DomainLocale[]
scriptLoader?: any[]
isPreview?: boolean
notFoundSrcPage?: string
Expand Down Expand Up @@ -150,7 +150,7 @@ export interface NextPageContext {
/**
* All configured locales
*/
locales?: string[]
locales?: readonly string[]
/**
* The configured default locale
*/
Expand Down

0 comments on commit 4f5df6a

Please sign in to comment.