From 836ad753ce92a0db3c945abc3087227e31474021 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Sun, 2 Feb 2020 21:33:53 -0600 Subject: [PATCH] Make sure runtime config is set before any imports for serverless (#10386) Co-authored-by: Joe Haddad --- .../webpack/loaders/next-serverless-loader.ts | 53 ++++++++++--------- .../serverless-runtime-configs/pages/_app.js | 10 ++++ .../pages/_document.js | 26 +++++++++ .../pages/api/config.js | 7 +++ .../pages/config.js | 6 ++- .../serverless-runtime-configs/server.js | 31 +++++++++++ .../test/index.test.js | 47 ++++++++++++---- 7 files changed, 144 insertions(+), 36 deletions(-) create mode 100644 test/integration/serverless-runtime-configs/pages/_app.js create mode 100644 test/integration/serverless-runtime-configs/pages/_document.js create mode 100644 test/integration/serverless-runtime-configs/pages/api/config.js create mode 100644 test/integration/serverless-runtime-configs/server.js diff --git a/packages/next/build/webpack/loaders/next-serverless-loader.ts b/packages/next/build/webpack/loaders/next-serverless-loader.ts index d90d7ef0e69126c..8f6d29349837b56 100644 --- a/packages/next/build/webpack/loaders/next-serverless-loader.ts +++ b/packages/next/build/webpack/loaders/next-serverless-loader.ts @@ -54,7 +54,7 @@ const nextServerlessLoader: loader.Loader = function() { const runtimeConfigImports = runtimeConfig ? ` - import { setConfig } from 'next/dist/next-server/lib/runtime-config' + const { setConfig } = require('next/dist/next-server/lib/runtime-config') ` : '' @@ -67,8 +67,8 @@ const nextServerlessLoader: loader.Loader = function() { const dynamicRouteImports = pageIsDynamicRoute ? ` - import { getRouteMatcher } from 'next/dist/next-server/lib/router/utils/route-matcher'; - import { getRouteRegex } from 'next/dist/next-server/lib/router/utils/route-regex'; + const { getRouteMatcher } = require('next/dist/next-server/lib/router/utils/route-matcher'); + const { getRouteRegex } = require('next/dist/next-server/lib/router/utils/route-regex'); ` : '' @@ -79,8 +79,8 @@ const nextServerlessLoader: loader.Loader = function() { : '' const rewriteImports = ` - import { rewrites } from '${routesManifest}' - import pathMatch, { pathToRegexp } from 'next/dist/next-server/server/lib/path-match' + const { rewrites } = require('${routesManifest}') + const { pathToRegexp, default: pathMatch } = require('next/dist/next-server/server/lib/path-match') ` const handleRewrites = ` @@ -132,20 +132,22 @@ const nextServerlessLoader: loader.Loader = function() { if (page.match(API_ROUTE)) { return ` - ${dynamicRouteImports} - import { parse } from 'url' - import { apiResolver } from 'next/dist/next-server/server/api-utils' 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 */ + /* + this needs to be called first so its available for any other imports + */ runtimeConfigSetter } + ${dynamicRouteImports} + const { parse } = require('url') + const { apiResolver } = require('next/dist/next-server/server/api-utils') + ${rewriteImports} + + ${dynamicRouteMatcher} + ${handleRewrites} export default async (req, res) => { try { @@ -186,25 +188,24 @@ const nextServerlessLoader: loader.Loader = function() { ` } else { return ` - import {parse} from 'url' - import {parse as parseQs} from 'querystring' - import {renderToHTML} from 'next/dist/next-server/server/render'; - import {sendHTML} from 'next/dist/next-server/server/send-html'; import initServer from 'next-plugin-loader?middleware=on-init-server!' import onError from 'next-plugin-loader?middleware=on-error-server!' - import buildManifest from '${buildManifest}'; - import reactLoadableManifest from '${reactLoadableManifest}'; - import Document from '${absoluteDocumentPath}'; - import Error from '${absoluteErrorPath}'; - import App from '${absoluteAppPath}'; - ${dynamicRouteImports} - ${rewriteImports} ${runtimeConfigImports} - ${ - /* this needs to be called before importing the component */ + // this needs to be called first so its available for any other imports runtimeConfigSetter } + const {parse} = require('url') + const {parse: parseQs} = require('querystring') + const {renderToHTML} =require('next/dist/next-server/server/render'); + const {sendHTML} = require('next/dist/next-server/server/send-html'); + const buildManifest = require('${buildManifest}'); + const reactLoadableManifest = require('${reactLoadableManifest}'); + const Document = require('${absoluteDocumentPath}').default; + const Error = require('${absoluteErrorPath}').default; + const App = require('${absoluteAppPath}').default; + ${dynamicRouteImports} + ${rewriteImports} const ComponentInfo = require('${absolutePagePath}') diff --git a/test/integration/serverless-runtime-configs/pages/_app.js b/test/integration/serverless-runtime-configs/pages/_app.js new file mode 100644 index 000000000000000..9d7f9f03f9d3bbc --- /dev/null +++ b/test/integration/serverless-runtime-configs/pages/_app.js @@ -0,0 +1,10 @@ +import getConfig from 'next/config' + +const config = getConfig() + +export default ({ Component, pageProps }) => ( + <> +

{JSON.stringify(config)}

+ + +) diff --git a/test/integration/serverless-runtime-configs/pages/_document.js b/test/integration/serverless-runtime-configs/pages/_document.js new file mode 100644 index 000000000000000..6f85bbef1fdb73e --- /dev/null +++ b/test/integration/serverless-runtime-configs/pages/_document.js @@ -0,0 +1,26 @@ +import Document, { Html, Head, Main, NextScript } from 'next/document' +import getConfig from 'next/config' + +const config = getConfig() + +class MyDocument extends Document { + static async getInitialProps(ctx) { + const initialProps = await Document.getInitialProps(ctx) + return { ...initialProps } + } + + render() { + return ( + + + +
{JSON.stringify(config)}
+
+ + + + ) + } +} + +export default MyDocument diff --git a/test/integration/serverless-runtime-configs/pages/api/config.js b/test/integration/serverless-runtime-configs/pages/api/config.js new file mode 100644 index 000000000000000..8e64ae591ba48f7 --- /dev/null +++ b/test/integration/serverless-runtime-configs/pages/api/config.js @@ -0,0 +1,7 @@ +import getConfig from 'next/config' + +const config = getConfig() + +export default (req, res) => { + res.json(config) +} diff --git a/test/integration/serverless-runtime-configs/pages/config.js b/test/integration/serverless-runtime-configs/pages/config.js index 45ace3c4a933763..27341d294b8245e 100644 --- a/test/integration/serverless-runtime-configs/pages/config.js +++ b/test/integration/serverless-runtime-configs/pages/config.js @@ -2,4 +2,8 @@ import getConfig from 'next/config' const config = getConfig() -export default () =>

