未验证 提交 1aade36f 编写于 作者: T Tim Neutkens 提交者: GitHub

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

* Add next/config

* Set config on server start / client render

* Add documentation for next/config

* Add next/config support for next export

* Fix test

* Use the correct name

* Set default to empty object on the client side

* Add config tests

* Rename config to runtimeConfig
上级 0117e2b3
......@@ -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
......@@ -26,7 +27,8 @@ const {
query,
buildId,
chunks,
assetPrefix
assetPrefix,
runtimeConfig
},
location
} = window
......@@ -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()
......
module.exports = require('./dist/lib/runtime-config')
let runtimeConfig
export default () => {
return runtimeConfig
}
export function setConfig (configValue) {
runtimeConfig = configValue
}
......@@ -20,7 +20,8 @@
"router.js",
"asset.js",
"error.js",
"constants.js"
"constants.js",
"config.js"
],
"bin": {
"next": "./dist/bin/next"
......
......@@ -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.
......
......@@ -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}`)
......@@ -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'
......
......@@ -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 = {
......@@ -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')
......@@ -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 = {
......@@ -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()
}
......@@ -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)
......@@ -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)
......
......@@ -40,6 +40,7 @@ async function doRender (req, res, pathname, query, {
buildStats,
hotReloader,
assetPrefix,
runtimeConfig,
availableChunks,
dist,
dir = process.cwd(),
......@@ -109,6 +110,7 @@ async function doRender (req, res, pathname, query, {
buildId,
buildStats,
assetPrefix,
runtimeConfig,
nextExport,
err: (err) ? serializeError(dev, err) : null
},
......
......@@ -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'
}
}
})
// 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>
/* 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()
})
})
}
......@@ -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))
})
......@@ -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')
})
})
}
......@@ -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
......@@ -180,7 +180,7 @@ describe('Production Usage', () => {
}
await app.render(req, res, req.url)
app.config.poweredByHeader = originalConfigValue
app.nextConfig.poweredByHeader = originalConfigValue
})
})
......
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册