Skip to content

Commit 5017f91

Browse files
timneutkensarunoda
authored andcommitted
Allow next.config.js to export a function (vercel#3867)
* Allow next.config.js to export a function * Expose phases to the configuration function * Use same value as variable name * Add next/constants * Add documentation for config function / phases * Add constants.js to npm bundle
1 parent 50fcefd commit 5017f91

File tree

11 files changed

+101
-7
lines changed

11 files changed

+101
-7
lines changed

constants.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('./dist/lib/constants')

lib/constants.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export const PHASE_EXPORT = 'phase-export'
2+
export const PHASE_PRODUCTION_BUILD = 'phase-production-build'
3+
export const PHASE_PRODUCTION_SERVER = 'phase-production-server'
4+
export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server'

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"prefetch.js",
2020
"router.js",
2121
"asset.js",
22-
"error.js"
22+
"error.js",
23+
"constants.js"
2324
],
2425
"bin": {
2526
"next": "./dist/bin/next"

readme.md

+30
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,36 @@ module.exports = {
997997
}
998998
```
999999

1000+
Or use a function:
1001+
1002+
```js
1003+
module.exports = (phase, {defaultConfig}){
1004+
//
1005+
// https://github.com/zeit/
1006+
return {
1007+
/* config options here */
1008+
}
1009+
}
1010+
```
1011+
1012+
`phase` is the current context in which the configuration is loaded. You can see all phases here: [contants](./lib/constants.js)
1013+
Phases can be imported from `next/constants`:
1014+
1015+
```js
1016+
const {PHASE_DEVELOPMENT_SERVER} = require('next/constants')
1017+
module.exports = (phase, {defaultConfig}){
1018+
if(phase === PHASE_DEVELOPMENT_SERVER) {
1019+
return {
1020+
/* development only config options here */
1021+
}
1022+
}
1023+
1024+
return {
1025+
/* config options for all phases except development here */
1026+
}
1027+
}
1028+
```
1029+
10001030
#### Setting a custom build directory
10011031

10021032
You can specify a name to use for a custom build directory. For example, the following config will create a `build` folder instead of a `.next` folder. If no configuration is specified then next will create a `.next` folder.

server/build/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import fs from 'mz/fs'
33
import uuid from 'uuid'
44
import webpack from 'webpack'
55
import getConfig from '../config'
6+
import {PHASE_PRODUCTION_BUILD} from '../../lib/constants'
67
import getBaseWebpackConfig from './webpack'
78
import md5File from 'md5-file/promise'
89

910
export default async function build (dir, conf = null) {
10-
const config = getConfig(dir, conf)
11+
const config = getConfig(PHASE_PRODUCTION_BUILD, dir, conf)
1112
const buildId = uuid.v4()
1213

1314
try {

server/config.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ const defaultConfig = {
1313
pageExtensions: ['jsx', 'js'] // jsx before js because otherwise regex matching will match js first
1414
}
1515

16-
export default function getConfig (dir, customConfig) {
16+
export default function getConfig (phase, dir, customConfig) {
1717
if (!cache.has(dir)) {
18-
cache.set(dir, loadConfig(dir, customConfig))
18+
cache.set(dir, loadConfig(phase, dir, customConfig))
1919
}
2020
return cache.get(dir)
2121
}
2222

23-
function loadConfig (dir, customConfig) {
23+
export function loadConfig (phase, dir, customConfig) {
2424
if (customConfig && typeof customConfig === 'object') {
2525
customConfig.configOrigin = 'server'
2626
return withDefaults(customConfig)
@@ -34,6 +34,9 @@ function loadConfig (dir, customConfig) {
3434
if (path && path.length) {
3535
const userConfigModule = require(path)
3636
userConfig = userConfigModule.default || userConfigModule
37+
if (typeof userConfigModule === 'function') {
38+
userConfig = userConfigModule(phase, {defaultConfig})
39+
}
3740
userConfig.configOrigin = 'next.config.js'
3841
}
3942

server/export.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@ import walk from 'walk'
55
import { extname, resolve, join, dirname, sep } from 'path'
66
import { existsSync, readFileSync, writeFileSync } from 'fs'
77
import getConfig from './config'
8+
import {PHASE_EXPORT} from '../lib/constants'
89
import { renderToHTML } from './render'
910
import { getAvailableChunks } from './utils'
1011
import { printAndExit } from '../lib/utils'
1112
import { setAssetPrefix } from '../lib/asset'
1213

1314
export default async function (dir, options, configuration) {
1415
dir = resolve(dir)
15-
const config = configuration || getConfig(dir)
16+
const config = configuration || getConfig(PHASE_EXPORT, dir)
1617
const nextDir = join(dir, config.distDir)
1718

1819
log(` using build directory: ${nextDir}`)

server/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import Router from './router'
1717
import { getAvailableChunks, isInternalUrl } from './utils'
1818
import getConfig from './config'
19+
import {PHASE_PRODUCTION_SERVER, PHASE_DEVELOPMENT_SERVER} from '../lib/constants'
1920
// We need to go up one more level since we are in the `dist` directory
2021
import pkg from '../../package'
2122
import * as asset from '../lib/asset'
@@ -33,7 +34,8 @@ export default class Server {
3334
this.quiet = quiet
3435
this.router = new Router()
3536
this.http = null
36-
this.config = getConfig(this.dir, conf)
37+
const phase = dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER
38+
this.config = getConfig(phase, this.dir, conf)
3739
this.dist = this.config.distDir
3840

3941
this.hotReloader = dev ? this.getHotReloader(this.dir, { quiet, config: this.config }) : null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module.exports = (phase, {defaultConfig}) => {
2+
return {
3+
phase,
4+
defaultConfig,
5+
customConfig: true
6+
}
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
customConfig: true
3+
}

test/isolated/config.test.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* global describe, it, expect */
2+
3+
import {join} from 'path'
4+
import getConfig, {loadConfig} from '../../dist/server/config'
5+
import {PHASE_DEVELOPMENT_SERVER} from '../../dist/lib/constants'
6+
7+
const pathToConfig = join(__dirname, '_resolvedata', 'without-function')
8+
const pathToConfigFn = join(__dirname, '_resolvedata', 'with-function')
9+
10+
describe('config', () => {
11+
it('Should get the configuration', () => {
12+
const config = loadConfig(PHASE_DEVELOPMENT_SERVER, pathToConfig)
13+
expect(config.customConfig).toBe(true)
14+
})
15+
16+
it('Should pass the phase correctly', () => {
17+
const config = loadConfig(PHASE_DEVELOPMENT_SERVER, pathToConfigFn)
18+
expect(config.phase).toBe(PHASE_DEVELOPMENT_SERVER)
19+
})
20+
21+
it('Should pass the defaultConfig correctly', () => {
22+
const config = loadConfig(PHASE_DEVELOPMENT_SERVER, pathToConfigFn)
23+
expect(config.defaultConfig).toBeDefined()
24+
})
25+
26+
it('Should pass the customConfig correctly', () => {
27+
const config = loadConfig(PHASE_DEVELOPMENT_SERVER, null, {customConfig: true})
28+
expect(config.customConfig).toBe(true)
29+
})
30+
31+
it('Should not pass the customConfig when it is null', () => {
32+
const config = loadConfig(PHASE_DEVELOPMENT_SERVER, null, null)
33+
expect(config.webpack).toBe(null)
34+
})
35+
36+
it('Should cache on getConfig', () => {
37+
const config = getConfig(PHASE_DEVELOPMENT_SERVER, pathToConfig)
38+
const config2 = getConfig(PHASE_DEVELOPMENT_SERVER, pathToConfig, {extraConfig: true}) // won't add extraConfig because it's cached.
39+
expect(config === config2).toBe(true)
40+
})
41+
})

0 commit comments

Comments
 (0)