From 41f78b79325fd44a408cb69d5842a8fdf577db77 Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Wed, 27 May 2020 10:31:37 +0200 Subject: [PATCH 1/4] chore(next-server): define a Config type --- packages/next/next-server/server/config.ts | 135 ++++++++++----------- 1 file changed, 67 insertions(+), 68 deletions(-) diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts index 918d8b0ce9fc0..b8052d4fd57f5 100644 --- a/packages/next/next-server/server/config.ts +++ b/packages/next/next-server/server/config.ts @@ -10,7 +10,9 @@ import * as Log from '../../build/output/log' const targets = ['server', 'serverless', 'experimental-serverless-trace'] const reactModes = ['legacy', 'blocking', 'concurrent'] -const defaultConfig: { [key: string]: any } = { +type Config = { [key: string]: any } + +const defaultConfig: Config = { env: [], webpack: null, webpackDevMiddleware: null, @@ -72,84 +74,81 @@ const experimentalWarning = execOnce(() => { console.warn() }) -function assignDefaults(userConfig: { [key: string]: any }) { - const config = Object.keys(userConfig).reduce<{ [key: string]: any }>( - (config, key) => { - const value = userConfig[key] +function assignDefaults(userConfig: Config): Config { + const config = Object.keys(userConfig).reduce((config, key) => { + const value = userConfig[key] - if (value === undefined || value === null) { - return config - } + if (value === undefined || value === null) { + return config + } + + if (key === 'experimental' && value && value !== defaultConfig[key]) { + experimentalWarning() + } - if (key === 'experimental' && value && value !== defaultConfig[key]) { - experimentalWarning() + if (key === 'distDir') { + if (typeof value !== 'string') { + throw new Error( + `Specified distDir is not a string, found type "${typeof value}"` + ) } + const userDistDir = value.trim() - if (key === 'distDir') { - if (typeof value !== 'string') { - throw new Error( - `Specified distDir is not a string, found type "${typeof value}"` - ) - } - const userDistDir = value.trim() + // don't allow public as the distDir as this is a reserved folder for + // public files + if (userDistDir === 'public') { + throw new Error( + `The 'public' directory is reserved in Next.js and can not be set as the 'distDir'. https://err.sh/zeit/next.js/can-not-output-to-public` + ) + } + // make sure distDir isn't an empty string as it can result in the provided + // directory being deleted in development mode + if (userDistDir.length === 0) { + throw new Error( + `Invalid distDir provided, distDir can not be an empty string. Please remove this config or set it to undefined` + ) + } + } - // don't allow public as the distDir as this is a reserved folder for - // public files - if (userDistDir === 'public') { - throw new Error( - `The 'public' directory is reserved in Next.js and can not be set as the 'distDir'. https://err.sh/zeit/next.js/can-not-output-to-public` - ) - } - // make sure distDir isn't an empty string as it can result in the provided - // directory being deleted in development mode - if (userDistDir.length === 0) { - throw new Error( - `Invalid distDir provided, distDir can not be an empty string. Please remove this config or set it to undefined` - ) - } + if (key === 'pageExtensions') { + if (!Array.isArray(value)) { + throw new Error( + `Specified pageExtensions is not an array of strings, found "${value}". Please update this config or remove it.` + ) } - if (key === 'pageExtensions') { - if (!Array.isArray(value)) { - throw new Error( - `Specified pageExtensions is not an array of strings, found "${value}". Please update this config or remove it.` - ) - } + if (!value.length) { + throw new Error( + `Specified pageExtensions is an empty array. Please update it with the relevant extensions or remove it.` + ) + } - if (!value.length) { + value.forEach((ext) => { + if (typeof ext !== 'string') { throw new Error( - `Specified pageExtensions is an empty array. Please update it with the relevant extensions or remove it.` + `Specified pageExtensions is not an array of strings, found "${ext}" of type "${typeof ext}". Please update this config or remove it.` ) } + }) + } - value.forEach((ext) => { - if (typeof ext !== 'string') { - throw new Error( - `Specified pageExtensions is not an array of strings, found "${ext}" of type "${typeof ext}". Please update this config or remove it.` - ) + if (!!value && value.constructor === Object) { + config[key] = { + ...defaultConfig[key], + ...Object.keys(value).reduce((c, k) => { + const v = value[k] + if (v !== undefined && v !== null) { + c[k] = v } - }) - } - - if (!!value && value.constructor === Object) { - config[key] = { - ...defaultConfig[key], - ...Object.keys(value).reduce((c, k) => { - const v = value[k] - if (v !== undefined && v !== null) { - c[k] = v - } - return c - }, {}), - } - } else { - config[key] = value + return c + }, {}), } + } else { + config[key] = value + } - return config - }, - {} - ) + return config + }, {}) const result = { ...defaultConfig, ...config } @@ -195,7 +194,7 @@ function assignDefaults(userConfig: { [key: string]: any }) { return result } -export function normalizeConfig(phase: string, config: any) { +export function normalizeConfig(phase: string, config: Config): Config { if (typeof config === 'function') { config = config(phase, { defaultConfig }) @@ -211,8 +210,8 @@ export function normalizeConfig(phase: string, config: any) { export default function loadConfig( phase: string, dir: string, - customConfig?: object | null -) { + customConfig?: Config | null +): Config { if (customConfig) { return assignDefaults({ configOrigin: 'server', ...customConfig }) } @@ -287,7 +286,7 @@ export default function loadConfig( return defaultConfig } -export function isTargetLikeServerless(target: string) { +export function isTargetLikeServerless(target: string): boolean { const isServerless = target === 'serverless' const isServerlessTrace = target === 'experimental-serverless-trace' return isServerless || isServerlessTrace From 6a7c4fcd1834d601839fc757b5f99b9fdc4e7c77 Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Wed, 27 May 2020 18:53:07 +0200 Subject: [PATCH 2/4] chore: define a proper interface for Config --- packages/next/next-server/server/config.ts | 63 ++++++++++++++++++++-- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts index b8052d4fd57f5..ee508f6681e34 100644 --- a/packages/next/next-server/server/config.ts +++ b/packages/next/next-server/server/config.ts @@ -10,7 +10,58 @@ import * as Log from '../../build/output/log' const targets = ['server', 'serverless', 'experimental-serverless-trace'] const reactModes = ['legacy', 'blocking', 'concurrent'] -type Config = { [key: string]: any } +interface Config { + env: any[] + webpack: any | null + webpackDevMiddleware: any | null + distDir: string + assetPrefix: string + configOrigin: string + useFileSystemPublicRoutes: boolean + generateBuildId: () => null + generateEtags: boolean + pageExtensions: string[] + target: 'server' | 'serverless' | 'experimental-serverless-trace' + poweredByHeader: boolean + compress: boolean + devIndicators: { + buildActivity: boolean + autoPrerender: boolean + } + onDemandEntries: { + maxInactiveAge: number + pagesBufferLength: number + } + amp: { + canonicalBase: string + } + exportTrailingSlash: boolean + sassOptions: object + experimental: { + cpus: number + granularChunks: boolean + modern: boolean + plugins: boolean + profiling: boolean + sprFlushToDisk: boolean + reactMode: string + workerThreads: boolean + basePath: string + pageEnv: boolean + productionBrowserSourceMaps: boolean + optionalCatchAll: boolean + redirects?: any + rewrites?: any + headers?: any + } + future: { + excludeDefaultMomentLocales: boolean + } + serverRuntimeConfig: object + publicRuntimeConfig: object + reactStrictMode: boolean + exportPathMap?: any +} const defaultConfig: Config = { env: [], @@ -75,8 +126,10 @@ const experimentalWarning = execOnce(() => { }) function assignDefaults(userConfig: Config): Config { - const config = Object.keys(userConfig).reduce((config, key) => { - const value = userConfig[key] + const config = Object.keys(userConfig).reduce<{ + [key: string]: Partial + }>((config, key) => { + const value = userConfig[key as keyof Config] if (value === undefined || value === null) { return config @@ -134,7 +187,7 @@ function assignDefaults(userConfig: Config): Config { if (!!value && value.constructor === Object) { config[key] = { - ...defaultConfig[key], + ...defaultConfig[key as keyof Config], ...Object.keys(value).reduce((c, k) => { const v = value[k] if (v !== undefined && v !== null) { @@ -194,7 +247,7 @@ function assignDefaults(userConfig: Config): Config { return result } -export function normalizeConfig(phase: string, config: Config): Config { +export function normalizeConfig(phase: string, config: Config | any): Config { if (typeof config === 'function') { config = config(phase, { defaultConfig }) From ad2c2485f5b17eec2bb5622c30d739a4b1e33d94 Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Fri, 29 May 2020 11:21:39 +0200 Subject: [PATCH 3/4] chore: strict type check --- packages/next/build/generate-build-id.ts | 2 +- packages/next/next-server/server/config.ts | 174 +++++++++++---------- 2 files changed, 91 insertions(+), 85 deletions(-) diff --git a/packages/next/build/generate-build-id.ts b/packages/next/build/generate-build-id.ts index 0f9de31c1ddb4..884ed5e062219 100644 --- a/packages/next/build/generate-build-id.ts +++ b/packages/next/build/generate-build-id.ts @@ -1,5 +1,5 @@ export async function generateBuildId( - generate: () => string | null, + generate: () => string | null | Promise, fallback: () => string ): Promise { let buildId = await generate() diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts index 047b300ad2a6d..4f7046bcf06ab 100644 --- a/packages/next/next-server/server/config.ts +++ b/packages/next/next-server/server/config.ts @@ -6,22 +6,40 @@ import { basename, extname } from 'path' import { CONFIG_FILE } from '../lib/constants' import { execOnce } from '../lib/utils' import * as Log from '../../build/output/log' +import webpack from 'webpack' const targets = ['server', 'serverless', 'experimental-serverless-trace'] const reactModes = ['legacy', 'blocking', 'concurrent'] +type ExportPathMap = { + [page: string]: { page: string; query?: { [key: string]: string } } +} + +type Webpack = ( + config: webpack.Configuration, + { + dir, + dev, + isServer, + buildId, + }: { dir: string; dev: boolean; isServer: boolean; buildId: boolean } +) => webpack.Configuration | Promise +type WebpackDevMiddleware = ( + config: webpack.Configuration +) => webpack.Configuration + interface Config { - env: any[] - webpack: any | null - webpackDevMiddleware: any | null + env: { [key: string]: any } + webpack: Webpack | null + webpackDevMiddleware: WebpackDevMiddleware | null distDir: string assetPrefix: string configOrigin: string useFileSystemPublicRoutes: boolean - generateBuildId: () => null + generateBuildId: () => string | null | Promise generateEtags: boolean pageExtensions: string[] - target: 'server' | 'serverless' | 'experimental-serverless-trace' + target: 'server' | 'serverless' poweredByHeader: boolean compress: boolean devIndicators: { @@ -37,30 +55,17 @@ interface Config { } exportTrailingSlash: boolean sassOptions: object - experimental: { - cpus: number - granularChunks: boolean - modern: boolean - plugins: boolean - profiling: boolean - sprFlushToDisk: boolean - reactMode: string - workerThreads: boolean - basePath: string - pageEnv: boolean - productionBrowserSourceMaps: boolean - optionalCatchAll: boolean - redirects?: any - rewrites?: any - headers?: any - } + experimental: { [key: string]: any } future: { excludeDefaultMomentLocales: boolean } serverRuntimeConfig: object publicRuntimeConfig: object reactStrictMode: boolean - exportPathMap?: any + exportPathMap?: (defaultMap: ExportPathMap) => ExportPathMap + typescript?: { + ignoreBuildErrors: boolean + } } const defaultConfig: Config = { @@ -126,82 +131,83 @@ const experimentalWarning = execOnce(() => { }) function assignDefaults(userConfig: Config): Config { - const config = Object.keys(userConfig).reduce<{ - [key: string]: Partial - }>((config, key) => { - const value = userConfig[key as keyof Config] - - if (value === undefined || value === null) { - return config - } - - if (key === 'experimental' && value && value !== defaultConfig[key]) { - experimentalWarning() - } + const config = Object.keys(userConfig).reduce( + (config, key) => { + const value = (userConfig as any)[key] - if (key === 'distDir') { - if (typeof value !== 'string') { - throw new Error( - `Specified distDir is not a string, found type "${typeof value}"` - ) + if (value === undefined || value === null) { + return config } - const userDistDir = value.trim() - // don't allow public as the distDir as this is a reserved folder for - // public files - if (userDistDir === 'public') { - throw new Error( - `The 'public' directory is reserved in Next.js and can not be set as the 'distDir'. https://err.sh/vercel/next.js/can-not-output-to-public` - ) + if (key === 'experimental' && value && value !== defaultConfig[key]) { + experimentalWarning() } - // make sure distDir isn't an empty string as it can result in the provided - // directory being deleted in development mode - if (userDistDir.length === 0) { - throw new Error( - `Invalid distDir provided, distDir can not be an empty string. Please remove this config or set it to undefined` - ) - } - } - if (key === 'pageExtensions') { - if (!Array.isArray(value)) { - throw new Error( - `Specified pageExtensions is not an array of strings, found "${value}". Please update this config or remove it.` - ) - } + if (key === 'distDir') { + if (typeof value !== 'string') { + throw new Error( + `Specified distDir is not a string, found type "${typeof value}"` + ) + } + const userDistDir = value.trim() - if (!value.length) { - throw new Error( - `Specified pageExtensions is an empty array. Please update it with the relevant extensions or remove it.` - ) + // don't allow public as the distDir as this is a reserved folder for + // public files + if (userDistDir === 'public') { + throw new Error( + `The 'public' directory is reserved in Next.js and can not be set as the 'distDir'. https://err.sh/vercel/next.js/can-not-output-to-public` + ) + } + // make sure distDir isn't an empty string as it can result in the provided + // directory being deleted in development mode + if (userDistDir.length === 0) { + throw new Error( + `Invalid distDir provided, distDir can not be an empty string. Please remove this config or set it to undefined` + ) + } } - value.forEach((ext) => { - if (typeof ext !== 'string') { + if (key === 'pageExtensions') { + if (!Array.isArray(value)) { throw new Error( - `Specified pageExtensions is not an array of strings, found "${ext}" of type "${typeof ext}". Please update this config or remove it.` + `Specified pageExtensions is not an array of strings, found "${value}". Please update this config or remove it.` + ) + } + + if (!value.length) { + throw new Error( + `Specified pageExtensions is an empty array. Please update it with the relevant extensions or remove it.` ) } - }) - } - if (!!value && value.constructor === Object) { - config[key] = { - ...defaultConfig[key as keyof Config], - ...Object.keys(value).reduce((c, k) => { - const v = value[k] - if (v !== undefined && v !== null) { - c[k] = v + value.forEach((ext) => { + if (typeof ext !== 'string') { + throw new Error( + `Specified pageExtensions is not an array of strings, found "${ext}" of type "${typeof ext}". Please update this config or remove it.` + ) } - return c - }, {}), + }) } - } else { - config[key] = value - } - return config - }, {}) + if (!!value && value.constructor === Object) { + ;(config as any)[key] = { + ...(defaultConfig as any)[key], + ...Object.keys(value).reduce((c, k) => { + const v = value[k] + if (v !== undefined && v !== null) { + c[k] = v + } + return c + }, {}), + } + } else { + ;(config as any)[key] = value + } + + return config + }, + {} + ) const result = { ...defaultConfig, ...config } From 599fdb779c75e7c6abe416a61351004e1f89281f Mon Sep 17 00:00:00 2001 From: Maxi Gimenez Date: Sat, 30 May 2020 14:45:58 +0200 Subject: [PATCH 4/4] chore: make object indexeable by string --- packages/next/next-server/server/config.ts | 127 +++++++++++---------- 1 file changed, 64 insertions(+), 63 deletions(-) diff --git a/packages/next/next-server/server/config.ts b/packages/next/next-server/server/config.ts index 4f7046bcf06ab..087c9ab84740b 100644 --- a/packages/next/next-server/server/config.ts +++ b/packages/next/next-server/server/config.ts @@ -28,7 +28,11 @@ type WebpackDevMiddleware = ( config: webpack.Configuration ) => webpack.Configuration -interface Config { +export interface IIndexable { + [key: string]: any +} + +interface Config extends IIndexable { env: { [key: string]: any } webpack: Webpack | null webpackDevMiddleware: WebpackDevMiddleware | null @@ -131,83 +135,80 @@ const experimentalWarning = execOnce(() => { }) function assignDefaults(userConfig: Config): Config { - const config = Object.keys(userConfig).reduce( - (config, key) => { - const value = (userConfig as any)[key] + const config = Object.keys(userConfig).reduce((config, key) => { + const value = userConfig[key] - if (value === undefined || value === null) { - return config - } + if (value === undefined || value === null) { + return config + } - if (key === 'experimental' && value && value !== defaultConfig[key]) { - experimentalWarning() + if (key === 'experimental' && value && value !== defaultConfig[key]) { + experimentalWarning() + } + + if (key === 'distDir') { + if (typeof value !== 'string') { + throw new Error( + `Specified distDir is not a string, found type "${typeof value}"` + ) } + const userDistDir = value.trim() - if (key === 'distDir') { - if (typeof value !== 'string') { - throw new Error( - `Specified distDir is not a string, found type "${typeof value}"` - ) - } - const userDistDir = value.trim() + // don't allow public as the distDir as this is a reserved folder for + // public files + if (userDistDir === 'public') { + throw new Error( + `The 'public' directory is reserved in Next.js and can not be set as the 'distDir'. https://err.sh/vercel/next.js/can-not-output-to-public` + ) + } + // make sure distDir isn't an empty string as it can result in the provided + // directory being deleted in development mode + if (userDistDir.length === 0) { + throw new Error( + `Invalid distDir provided, distDir can not be an empty string. Please remove this config or set it to undefined` + ) + } + } - // don't allow public as the distDir as this is a reserved folder for - // public files - if (userDistDir === 'public') { - throw new Error( - `The 'public' directory is reserved in Next.js and can not be set as the 'distDir'. https://err.sh/vercel/next.js/can-not-output-to-public` - ) - } - // make sure distDir isn't an empty string as it can result in the provided - // directory being deleted in development mode - if (userDistDir.length === 0) { - throw new Error( - `Invalid distDir provided, distDir can not be an empty string. Please remove this config or set it to undefined` - ) - } + if (key === 'pageExtensions') { + if (!Array.isArray(value)) { + throw new Error( + `Specified pageExtensions is not an array of strings, found "${value}". Please update this config or remove it.` + ) } - if (key === 'pageExtensions') { - if (!Array.isArray(value)) { - throw new Error( - `Specified pageExtensions is not an array of strings, found "${value}". Please update this config or remove it.` - ) - } + if (!value.length) { + throw new Error( + `Specified pageExtensions is an empty array. Please update it with the relevant extensions or remove it.` + ) + } - if (!value.length) { + value.forEach((ext) => { + if (typeof ext !== 'string') { throw new Error( - `Specified pageExtensions is an empty array. Please update it with the relevant extensions or remove it.` + `Specified pageExtensions is not an array of strings, found "${ext}" of type "${typeof ext}". Please update this config or remove it.` ) } + }) + } - value.forEach((ext) => { - if (typeof ext !== 'string') { - throw new Error( - `Specified pageExtensions is not an array of strings, found "${ext}" of type "${typeof ext}". Please update this config or remove it.` - ) + if (!!value && value.constructor === Object) { + config[key] = { + ...defaultConfig[key], + ...Object.keys(value).reduce((c, k) => { + const v = value[k] + if (v !== undefined && v !== null) { + c[k] = v } - }) - } - - if (!!value && value.constructor === Object) { - ;(config as any)[key] = { - ...(defaultConfig as any)[key], - ...Object.keys(value).reduce((c, k) => { - const v = value[k] - if (v !== undefined && v !== null) { - c[k] = v - } - return c - }, {}), - } - } else { - ;(config as any)[key] = value + return c + }, {}), } + } else { + config[key] = value + } - return config - }, - {} - ) + return config + }, {}) const result = { ...defaultConfig, ...config }