{JSON.stringify(config)}

+const page = () =>

{JSON.stringify(config)}

+ +page.getInitialProps = () => ({ a: 'b' }) + +export default page diff --git a/test/integration/serverless-runtime-configs/server.js b/test/integration/serverless-runtime-configs/server.js new file mode 100644 index 000000000000000..a80556e110bd001 --- /dev/null +++ b/test/integration/serverless-runtime-configs/server.js @@ -0,0 +1,31 @@ +const path = require('path') +const http = require('http') +const send = require('send') + +const server = http.createServer((req, res) => { + if (req.url === '/config') { + return require('./.next/serverless/pages/config.js').render(req, res) + } + + if (req.url === '/') { + return send( + req, + path.join(__dirname, '.next/serverless/pages/index.html') + ).pipe(res) + } + + if (req.url === '/api/config') { + return require('./.next/serverless/pages/api/config.js').default(req, res) + } + + if (req.url.startsWith('/_next')) { + send( + req, + path.join(__dirname, '.next', req.url.split('/_next').pop()) + ).pipe(res) + } +}) + +server.listen(process.env.PORT, () => { + console.log('ready on', process.env.PORT) +}) diff --git a/test/integration/serverless-runtime-configs/test/index.test.js b/test/integration/serverless-runtime-configs/test/index.test.js index 81d79a75a2e9222..b9776f2055ed230 100644 --- a/test/integration/serverless-runtime-configs/test/index.test.js +++ b/test/integration/serverless-runtime-configs/test/index.test.js @@ -5,9 +5,9 @@ import { join } from 'path' import { nextBuild, findPort, - nextStart, killApp, renderViaHTTP, + initNextServerScript, } from 'next-test-utils' import cheerio from 'cheerio' import webdriver from 'next-webdriver' @@ -18,6 +18,18 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2 const cleanUp = () => fs.remove(nextConfigPath) +const nextStart = async (appDir, appPort) => { + const scriptPath = join(appDir, 'server.js') + const env = Object.assign({ ...process.env }, { PORT: `${appPort}` }) + + return initNextServerScript( + scriptPath, + /ready on/i, + env, + /ReferenceError: options is not defined/ + ) +} + describe('Serverless runtime configs', () => { beforeAll(() => cleanUp()) afterAll(() => cleanUp()) @@ -78,15 +90,13 @@ describe('Serverless runtime configs', () => { await nextBuild(appDir, [], { stderr: true, stdout: true }) const appPort = await findPort() - const app = await nextStart(appDir, appPort, { - onStdout: console.log, - onStderr: console.log, - }) + const app = await nextStart(appDir, appPort) const browser = await webdriver(appPort, '/config') const clientHTML = await browser.eval(`document.documentElement.innerHTML`) const ssrHTML = await renderViaHTTP(appPort, '/config') + const apiJson = await renderViaHTTP(appPort, '/api/config') await killApp(app) await fs.remove(nextConfigPath) @@ -97,19 +107,38 @@ describe('Serverless runtime configs', () => { const ssrConfig = ssr$('#config').text() const clientConfig = client$('#config').text() - expect(JSON.parse(ssrConfig)).toEqual({ + const expectedSsrConfig = { publicRuntimeConfig: { another: 'thing', }, serverRuntimeConfig: { hello: 'world', }, - }) - expect(JSON.parse(clientConfig)).toEqual({ + } + + const expectedClientConfig = { publicRuntimeConfig: { another: 'thing', }, serverRuntimeConfig: {}, - }) + } + + expect(JSON.parse(ssrConfig)).toEqual(expectedSsrConfig) + expect(JSON.parse(clientConfig)).toEqual(expectedClientConfig) + + const appSsrConfig = ssr$('#app-config').text() + const appClientConfig = client$('#app-config').text() + + expect(JSON.parse(appSsrConfig)).toEqual(expectedSsrConfig) + expect(JSON.parse(appClientConfig)).toEqual(expectedClientConfig) + + const docSsrConfig = ssr$('#doc-config').text() + const docClientConfig = client$('#doc-config').text() + + // _document doesn't update on client so should be the same + expect(JSON.parse(docSsrConfig)).toEqual(expectedSsrConfig) + expect(JSON.parse(docClientConfig)).toEqual(expectedSsrConfig) + + expect(JSON.parse(apiJson)).toEqual(expectedSsrConfig) }) })