Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for runtimeConfigs in serverless mode #10365

Merged
merged 3 commits into from Feb 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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: {},
})
})
})