diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 74f647d1c7f4e8880c00412cf06d4b8ab164cbf5..c1aaed6a33aec418560a2a0cc9198530ba54a4a9 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -769,7 +769,7 @@ export default async function getBaseWebpackConfig( ? Object.keys(process.env).reduce( (prev: { [key: string]: string }, key: string) => { if (key.startsWith('NEXT_PUBLIC_')) { - prev[key] = process.env[key]! + prev[`process.env.${key}`] = JSON.stringify(process.env[key]!) } return prev }, diff --git a/test/integration/env-config/app/next.config.js b/test/integration/env-config/app/next.config.js new file mode 100644 index 0000000000000000000000000000000000000000..4c8d83b29f564549bb50de37cc05830a987ef1a5 --- /dev/null +++ b/test/integration/env-config/app/next.config.js @@ -0,0 +1,15 @@ +module.exports = { + experimental: { + pageEnv: true, + + async redirects() { + return [ + { + source: '/hello', + permanent: false, + destination: `/${process.env.NEXT_PUBLIC_TEST_DEST}`, + }, + ] + }, + }, +} diff --git a/test/integration/env-config/app/pages/next-config-loaded-env.js b/test/integration/env-config/app/pages/next-config-loaded-env.js deleted file mode 100644 index 51e8a5a85f4b597105c7f78c2e48ee8ab40fdcd6..0000000000000000000000000000000000000000 --- a/test/integration/env-config/app/pages/next-config-loaded-env.js +++ /dev/null @@ -1,16 +0,0 @@ -export default () => ( -

- {JSON.stringify({ - LOCAL_ENV_FILE_KEY: process.env.NC_LOCAL_ENV_FILE_KEY, - ENV_FILE_KEY: process.env.NC_ENV_FILE_KEY, - PRODUCTION_ENV_FILE_KEY: process.env.NC_PRODUCTION_ENV_FILE_KEY, - LOCAL_PRODUCTION_ENV_FILE_KEY: - process.env.NC_LOCAL_PRODUCTION_ENV_FILE_KEY, - DEVELOPMENT_ENV_FILE_KEY: process.env.NC_DEVELOPMENT_ENV_FILE_KEY, - TEST_ENV_FILE_KEY: process.env.NC_TEST_ENV_FILE_KEY, - LOCAL_TEST_ENV_FILE_KEY: process.env.NC_LOCAL_TEST_ENV_FILE_KEY, - LOCAL_DEVELOPMENT_ENV_FILE_KEY: - process.env.NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY, - })} -

-) diff --git a/test/integration/env-config/test/index.test.js b/test/integration/env-config/test/index.test.js index 08dc7e5f8db0d88608e65ac565d796e8d97cd0ce..3231cf7ee59e42847f2c0945472575b15704b984 100644 --- a/test/integration/env-config/test/index.test.js +++ b/test/integration/env-config/test/index.test.js @@ -18,24 +18,8 @@ jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2 let app let appPort +let buildId const appDir = join(__dirname, '../app') -const nextConfig = join(appDir, 'next.config.js') - -const nextConfigContent = ` - experimental: { - pageEnv: true, - - async redirects() { - return [ - { - source: '/hello', - permanent: false, - destination: \`/\${process.env.NEXT_PUBLIC_TEST_DEST}\`, - } - ] - } - } -` const getEnvFromHtml = async path => { const html = await renderViaHTTP(appPort, path) @@ -46,17 +30,35 @@ const getEnvFromHtml = async path => { ) } -const runTests = (isDev, isServerless, isTestEnv) => { - // TODO: support runtime overrides in serverless output - if (!isServerless) { - describe('Process environment', () => { - it('should override .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.PROCESS_ENV_KEY).toEqual('processenvironment') - }) - }) +const runTests = (mode = 'dev') => { + const isDevOnly = mode === 'dev' + const isTestEnv = mode === 'test' + const isDev = isDevOnly || isTestEnv + + const checkEnvData = data => { + expect(data.ENV_FILE_KEY).toBe('env') + expect(data.LOCAL_ENV_FILE_KEY).toBe(!isTestEnv ? 'localenv' : undefined) + expect(data.DEVELOPMENT_ENV_FILE_KEY).toBe( + isDevOnly ? 'development' : undefined + ) + expect(data.LOCAL_DEVELOPMENT_ENV_FILE_KEY).toBe( + isDevOnly ? 'localdevelopment' : undefined + ) + expect(data.TEST_ENV_FILE_KEY).toBe(isTestEnv ? 'test' : undefined) + expect(data.LOCAL_TEST_ENV_FILE_KEY).toBe( + isTestEnv ? 'localtest' : undefined + ) + expect(data.PRODUCTION_ENV_FILE_KEY).toBe(isDev ? undefined : 'production') + expect(data.LOCAL_PRODUCTION_ENV_FILE_KEY).toBe( + isDev ? undefined : 'localproduction' + ) } + it('should have process environment override .env', async () => { + const data = await getEnvFromHtml('/') + expect(data.PROCESS_ENV_KEY).toEqual('processenvironment') + }) + it('should provide global env to next.config.js', async () => { const res = await fetchViaHTTP(appPort, '/hello', undefined, { redirect: 'manual', @@ -68,355 +70,74 @@ const runTests = (isDev, isServerless, isTestEnv) => { }) it('should inline global values during build', async () => { - const html = await renderViaHTTP(appPort, '/global') - const $ = cheerio.load(html) - expect($('p').text()).toContain('another') + // make sure to build page + await renderViaHTTP(appPort, '/global') + + // read client bundle contents since a server side render can + // have the value available during render but it not be injected + const bundleContent = await fs.readFile( + join(appDir, '.next/static', buildId, 'pages/global.js'), + 'utf8' + ) + expect(bundleContent).toContain('another') }) - describe('Loads .env', () => { - it('should provide env for SSG', async () => { - const data = await getEnvFromHtml('/some-ssg') - expect(data.ENV_FILE_KEY).toBe('env') - }) - - it('should provide env correctly for SSR', async () => { - const data = await getEnvFromHtml('/some-ssp') - expect(data.ENV_FILE_KEY).toBe('env') - }) - - it('should provide env correctly for API routes', async () => { - const data = await renderViaHTTP(appPort, '/api/all') - expect(JSON.parse(data).ENV_FILE_KEY).toEqual('env') - }) - - // TODO: uncomment once env is provided to next.config.js - // it('should provide env correctly through next.config.js', async () => { - // const data = await getEnvFromHtml('/next-config-loaded-env') - // expect(data.ENV_FILE_KEY).toEqual('env') - // }) + it('should provide env for SSG', async () => { + const data = await getEnvFromHtml('/some-ssg') + checkEnvData(data) }) - if (!isTestEnv) { - describe('Loads .env.local', () => { - it('should provide env for SSG', async () => { - const data = await getEnvFromHtml('/some-ssg') - expect(data.LOCAL_ENV_FILE_KEY).toBe('localenv') - }) - - it('should provide env correctly for SSR', async () => { - const data = await getEnvFromHtml('/some-ssp') - expect(data.LOCAL_ENV_FILE_KEY).toBe('localenv') - }) - - it('should provide env correctly for API routes', async () => { - const data = await renderViaHTTP(appPort, '/api/all') - expect(JSON.parse(data).LOCAL_ENV_FILE_KEY).toEqual('localenv') - }) - - // TODO: uncomment once env is provided to next.config.js - // it('should provide env correctly through next.config.js', async () => { - // const data = await getEnvFromHtml('/next-config-loaded-env') - // expect(data.LOCAL_ENV_FILE_KEY).toEqual('localenv') - // }) - - it('should load env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_KEY).toEqual('env') - }) - - it('should override env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_LOCAL_OVERRIDE_TEST).toEqual('localenv') - }) - }) - - describe('Loads .env.development', () => { - it('should provide env for SSG', async () => { - const data = await getEnvFromHtml('/some-ssg') - expect(data.DEVELOPMENT_ENV_FILE_KEY).toBe( - isDev ? 'development' : undefined - ) - }) - - it('should provide env correctly for SSR', async () => { - const data = await getEnvFromHtml('/some-ssp') - expect(data.DEVELOPMENT_ENV_FILE_KEY).toBe( - isDev ? 'development' : undefined - ) - }) - - it('should provide env correctly for API routes', async () => { - const data = await renderViaHTTP(appPort, '/api/all') - expect(JSON.parse(data).DEVELOPMENT_ENV_FILE_KEY).toEqual( - isDev ? 'development' : undefined - ) - }) - - // TODO: uncomment once env is provided to next.config.js - // it('should provide env correctly through next.config.js', async () => { - // const data = await getEnvFromHtml('/next-config-loaded-env') - // expect(data.DEVELOPMENT_ENV_FILE_KEY).toEqual( - // isDev ? 'development' : undefined - // ) - // }) - - it('should load env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_KEY).toEqual('env') - }) - - it('should override env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_DEVELOPMENT_OVERRIDE_TEST).toEqual( - isDev ? 'development' : 'env' - ) - }) - }) - - describe('Loads .env.development.local', () => { - it('should provide env for SSG', async () => { - const data = await getEnvFromHtml('/some-ssg') - expect(data.LOCAL_DEVELOPMENT_ENV_FILE_KEY).toBe( - isDev ? 'localdevelopment' : undefined - ) - }) - - it('should provide env correctly for SSR', async () => { - const data = await getEnvFromHtml('/some-ssp') - expect(data.LOCAL_DEVELOPMENT_ENV_FILE_KEY).toBe( - isDev ? 'localdevelopment' : undefined - ) - }) - - it('should provide env correctly for API routes', async () => { - const data = await renderViaHTTP(appPort, '/api/all') - expect(JSON.parse(data).LOCAL_DEVELOPMENT_ENV_FILE_KEY).toEqual( - isDev ? 'localdevelopment' : undefined - ) - }) - - // TODO: uncomment once env is provided to next.config.js - // it('should provide env correctly through next.config.js', async () => { - // const data = await getEnvFromHtml('/next-config-loaded-env') - // expect(data.LOCAL_DEVELOPMENT_ENV_FILE_KEY).toEqual( - // isDev ? 'localdevelopment' : undefined - // ) - // }) - - it('should load env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_KEY).toEqual('env') - }) - - it('should override env from .env and .env.development', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST).toEqual( - isDev ? 'localdevelopment' : 'env' - ) - }) - }) - - describe('Loads .env.production', () => { - it('should provide env for SSG', async () => { - const data = await getEnvFromHtml('/some-ssg') - expect(data.PRODUCTION_ENV_FILE_KEY).toBe( - isDev ? undefined : 'production' - ) - }) - - it('should provide env correctly for SSR', async () => { - const data = await getEnvFromHtml('/some-ssp') - expect(data.PRODUCTION_ENV_FILE_KEY).toBe( - isDev ? undefined : 'production' - ) - }) - - it('should provide env correctly for API routes', async () => { - const data = await renderViaHTTP(appPort, '/api/all') - expect(JSON.parse(data).PRODUCTION_ENV_FILE_KEY).toEqual( - isDev ? undefined : 'production' - ) - }) - - // TODO: uncomment once env is provided to next.config.js - // it('should provide env correctly through next.config.js', async () => { - // const data = await getEnvFromHtml('/next-config-loaded-env') - // expect(data.PRODUCTION_ENV_FILE_KEY).toEqual( - // isDev ? undefined : 'production' - // ) - // }) - - it('should load env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_KEY).toEqual('env') - }) - - it('should override env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST).toEqual( - isDev ? 'env' : 'production' - ) - }) - }) - - describe('Loads .env.production.local', () => { - it('should provide env for SSG', async () => { - const data = await getEnvFromHtml('/some-ssg') - expect(data.LOCAL_PRODUCTION_ENV_FILE_KEY).toBe( - isDev ? undefined : 'localproduction' - ) - }) - - it('should provide env correctly for SSR', async () => { - const data = await getEnvFromHtml('/some-ssp') - expect(data.LOCAL_PRODUCTION_ENV_FILE_KEY).toBe( - isDev ? undefined : 'localproduction' - ) - }) - - it('should provide env correctly for API routes', async () => { - const data = await renderViaHTTP(appPort, '/api/all') - expect(JSON.parse(data).LOCAL_PRODUCTION_ENV_FILE_KEY).toEqual( - isDev ? undefined : 'localproduction' - ) - }) - - // TODO: uncomment once env is provided to next.config.js - // it('should provide env correctly through next.config.js', async () => { - // const data = await getEnvFromHtml('/next-config-loaded-env') - // expect(data.LOCAL_PRODUCTION_ENV_FILE_KEY).toEqual( - // isDev ? undefined : 'localproduction' - // ) - // }) - - it('should load env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_KEY).toEqual('env') - }) - - it('should override env from .env and .env.production', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST).toEqual( - isDev ? 'env' : 'localproduction' - ) - }) - }) - } - - if (isTestEnv) { - describe('Loads .env.test', () => { - it('should provide env for SSG', async () => { - const data = await getEnvFromHtml('/some-ssg') - expect(data.TEST_ENV_FILE_KEY).toBe(isDev ? 'test' : undefined) - }) - - it('should provide env correctly for SSR', async () => { - const data = await getEnvFromHtml('/some-ssp') - expect(data.TEST_ENV_FILE_KEY).toBe(isDev ? 'test' : undefined) - }) - - it('should provide env correctly for API routes', async () => { - const data = await renderViaHTTP(appPort, '/api/all') - expect(JSON.parse(data).TEST_ENV_FILE_KEY).toEqual( - isDev ? 'test' : undefined - ) - }) - - // TODO: uncomment once env is provided to next.config.js - // it('should provide env correctly through next.config.js', async () => { - // const data = await getEnvFromHtml('/next-config-loaded-env') - // expect(data.TEST_ENV_FILE_KEY).toEqual(isDev ? 'test' : undefined) - // }) - - it('should load env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_KEY).toEqual('env') - }) - - it('should override env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_TEST_OVERRIDE_TEST).toEqual(isDev ? 'test' : 'env') - }) - }) - - describe('Loads .env.test.local', () => { - it('should provide env for SSG', async () => { - const data = await getEnvFromHtml('/some-ssg') - expect(data.LOCAL_TEST_ENV_FILE_KEY).toBe( - isDev ? 'localtest' : undefined - ) - }) - - it('should provide env correctly for SSR', async () => { - const data = await getEnvFromHtml('/some-ssp') - expect(data.LOCAL_TEST_ENV_FILE_KEY).toBe( - isDev ? 'localtest' : undefined - ) - }) - - it('should provide env correctly for API routes', async () => { - const data = await renderViaHTTP(appPort, '/api/all') - expect(JSON.parse(data).LOCAL_TEST_ENV_FILE_KEY).toEqual( - isDev ? 'localtest' : undefined - ) - }) - - // TODO: uncomment once env is provided to next.config.js - // it('should provide env correctly through next.config.js', async () => { - // const data = await getEnvFromHtml('/next-config-loaded-env') - // expect(data.LOCAL_TEST_ENV_FILE_KEY).toEqual( - // isDev ? 'localtest' : undefined - // ) - // }) - - it('should load env from .env', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_KEY).toEqual('env') - }) + it('should provide env correctly for SSR', async () => { + const data = await getEnvFromHtml('/some-ssp') + checkEnvData(data) + }) - it('should override env from .env and .env.test', async () => { - const data = await getEnvFromHtml('/') - expect(data.ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST).toEqual( - isDev ? 'localtest' : 'env' - ) - }) + it('should provide env correctly for API routes', async () => { + const data = JSON.parse(await renderViaHTTP(appPort, '/api/all')) + checkEnvData(data) + }) - it('should not load .env.local', async () => { - const data = await getEnvFromHtml('/') - expect(data.LOCAL_ENV_FILE_KEY).toEqual(undefined) - }) - }) - } + it('should load env from .env', async () => { + const data = await getEnvFromHtml('/') + expect(data.ENV_FILE_KEY).toEqual('env') + expect(data.ENV_FILE_DEVELOPMENT_OVERRIDE_TEST).toEqual( + isDevOnly ? 'development' : 'env' + ) + expect(data.ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST).toEqual( + isDevOnly ? 'localdevelopment' : 'env' + ) + expect(data.ENV_FILE_TEST_OVERRIDE_TEST).toEqual(isTestEnv ? 'test' : 'env') + expect(data.ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST).toBe( + isTestEnv ? 'localtest' : 'env' + ) + expect(data.LOCAL_ENV_FILE_KEY).toBe(isTestEnv ? undefined : 'localenv') + expect(data.ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST).toEqual( + isDev ? 'env' : 'production' + ) + expect(data.ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST).toEqual( + isDev ? 'env' : 'localproduction' + ) + }) } describe('Env Config', () => { describe('dev mode', () => { beforeAll(async () => { - await fs.writeFile( - nextConfig, - `module.exports = { env: { NC_ENV_FILE_KEY: process.env.ENV_FILE_KEY, NC_LOCAL_ENV_FILE_KEY: process.env.LOCAL_ENV_FILE_KEY, NC_PRODUCTION_ENV_FILE_KEY: process.env.PRODUCTION_ENV_FILE_KEY, NC_LOCAL_PRODUCTION_ENV_FILE_KEY: process.env.LOCAL_PRODUCTION_ENV_FILE_KEY, NC_DEVELOPMENT_ENV_FILE_KEY: process.env.DEVELOPMENT_ENV_FILE_KEY, NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY: process.env.LOCAL_DEVELOPMENT_ENV_FILE_KEY }, ${nextConfigContent} }` - ) appPort = await findPort() app = await launchApp(appDir, appPort, { env: { PROCESS_ENV_KEY: 'processenvironment', }, }) + buildId = 'development' }) - afterAll(async () => { - await fs.remove(nextConfig) - await killApp(app) - }) + afterAll(() => killApp(app)) - runTests(true, false, false) + runTests('dev') }) describe('test environment', () => { beforeAll(async () => { - await fs.writeFile( - nextConfig, - `module.exports = { env: { NC_ENV_FILE_KEY: process.env.ENV_FILE_KEY, NC_LOCAL_ENV_FILE_KEY: process.env.LOCAL_ENV_FILE_KEY, NC_PRODUCTION_ENV_FILE_KEY: process.env.PRODUCTION_ENV_FILE_KEY, NC_LOCAL_PRODUCTION_ENV_FILE_KEY: process.env.LOCAL_PRODUCTION_ENV_FILE_KEY, NC_DEVELOPMENT_ENV_FILE_KEY: process.env.DEVELOPMENT_ENV_FILE_KEY, NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY: process.env.LOCAL_DEVELOPMENT_ENV_FILE_KEY, NC_TEST_ENV_FILE_KEY: process.env.TEST_ENV_FILE_KEY, NC_LOCAL_TEST_ENV_FILE_KEY: process.env.LOCAL_TEST_ENV_FILE_KEY }, ${nextConfigContent} }` - ) appPort = await findPort() app = await launchApp(appDir, appPort, { env: { @@ -424,58 +145,46 @@ describe('Env Config', () => { NODE_ENV: 'test', }, }) + buildId = 'development' }) - afterAll(async () => { - await fs.remove(nextConfig) - await killApp(app) - }) + afterAll(() => killApp(app)) - runTests(true, false, true) + runTests('test') }) describe('server mode', () => { beforeAll(async () => { - await fs.writeFile( - nextConfig, - `module.exports = { env: { NC_ENV_FILE_KEY: process.env.ENV_FILE_KEY, NC_LOCAL_ENV_FILE_KEY: process.env.LOCAL_ENV_FILE_KEY, NC_PRODUCTION_ENV_FILE_KEY: process.env.PRODUCTION_ENV_FILE_KEY, NC_LOCAL_PRODUCTION_ENV_FILE_KEY: process.env.LOCAL_PRODUCTION_ENV_FILE_KEY, NC_DEVELOPMENT_ENV_FILE_KEY: process.env.DEVELOPMENT_ENV_FILE_KEY, NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY: process.env.LOCAL_DEVELOPMENT_ENV_FILE_KEY }, ${nextConfigContent} }` - ) const { code } = await nextBuild(appDir, [], { env: { PROCESS_ENV_KEY: 'processenvironment', }, }) if (code !== 0) throw new Error(`Build failed with exit code ${code}`) + appPort = await findPort() - app = await nextStart(appDir, appPort, {}) - }) - afterAll(async () => { - await fs.remove(nextConfig) - await killApp(app) + app = await nextStart(appDir, appPort) }) + afterAll(() => killApp(app)) - runTests(false, false, false) + runTests('server') }) describe('serverless mode', () => { beforeAll(async () => { - await fs.writeFile( - nextConfig, - `module.exports = { target: 'experimental-serverless-trace', env: { NC_ENV_FILE_KEY: process.env.ENV_FILE_KEY, NC_LOCAL_ENV_FILE_KEY: process.env.LOCAL_ENV_FILE_KEY, NC_PRODUCTION_ENV_FILE_KEY: process.env.PRODUCTION_ENV_FILE_KEY, NC_LOCAL_PRODUCTION_ENV_FILE_KEY: process.env.LOCAL_PRODUCTION_ENV_FILE_KEY, NC_DEVELOPMENT_ENV_FILE_KEY: process.env.DEVELOPMENT_ENV_FILE_KEY, NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY: process.env.LOCAL_DEVELOPMENT_ENV_FILE_KEY }, ${nextConfigContent} }` - ) - const { code } = await nextBuild(appDir, [], {}) - if (code !== 0) throw new Error(`Build failed with exit code ${code}`) - appPort = await findPort() - app = await nextStart(appDir, appPort, { + const { code } = await nextBuild(appDir, [], { env: { PROCESS_ENV_KEY: 'processenvironment', }, }) + + if (code !== 0) throw new Error(`Build failed with exit code ${code}`) + appPort = await findPort() + + app = await nextStart(appDir, appPort) + buildId = await fs.readFile(join(appDir, '.next/BUILD_ID'), 'utf8') }) - afterAll(async () => { - await fs.remove(nextConfig) - await killApp(app) - }) + afterAll(() => killApp(app)) - runTests(false, true, false) + runTests('serverless') }) })