config.ts 8.6 KB
Newer Older
G
Guy Bedford 已提交
1
import chalk from 'next/dist/compiled/chalk'
G
find-up  
Guy Bedford 已提交
2
import findUp from 'next/dist/compiled/find-up'
3
import os from 'os'
4
import { basename, extname } from 'path'
5
import * as Log from '../../build/output/log'
T
Tim Neutkens 已提交
6
import { CONFIG_FILE } from '../lib/constants'
7
import { execOnce } from '../lib/utils'
8

9
const targets = ['server', 'serverless', 'experimental-serverless-trace']
10
const reactModes = ['legacy', 'blocking', 'concurrent']
T
Tim Neutkens 已提交
11

12
const defaultConfig: { [key: string]: any } = {
13
  env: [],
14
  webpack: null,
15
  webpackDevMiddleware: null,
16
  distDir: '.next',
17
  assetPrefix: '',
18
  configOrigin: 'default',
19
  useFileSystemPublicRoutes: true,
20
  generateBuildId: () => null,
21
  generateEtags: true,
22
  pageExtensions: ['tsx', 'ts', 'jsx', 'js'],
J
Joe Haddad 已提交
23
  target: 'server',
T
Tim Neutkens 已提交
24
  poweredByHeader: true,
25
  compress: true,
26 27 28 29
  devIndicators: {
    buildActivity: true,
    autoPrerender: true,
  },
30 31
  onDemandEntries: {
    maxInactiveAge: 60 * 1000,
T
Tim Neutkens 已提交
32
    pagesBufferLength: 2,
33
  },
34 35 36
  amp: {
    canonicalBase: '',
  },
37
  basePath: '',
38
  sassOptions: {},
39
  trailingSlash: false,
40
  experimental: {
41 42 43
    cpus: Math.max(
      1,
      (Number(process.env.CIRCLE_NODE_TOTAL) ||
44
        (os.cpus() || { length: 1 }).length) - 1
45
    ),
46
    modern: false,
47
    plugins: false,
J
Joe Haddad 已提交
48
    profiling: false,
J
JJ Kasper 已提交
49
    sprFlushToDisk: true,
50
    reactMode: 'legacy',
51
    workerThreads: false,
52
    pageEnv: false,
53
    productionBrowserSourceMaps: false,
P
Prateek Bhatnagar 已提交
54
    optimizeFonts: false,
55
    optimizeImages: false,
56
    scrollRestoration: false,
57 58 59
  },
  future: {
    excludeDefaultMomentLocales: false,
T
Tim Neutkens 已提交
60
  },
61 62
  serverRuntimeConfig: {},
  publicRuntimeConfig: {},
G
Gerald Monaco 已提交
63
  reactStrictMode: false,
T
Tim Neutkens 已提交
64 65
}

66
const experimentalWarning = execOnce(() => {
T
Tim Neutkens 已提交
67 68
  Log.warn(chalk.bold('You have enabled experimental feature(s).'))
  Log.warn(
J
Joe Haddad 已提交
69 70 71 72
    `Experimental features are not covered by semver, and may cause unexpected or broken application behavior. ` +
      `Use them at your own risk.`
  )
  console.warn()
73 74
})

75
function assignDefaults(userConfig: { [key: string]: any }) {
76 77 78 79 80 81 82 83 84 85 86
  if (typeof userConfig.exportTrailingSlash !== 'undefined') {
    console.warn(
      chalk.yellow.bold('Warning: ') +
        'The "exportTrailingSlash" option has been renamed to "trailingSlash". Please update your next.config.js.'
    )
    if (typeof userConfig.trailingSlash === 'undefined') {
      userConfig.trailingSlash = userConfig.exportTrailingSlash
    }
    delete userConfig.exportTrailingSlash
  }

87
  const config = Object.keys(userConfig).reduce<{ [key: string]: any }>(
88
    (currentConfig, key) => {
89
      const value = userConfig[key]
90

91
      if (value === undefined || value === null) {
92
        return currentConfig
93 94
      }

95 96
      if (key === 'experimental' && value && value !== defaultConfig[key]) {
        experimentalWarning()
97
      }
98

99 100 101 102 103 104 105
      if (key === 'distDir') {
        if (typeof value !== 'string') {
          throw new Error(
            `Specified distDir is not a string, found type "${typeof value}"`
          )
        }
        const userDistDir = value.trim()
106

107 108 109 110
        // don't allow public as the distDir as this is a reserved folder for
        // public files
        if (userDistDir === 'public') {
          throw new Error(
111
            `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`
112 113 114 115 116 117 118 119 120
          )
        }
        // 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`
          )
        }
121 122
      }

123 124 125 126 127 128
      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.`
          )
        }
129

130
        if (!value.length) {
131
          throw new Error(
132
            `Specified pageExtensions is an empty array. Please update it with the relevant extensions or remove it.`
133 134 135
          )
        }

136 137 138 139 140 141 142
        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.`
            )
          }
        })
143 144
      }

145
      if (!!value && value.constructor === Object) {
146
        currentConfig[key] = {
147 148 149 150 151 152 153 154 155 156
          ...defaultConfig[key],
          ...Object.keys(value).reduce<any>((c, k) => {
            const v = value[k]
            if (v !== undefined && v !== null) {
              c[k] = v
            }
            return c
          }, {}),
        }
      } else {
157
        currentConfig[key] = value
158 159
      }

