Skip to content

Commit

Permalink
Add support for runtimeConfigs in serverless mode (#10365)
Browse files Browse the repository at this point in the history
* Add support for runtimeConfigs in serverless mode

* Update test
  • Loading branch information
ijjk committed Feb 1, 2020
1 parent e63d822 commit afc278f
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 46 deletions.
23 changes: 0 additions & 23 deletions errors/serverless-publicRuntimeConfig.md

This file was deleted.

10 changes: 10 additions & 0 deletions packages/next/build/entries.ts
Expand Up @@ -67,6 +67,10 @@ export function createEntrypoints(
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'],
Expand All @@ -77,6 +81,12 @@ export function createEntrypoints(
generateEtags: config.generateEtags,
canonicalBase: config.canonicalBase,
basePath: config.experimental.basePath,
runtimeConfig: hasRuntimeConfig
? JSON.stringify({
publicRuntimeConfig: config.publicRuntimeConfig,
serverRuntimeConfig: config.serverRuntimeConfig,
})
: '',
}

Object.keys(pages).forEach(page => {
Expand Down
30 changes: 29 additions & 1 deletion packages/next/build/webpack/loaders/next-serverless-loader.ts
Expand Up @@ -22,6 +22,7 @@ export type ServerlessLoaderQuery = {
generateEtags: string
canonicalBase: string
basePath: string
runtimeConfig: string
}

const nextServerlessLoader: loader.Loader = function() {
Expand All @@ -37,6 +38,7 @@ const nextServerlessLoader: loader.Loader = function() {
absoluteErrorPath,
generateEtags,
basePath,
runtimeConfig,
}: ServerlessLoaderQuery =
typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query

Expand All @@ -50,6 +52,19 @@ const nextServerlessLoader: loader.Loader = function() {
const escapedBuildId = escapeRegexp(buildId)
const pageIsDynamicRoute = isDynamicRoute(page)

const runtimeConfigImports = runtimeConfig
? `
import { setConfig } from 'next/dist/next-server/lib/runtime-config'
`
: ''

const runtimeConfigSetter = runtimeConfig
? `
const runtimeConfig = ${runtimeConfig}
setConfig(runtimeConfig)
`
: 'const runtimeConfig = {}'

const dynamicRouteImports = pageIsDynamicRoute
? `
import { getRouteMatcher } from 'next/dist/next-server/lib/router/utils/route-matcher';
Expand Down Expand Up @@ -123,9 +138,14 @@ const nextServerlessLoader: loader.Loader = function() {
import initServer from 'next-plugin-loader?middleware=on-init-server!'
import onError from 'next-plugin-loader?middleware=on-error-server!'
${rewriteImports}
${runtimeConfigImports}
${dynamicRouteMatcher}
${handleRewrites}
${
/* this needs to be called before importing the API method */
runtimeConfigSetter
}
export default async (req, res) => {
try {
Expand Down Expand Up @@ -177,9 +197,16 @@ const nextServerlessLoader: loader.Loader = function() {
import Document from '${absoluteDocumentPath}';
import Error from '${absoluteErrorPath}';
import App from '${absoluteAppPath}';
import * as ComponentInfo from '${absolutePagePath}';
${dynamicRouteImports}
${rewriteImports}
${runtimeConfigImports}
${
/* this needs to be called before importing the component */
runtimeConfigSetter
}
const ComponentInfo = require('${absolutePagePath}')
const Component = ComponentInfo.default
export default Component
Expand Down Expand Up @@ -214,6 +241,7 @@ const nextServerlessLoader: loader.Loader = function() {
canonicalBase: "${canonicalBase}",
buildId: "${buildId}",
assetPrefix: "${assetPrefix}",
runtimeConfig: runtimeConfig.publicRuntimeConfig || {},
..._renderOpts
}
let _nextData = false
Expand Down
14 changes: 0 additions & 14 deletions packages/next/next-server/server/config.ts
Expand Up @@ -254,20 +254,6 @@ export default function loadConfig(
: canonicalBase) || ''
}

if (
userConfig.target &&
userConfig.target !== 'server' &&
((userConfig.publicRuntimeConfig &&
Object.keys(userConfig.publicRuntimeConfig).length !== 0) ||
(userConfig.serverRuntimeConfig &&
Object.keys(userConfig.serverRuntimeConfig).length !== 0))
) {
// TODO: change error message tone to "Only compatible with [fat] server mode"
throw new Error(
'Cannot use publicRuntimeConfig or serverRuntimeConfig with target=serverless https://err.sh/zeit/next.js/serverless-publicRuntimeConfig'
)
}

if (
userConfig.experimental?.reactMode &&
!reactModes.includes(userConfig.experimental.reactMode)
Expand Down
Expand Up @@ -11,7 +11,7 @@ describe('Handle non-page in pages when target: serverless', () => {
const { stderr } = await nextBuild(appDir, [], { stderr: true })

expect(stderr).toMatch(
/webpack build failed: found page without a React Component as default export in/
/found page without a React Component as default export in/
)
expect(stderr).toMatch(/pages\/invalid/)
})
Expand Down
5 changes: 5 additions & 0 deletions test/integration/serverless-runtime-configs/pages/config.js
@@ -0,0 +1,5 @@
import getConfig from 'next/config'

const config = getConfig()

export default () => <p id="config">{JSON.stringify(config)}</p>
79 changes: 72 additions & 7 deletions test/integration/serverless-runtime-configs/test/index.test.js
Expand Up @@ -2,7 +2,15 @@
/* global jasmine */
import fs from 'fs-extra'
import { join } from 'path'
import { nextBuild } from 'next-test-utils'
import {
nextBuild,
findPort,
nextStart,
killApp,
renderViaHTTP,
} from 'next-test-utils'
import cheerio from 'cheerio'
import webdriver from 'next-webdriver'

const appDir = join(__dirname, '../')
const nextConfigPath = join(appDir, 'next.config.js')
Expand All @@ -14,7 +22,7 @@ describe('Serverless runtime configs', () => {
beforeAll(() => cleanUp())
afterAll(() => cleanUp())

it('should error on usage of publicRuntimeConfig', async () => {
it('should not error on usage of publicRuntimeConfig', async () => {
await fs.writeFile(
nextConfigPath,
`module.exports = {
Expand All @@ -25,13 +33,16 @@ describe('Serverless runtime configs', () => {
}`
)

const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(
const { stderr, code } = await nextBuild(appDir, undefined, {
stderr: true,
})
expect(code).toBe(0)
expect(stderr).not.toMatch(
/Cannot use publicRuntimeConfig or serverRuntimeConfig/
)
})

it('should error on usage of serverRuntimeConfig', async () => {
it('should not error on usage of serverRuntimeConfig', async () => {
await fs.writeFile(
nextConfigPath,
`module.exports = {
Expand All @@ -42,9 +53,63 @@ describe('Serverless runtime configs', () => {
}`
)

const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
expect(stderr).toMatch(
const { stderr, code } = await nextBuild(appDir, undefined, {
stderr: true,
})
expect(code).toBe(0)
expect(stderr).not.toMatch(
/Cannot use publicRuntimeConfig or serverRuntimeConfig/
)
})

it('should support runtime configs in serverless mode', async () => {
await fs.writeFile(
nextConfigPath,
`module.exports = {
target: 'serverless',
serverRuntimeConfig: {
hello: 'world'
},
publicRuntimeConfig: {
another: 'thing'
}
}`
)

await nextBuild(appDir, [], { stderr: true, stdout: true })
const appPort = await findPort()
const app = await nextStart(appDir, appPort, {
onStdout: console.log,
onStderr: console.log,
})

const browser = await webdriver(appPort, '/config')

const clientHTML = await browser.eval(`document.documentElement.innerHTML`)
const ssrHTML = await renderViaHTTP(appPort, '/config')

await killApp(app)
await fs.remove(nextConfigPath)

const ssr$ = cheerio.load(ssrHTML)
const client$ = cheerio.load(clientHTML)

const ssrConfig = ssr$('#config').text()
const clientConfig = client$('#config').text()

expect(JSON.parse(ssrConfig)).toEqual({
publicRuntimeConfig: {
another: 'thing',
},
serverRuntimeConfig: {
hello: 'world',
},
})
expect(JSON.parse(clientConfig)).toEqual({
publicRuntimeConfig: {
another: 'thing',
},
serverRuntimeConfig: {},
})
})
})

0 comments on commit afc278f

Please sign in to comment.