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

Exposing configuration to the server / client side (minor) #3882

Merged
merged 10 commits into from
Feb 26, 2018
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import App from '../lib/app'
import { loadGetInitialProps, getURL } from '../lib/utils'
import PageLoader from '../lib/page-loader'
import * as asset from '../lib/asset'
import * as envConfig from '../lib/runtime-config'

// Polyfill Promise globally
// This is needed because Webpack2's dynamic loading(common chunks) code
Expand All @@ -26,7 +27,8 @@ const {
query,
buildId,
chunks,
assetPrefix
assetPrefix,
runtimeConfig
},
location
} = window
Expand All @@ -36,6 +38,8 @@ const {
__webpack_public_path__ = `${assetPrefix}/_next/webpack/` //eslint-disable-line
// Initialize next/asset with the assetPrefix
asset.setAssetPrefix(assetPrefix)
// Initialize next/config with the environment configuration
envConfig.setConfig(runtimeConfig || {})

const asPath = getURL()

Expand Down
1 change: 1 addition & 0 deletions config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./dist/lib/runtime-config')
9 changes: 9 additions & 0 deletions lib/runtime-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let runtimeConfig

export default () => {
return runtimeConfig
}

export function setConfig (configValue) {
runtimeConfig = configValue
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"router.js",
"asset.js",
"error.js",
"constants.js"
"constants.js",
"config.js"
],
"bin": {
"next": "./dist/bin/next"
Expand Down
28 changes: 28 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,34 @@ Here's an example `.babelrc` file:
}
```

#### Exposing configuration to the server / client side

The `config` key allows for exposing runtime configuration in your app. All keys are server only by default. To expose a configuration to both the server and client side you can use the `public` key.

```js
// next.config.js
module.exports = {
runtimeConfig: {
mySecret: 'secret',
public: {
staticFolder: '/static'
}
}
}
```

```js
// pages/index.js
import getConfig from 'next/config'
const config = getConfig()
console.log(config.mySecret) // Will be 'secret' on the server, `undefined` on the client
console.log(config.public.staticFolder) // Will be '/static' on both server and client

export default () => <div>
<img src={`${config.public.staticFolder}/logo.png`} />
</div>
```

### CDN support with Asset Prefix

To set up a CDN, you can set up the `assetPrefix` setting and configure your CDN's origin to resolve to the domain that Next.js is hosted on.
Expand Down
29 changes: 22 additions & 7 deletions server/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ import { renderToHTML } from './render'
import { getAvailableChunks } from './utils'
import { printAndExit } from '../lib/utils'
import { setAssetPrefix } from '../lib/asset'
import * as envConfig from '../lib/runtime-config'

export default async function (dir, options, configuration) {
dir = resolve(dir)
const config = configuration || getConfig(PHASE_EXPORT, dir)
const nextDir = join(dir, config.distDir)
const nextConfig = configuration || getConfig(PHASE_EXPORT, dir)
const nextDir = join(dir, nextConfig.distDir)

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

Expand Down Expand Up @@ -73,28 +74,42 @@ export default async function (dir, options, configuration) {
await copyPages(nextDir, outDir, buildId)

// Get the exportPathMap from the `next.config.js`
if (typeof config.exportPathMap !== 'function') {
if (typeof nextConfig.exportPathMap !== 'function') {
printAndExit(
'> Could not find "exportPathMap" function inside "next.config.js"\n' +
'> "next export" uses that function to build html pages.'
)
}

const exportPathMap = await config.exportPathMap()
const exportPathMap = await nextConfig.exportPathMap()
const exportPaths = Object.keys(exportPathMap)

// Start the rendering process
const renderOpts = {
dir,
dist: config.distDir,
dist: nextConfig.distDir,
buildStats,
buildId,
nextExport: true,
assetPrefix: config.assetPrefix.replace(/\/$/, ''),
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
dev: false,
staticMarkup: false,
hotReloader: null,
availableChunks: getAvailableChunks(dir, config.distDir)
availableChunks: getAvailableChunks(dir, nextConfig.distDir)
}

// Allow configuration from next.config.js to be passed to the server / client
if (nextConfig.runtimeConfig) {
// Initialize next/config with the environment configuration
envConfig.setConfig(nextConfig.runtimeConfig)

// Only the `public` key is exposed to the client side
// It'll be rendered as part of __NEXT_DATA__ on the client side
if (nextConfig.runtimeConfig.public) {
renderOpts.runtimeConfig = {
public: nextConfig.runtimeConfig.public
}
}
}

// set the assetPrefix to use for 'next/asset'
Expand Down
28 changes: 22 additions & 6 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {PHASE_PRODUCTION_SERVER, PHASE_DEVELOPMENT_SERVER} from '../lib/constant
// We need to go up one more level since we are in the `dist` directory
import pkg from '../../package'
import * as asset from '../lib/asset'
import * as envConfig from '../lib/runtime-config'
import { isResSent } from '../lib/utils'

const blockedPages = {
Expand All @@ -35,10 +36,10 @@ export default class Server {
this.router = new Router()
this.http = null
const phase = dev ? PHASE_DEVELOPMENT_SERVER : PHASE_PRODUCTION_SERVER
this.config = getConfig(phase, this.dir, conf)
this.dist = this.config.distDir
this.nextConfig = getConfig(phase, this.dir, conf)
this.dist = this.nextConfig.distDir

this.hotReloader = dev ? this.getHotReloader(this.dir, { quiet, config: this.config }) : null
this.hotReloader = dev ? this.getHotReloader(this.dir, { quiet, config: this.nextConfig }) : null

if (dev) {
updateNotifier(pkg, 'next')
Expand All @@ -48,6 +49,7 @@ export default class Server {
console.error(`> Could not find a valid build in the '${this.dist}' directory! Try building your app with 'next build' before starting the server.`)
process.exit(1)
}

this.buildStats = !dev ? require(join(this.dir, this.dist, 'build-stats.json')) : null
this.buildId = !dev ? this.readBuildId() : '-'
this.renderOpts = {
Expand All @@ -61,7 +63,21 @@ export default class Server {
availableChunks: dev ? {} : getAvailableChunks(this.dir, this.dist)
}

this.setAssetPrefix(this.config.assetPrefix)
// Allow configuration from next.config.js to be passed to the server / client
if (this.nextConfig.runtimeConfig) {
// Initialize next/config with the environment configuration
envConfig.setConfig(this.nextConfig.runtimeConfig)

// Only the `public` key is exposed to the client side
// It'll be rendered as part of __NEXT_DATA__ on the client side
if (this.nextConfig.runtimeConfig.public) {
this.renderOpts.runtimeConfig = {
public: this.nextConfig.runtimeConfig.public
}
}
}

this.setAssetPrefix(this.nextConfig.assetPrefix)
this.defineRoutes()
}

Expand Down Expand Up @@ -262,7 +278,7 @@ export default class Server {
}
}

if (this.config.useFileSystemPublicRoutes) {
if (this.nextConfig.useFileSystemPublicRoutes) {
routes['/:path*'] = async (req, res, params, parsedUrl) => {
const { pathname, query } = parsedUrl
await this.render(req, res, pathname, query)
Expand Down Expand Up @@ -320,7 +336,7 @@ export default class Server {
return
}

if (this.config.poweredByHeader) {
if (this.nextConfig.poweredByHeader) {
res.setHeader('X-Powered-By', `Next.js ${pkg.version}`)
}
return sendHTML(req, res, html, req.method, this.renderOpts)
Expand Down
2 changes: 2 additions & 0 deletions server/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async function doRender (req, res, pathname, query, {
buildStats,
hotReloader,
assetPrefix,
runtimeConfig,
availableChunks,
dist,
dir = process.cwd(),
Expand Down Expand Up @@ -109,6 +110,7 @@ async function doRender (req, res, pathname, query, {
buildId,
buildStats,
assetPrefix,
runtimeConfig,
nextExport,
err: (err) ? serializeError(dev, err) : null
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,11 @@ module.exports = withCSS({
// Make sure entries are not getting disposed.
maxInactiveAge: 1000 * 60 * 60
},
cssModules: true
cssModules: true,
runtimeConfig: {
mySecret: 'secret',
public: {
staticFolder: '/static'
}
}
})
8 changes: 8 additions & 0 deletions test/integration/config/pages/next-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// pages/index.js
import getConfig from 'next/config'
const config = getConfig()

export default () => <div>
<p id='server-only'>{config.mySecret}</p>
<p id='server-and-client'>{config.public.staticFolder}</p>
</div>
21 changes: 21 additions & 0 deletions test/integration/config/test/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* global describe, it, expect */

import webdriver from 'next-webdriver'
import { waitFor } from 'next-test-utils'

export default (context, render) => {
describe('Configuration', () => {
it('should have config available on the client', async () => {
const browser = await webdriver(context.appPort, '/next-config')
// Wait for client side to load
await waitFor(5000)

const serverText = await browser.elementByCss('#server-only').text()
const serverClientText = await browser.elementByCss('#server-and-client').text()

expect(serverText).toBe('')
expect(serverClientText).toBe('/static')
browser.close()
})
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@ import {

// test suits
import rendering from './rendering'
import client from './client'

const context = {}
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 5

describe('Next Plugins', () => {
describe('Configuration', () => {
beforeAll(async () => {
context.appPort = await findPort()
context.server = await launchApp(join(__dirname, '../'), context.appPort, true)

// pre-build all pages at the start
await Promise.all([
renderViaHTTP(context.appPort, '/next-config'),
renderViaHTTP(context.appPort, '/webpack-css')
])
})
afterAll(() => killApp(context.server))

rendering(context, 'Rendering via HTTP', (p, q) => renderViaHTTP(context.appPort, p, q), (p, q) => fetchViaHTTP(context.appPort, p, q))
client(context, (p, q) => renderViaHTTP(context.appPort, p, q))
})
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,15 @@ export default function ({ app }, suiteName, render, fetch) {
const $ = await get$('/webpack-css')
expect($('._2pRSkKTPDMGLMnmsEkP__J').text() === 'Hello World')
})

test('renders server config on the server only', async () => {
const $ = await get$('/next-config')
expect($('#server-only').text() === 'mySecret')
})

test('renders public config on the server only', async () => {
const $ = await get$('/next-config')
expect($('#server-and-client').text() === '/static')
})
})
}
6 changes: 3 additions & 3 deletions test/integration/production/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ describe('Production Usage', () => {

it('should not set it when poweredByHeader==false', async () => {
const req = { url: '/stateless', headers: {} }
const originalConfigValue = app.config.poweredByHeader
app.config.poweredByHeader = false
const originalConfigValue = app.nextConfig.poweredByHeader
app.nextConfig.poweredByHeader = false
const res = {
getHeader () {
return false
Expand All @@ -180,7 +180,7 @@ describe('Production Usage', () => {
}

await app.render(req, res, req.url)
app.config.poweredByHeader = originalConfigValue
app.nextConfig.poweredByHeader = originalConfigValue
})
})

Expand Down
Loading