160
      return currentConfig
161 162 163 164 165
    },
    {}
  )

  const result = { ...defaultConfig, ...config }
166 167 168

  if (typeof result.assetPrefix !== 'string') {
    throw new Error(
169
      `Specified assetPrefix is not a string, found type "${typeof result.assetPrefix}" https://err.sh/vercel/next.js/invalid-assetprefix`
170 171
    )
  }
T
Tim Neutkens 已提交
172
  if (result.experimental) {
173
    if (typeof result.basePath !== 'string') {
T
Tim Neutkens 已提交
174
      throw new Error(
175
        `Specified basePath is not a string, found type "${typeof result.basePath}"`
T
Tim Neutkens 已提交
176 177 178
      )
    }

179 180
    if (result.basePath !== '') {
      if (result.basePath === '/') {
T
Tim Neutkens 已提交
181 182 183 184 185
        throw new Error(
          `Specified basePath /. basePath has to be either an empty string or a path prefix"`
        )
      }

186
      if (!result.basePath.startsWith('/')) {
T
Tim Neutkens 已提交
187
        throw new Error(
188
          `Specified basePath has to start with a /, found "${result.basePath}"`
T
Tim Neutkens 已提交
189 190 191
        )
      }

192 193
      if (result.basePath !== '/') {
        if (result.basePath.endsWith('/')) {
T
Tim Neutkens 已提交
194
          throw new Error(
195
            `Specified basePath should not end with /, found "${result.basePath}"`
T
Tim Neutkens 已提交
196 197 198 199
          )
        }

        if (result.assetPrefix === '') {
200
          result.assetPrefix = result.basePath
T
Tim Neutkens 已提交
201 202 203
        }
      }
    }
J
Joe Haddad 已提交
204 205
  }
  return result
206 207
}

208
export function normalizeConfig(phase: string, config: any) {
T
Tim Neutkens 已提交
209
  if (typeof config === 'function') {
210
    config = config(phase, { defaultConfig })
T
Tim Neutkens 已提交
211

212 213
    if (typeof config.then === 'function') {
      throw new Error(
214
        '> Promise returned in next config. https://err.sh/vercel/next.js/promise-in-next-config'
215 216 217
      )
    }
  }
T
Tim Neutkens 已提交
218
  return config
219
}
N
Naoyuki Kanezawa 已提交
220

221 222 223
export default function loadConfig(
  phase: string,
  dir: string,
J
Joe Haddad 已提交
224
  customConfig?: object | null
225
) {
226
  if (customConfig) {
227
    return assignDefaults({ configOrigin: 'server', ...customConfig })
228
  }
T
Tim Neutkens 已提交
229
  const path = findUp.sync(CONFIG_FILE, {
T
Tim Neutkens 已提交
230
    cwd: dir,
231
  })
N
Naoyuki Kanezawa 已提交
232

233
  // If config file was found
234
  if (path?.length) {
235
    const userConfigModule = require(path)
236 237 238 239
    const userConfig = normalizeConfig(
      phase,
      userConfigModule.default || userConfigModule
    )
240 241

    if (Object.keys(userConfig).length === 0) {
242 243
      Log.warn(
        'Detected next.config.js, no exported configuration found. https://err.sh/vercel/next.js/empty-configuration'
244 245 246
      )
    }

T
Tim Neutkens 已提交
247
    if (userConfig.target && !targets.includes(userConfig.target)) {
248 249 250 251 252
      throw new Error(
        `Specified target is invalid. Provided: "${
          userConfig.target
        }" should be one of ${targets.join(', ')}`
      )
253
    }
254

255
    if (userConfig.amp?.canonicalBase) {
256
      const { canonicalBase } = userConfig.amp || ({} as any)
257
      userConfig.amp = userConfig.amp || {}
258 259 260 261
      userConfig.amp.canonicalBase =
        (canonicalBase.endsWith('/')
          ? canonicalBase.slice(0, -1)
          : canonicalBase) || ''
262 263
    }

264
    if (
265
      userConfig.experimental?.reactMode &&
266 267 268 269 270 271 272 273 274
      !reactModes.includes(userConfig.experimental.reactMode)
    ) {
      throw new Error(
        `Specified React Mode is invalid. Provided: ${
          userConfig.experimental.reactMode
        } should be one of ${reactModes.join(', ')}`
      )
    }

275
    return assignDefaults({ configOrigin: CONFIG_FILE, ...userConfig })
276 277 278 279 280 281 282 283 284 285 286
  } else {
    const configBaseName = basename(CONFIG_FILE, extname(CONFIG_FILE))
    const nonJsPath = findUp.sync(
      [
        `${configBaseName}.jsx`,
        `${configBaseName}.ts`,
        `${configBaseName}.tsx`,
        `${configBaseName}.json`,
      ],
      { cwd: dir }
    )
287
    if (nonJsPath?.length) {
288 289 290 291 292 293
      throw new Error(
        `Configuring Next.js via '${basename(
          nonJsPath
        )}' is not supported. Please replace the file with 'next.config.js'.`
      )
    }
294
  }
295

296
  return defaultConfig
N
Naoyuki Kanezawa 已提交
297
}
298 299 300 301 302 303

export function isTargetLikeServerless(target: string) {
  const isServerless = target === 'serverless'
  const isServerlessTrace = target === 'experimental-serverless-trace'
  return isServerless || isServerlessTrace
}