[Feature] Progress bar for static build (#15297)

Co-authored-by: Tim Neutkens <timneutkens@me.com>
Co-authored-by: JJ Kasper <jj@jjsweb.site>
This commit is contained in:
Jonathan G 2020-08-04 00:58:23 -07:00 committed by GitHub
parent 1ea8bdcdc7
commit 6c59cbb46a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 129 additions and 122 deletions

View file

@ -75,6 +75,8 @@ import {
import getBaseWebpackConfig from './webpack-config' import getBaseWebpackConfig from './webpack-config'
import { PagesManifest } from './webpack/plugins/pages-manifest-plugin' import { PagesManifest } from './webpack/plugins/pages-manifest-plugin'
import { writeBuildId } from './write-build-id' import { writeBuildId } from './write-build-id'
import * as Log from './output/log'
const staticCheckWorker = require.resolve('./utils') const staticCheckWorker = require.resolve('./utils')
export type SsgRoute = { export type SsgRoute = {
@ -126,17 +128,13 @@ export default async function build(
// Intentionally not piping to stderr in case people fail in CI when // Intentionally not piping to stderr in case people fail in CI when
// stderr is detected. // stderr is detected.
console.log( console.log(
chalk.bold.yellow(`Warning: `) + `${Log.prefixes.warn} No build cache found. Please configure build caching for faster rebuilds. Read more: https://err.sh/next.js/no-cache`
chalk.bold(
`No build cache found. Please configure build caching for faster rebuilds. Read more: https://err.sh/next.js/no-cache`
) )
)
console.log('')
} }
} }
const buildSpinner = createSpinner({ const buildSpinner = createSpinner({
prefixText: 'Creating an optimized production build', prefixText: `${Log.prefixes.info} Creating an optimized production build`,
}) })
const telemetry = new Telemetry({ distDir }) const telemetry = new Telemetry({ distDir })
@ -243,17 +241,11 @@ export default async function build(
}) })
if (nestedReservedPages.length) { if (nestedReservedPages.length) {
console.warn( Log.warn(
'\n' + `The following reserved Next.js pages were detected not directly under the pages directory:\n` +
chalk.bold.yellow(`Warning: `) +
chalk.bold(
`The following reserved Next.js pages were detected not directly under the pages directory:\n`
) +
nestedReservedPages.join('\n') + nestedReservedPages.join('\n') +
chalk.bold(
`\nSee more info here: https://err.sh/next.js/nested-reserved-page\n` `\nSee more info here: https://err.sh/next.js/nested-reserved-page\n`
) )
)
} }
const buildCustomRoute = ( const buildCustomRoute = (
@ -354,12 +346,9 @@ export default async function build(
(clientConfig.optimization.minimizer && (clientConfig.optimization.minimizer &&
clientConfig.optimization.minimizer.length === 0)) clientConfig.optimization.minimizer.length === 0))
) { ) {
console.warn( Log.warn(
chalk.bold.yellow(`Warning: `) +
chalk.bold(
`Production code optimization has been disabled in your project. Read more: https://err.sh/vercel/next.js/minification-disabled` `Production code optimization has been disabled in your project. Read more: https://err.sh/vercel/next.js/minification-disabled`
) )
)
} }
const webpackBuildStart = process.hrtime() const webpackBuildStart = process.hrtime()
@ -389,7 +378,6 @@ export default async function build(
if (buildSpinner) { if (buildSpinner) {
buildSpinner.stopAndPersist() buildSpinner.stopAndPersist()
} }
console.log()
result = formatWebpackMessages(result) result = formatWebpackMessages(result)
@ -435,15 +423,16 @@ export default async function build(
) )
if (result.warnings.length > 0) { if (result.warnings.length > 0) {
console.warn(chalk.yellow('Compiled with warnings.\n')) Log.warn('Compiled with warnings\n')
console.warn(result.warnings.join('\n\n')) console.warn(result.warnings.join('\n\n'))
console.warn() console.warn()
} else { } else {
console.log(chalk.green('Compiled successfully.\n')) Log.info('Compiled successfully')
} }
} }
const postBuildSpinner = createSpinner({
prefixText: 'Automatically optimizing pages', const postCompileSpinner = createSpinner({
prefixText: `${Log.prefixes.info} Collecting page data`,
}) })
const manifestPath = path.join( const manifestPath = path.join(
@ -685,15 +674,18 @@ export default async function build(
const finalPrerenderRoutes: { [route: string]: SsgRoute } = {} const finalPrerenderRoutes: { [route: string]: SsgRoute } = {}
const tbdPrerenderRoutes: string[] = [] const tbdPrerenderRoutes: string[] = []
if (postCompileSpinner) postCompileSpinner.stopAndPersist()
if (staticPages.size > 0 || ssgPages.size > 0 || useStatic404) { if (staticPages.size > 0 || ssgPages.size > 0 || useStatic404) {
const combinedPages = [...staticPages, ...ssgPages] const combinedPages = [...staticPages, ...ssgPages]
const exportApp = require('../export').default const exportApp = require('../export').default
const exportOptions = { const exportOptions = {
silent: true, silent: false,
buildExport: true, buildExport: true,
threads: config.experimental.cpus, threads: config.experimental.cpus,
pages: combinedPages, pages: combinedPages,
outdir: path.join(distDir, 'export'), outdir: path.join(distDir, 'export'),
statusMessage: 'Generating static pages',
} }
const exportConfig: any = { const exportConfig: any = {
...config, ...config,
@ -745,6 +737,10 @@ export default async function build(
await exportApp(dir, exportOptions, exportConfig) await exportApp(dir, exportOptions, exportConfig)
const postBuildSpinner = createSpinner({
prefixText: `${Log.prefixes.info} Finalizing page optimization`,
})
// remove server bundles that were exported // remove server bundles that were exported
for (const page of staticPages) { for (const page of staticPages) {
const serverBundle = getPagePath(page, distDir, isLikeServerless) const serverBundle = getPagePath(page, distDir, isLikeServerless)
@ -876,10 +872,10 @@ export default async function build(
JSON.stringify(pagesManifest, null, 2), JSON.stringify(pagesManifest, null, 2),
'utf8' 'utf8'
) )
}
if (postBuildSpinner) postBuildSpinner.stopAndPersist() if (postBuildSpinner) postBuildSpinner.stopAndPersist()
console.log() console.log()
}
const analysisEnd = process.hrtime(analysisBegin) const analysisEnd = process.hrtime(analysisBegin)
telemetry.record( telemetry.record(

View file

@ -1,6 +1,6 @@
import chalk from 'next/dist/compiled/chalk' import chalk from 'next/dist/compiled/chalk'
const prefixes = { export const prefixes = {
wait: chalk.cyan('wait') + ' -', wait: chalk.cyan('wait') + ' -',
error: chalk.red('error') + ' -', error: chalk.red('error') + ' -',
warn: chalk.yellow('warn') + ' -', warn: chalk.yellow('warn') + ' -',
@ -14,11 +14,11 @@ export function wait(...message: string[]) {
} }
export function error(...message: string[]) { export function error(...message: string[]) {
console.log(prefixes.error, ...message) console.error(prefixes.error, ...message)
} }
export function warn(...message: string[]) { export function warn(...message: string[]) {
console.log(prefixes.warn, ...message) console.warn(prefixes.warn, ...message)
} }
export function ready(...message: string[]) { export function ready(...message: string[]) {

View file

@ -7,7 +7,8 @@ const dotsSpinner = {
export default function createSpinner( export default function createSpinner(
text: string | { prefixText: string }, text: string | { prefixText: string },
options: ora.Options = {} options: ora.Options = {},
logFn: (...data: any[]) => void = console.log
) { ) {
let spinner: undefined | ora.Ora let spinner: undefined | ora.Ora
let prefixText = text && typeof text === 'object' && text.prefixText let prefixText = text && typeof text === 'object' && text.prefixText
@ -55,7 +56,7 @@ export default function createSpinner(
return spinner! return spinner!
} }
} else if (prefixText || text) { } else if (prefixText || text) {
console.log(prefixText ? prefixText + '...' : text) logFn(prefixText ? prefixText + '...' : text)
} }
return spinner return spinner

View file

@ -12,6 +12,7 @@ import { cpus } from 'os'
import { dirname, join, resolve, sep } from 'path' import { dirname, join, resolve, sep } from 'path'
import { promisify } from 'util' import { promisify } from 'util'
import { AmpPageStatus, formatAmpMessages } from '../build/output/index' import { AmpPageStatus, formatAmpMessages } from '../build/output/index'
import * as Log from '../build/output/log'
import createSpinner from '../build/spinner' import createSpinner from '../build/spinner'
import { API_ROUTE, SSG_FALLBACK_EXPORT_ERROR } from '../lib/constants' import { API_ROUTE, SSG_FALLBACK_EXPORT_ERROR } from '../lib/constants'
import { recursiveCopy } from '../lib/recursive-copy' import { recursiveCopy } from '../lib/recursive-copy'
@ -97,6 +98,7 @@ interface ExportOptions {
threads?: number threads?: number
pages?: string[] pages?: string[]
buildExport?: boolean buildExport?: boolean
statusMessage?: string
} }
export default async function exportApp( export default async function exportApp(
@ -104,13 +106,6 @@ export default async function exportApp(
options: ExportOptions, options: ExportOptions,
configuration?: any configuration?: any
): Promise<void> { ): Promise<void> {
function log(message: string): void {
if (options.silent) {
return
}
console.log(message)
}
dir = resolve(dir) dir = resolve(dir)
// attempt to load global env values so they are available in next.config.js // attempt to load global env values so they are available in next.config.js
@ -136,7 +131,9 @@ export default async function exportApp(
const subFolders = nextConfig.trailingSlash const subFolders = nextConfig.trailingSlash
const isLikeServerless = nextConfig.target !== 'server' const isLikeServerless = nextConfig.target !== 'server'
log(`> using build directory: ${distDir}`) if (!options.silent && !options.buildExport) {
Log.info(`using build directory: ${distDir}`)
}
if (!existsSync(distDir)) { if (!existsSync(distDir)) {
throw new Error( throw new Error(
@ -213,13 +210,20 @@ export default async function exportApp(
// Copy static directory // Copy static directory
if (!options.buildExport && existsSync(join(dir, 'static'))) { if (!options.buildExport && existsSync(join(dir, 'static'))) {
log(' copying "static" directory') if (!options.silent) {
Log.info('Copying "static" directory')
}
await recursiveCopy(join(dir, 'static'), join(outDir, 'static')) await recursiveCopy(join(dir, 'static'), join(outDir, 'static'))
} }
// Copy .next/static directory // Copy .next/static directory
if (existsSync(join(distDir, CLIENT_STATIC_FILES_PATH))) { if (
log(' copying "static build" directory') !options.buildExport &&
existsSync(join(distDir, CLIENT_STATIC_FILES_PATH))
) {
if (!options.silent) {
Log.info('Copying "static build" directory')
}
await recursiveCopy( await recursiveCopy(
join(distDir, CLIENT_STATIC_FILES_PATH), join(distDir, CLIENT_STATIC_FILES_PATH),
join(outDir, '_next', CLIENT_STATIC_FILES_PATH) join(outDir, '_next', CLIENT_STATIC_FILES_PATH)
@ -228,9 +232,11 @@ export default async function exportApp(
// Get the exportPathMap from the config file // Get the exportPathMap from the config file
if (typeof nextConfig.exportPathMap !== 'function') { if (typeof nextConfig.exportPathMap !== 'function') {
console.log( if (!options.silent) {
`> No "exportPathMap" found in "${CONFIG_FILE}". Generating map from "./pages"` Log.info(
`No "exportPathMap" found in "${CONFIG_FILE}". Generating map from "./pages"`
) )
}
nextConfig.exportPathMap = async (defaultMap: ExportPathMap) => { nextConfig.exportPathMap = async (defaultMap: ExportPathMap) => {
return defaultMap return defaultMap
} }
@ -264,7 +270,9 @@ export default async function exportApp(
nextExport: true, nextExport: true,
} }
log(` launching ${threads} workers`) if (!options.silent && !options.buildExport) {
Log.info(`Launching ${threads} workers`)
}
const exportPathMap = await nextConfig.exportPathMap(defaultPathMap, { const exportPathMap = await nextConfig.exportPathMap(defaultPathMap, {
dev: false, dev: false,
dir, dir,
@ -322,9 +330,8 @@ export default async function exportApp(
// Warn if the user defines a path for an API page // Warn if the user defines a path for an API page
if (hasApiRoutes) { if (hasApiRoutes) {
log( if (!options.silent) {
chalk.bold.red(`Warning`) + Log.warn(
': ' +
chalk.yellow( chalk.yellow(
`Statically exporting a Next.js application via \`next export\` disables API routes.` `Statically exporting a Next.js application via \`next export\` disables API routes.`
) + ) +
@ -338,11 +345,20 @@ export default async function exportApp(
chalk.yellow( chalk.yellow(
`Pages in your application without server-side data dependencies will be automatically statically exported by \`next build\`, including pages powered by \`getStaticProps\`.` `Pages in your application without server-side data dependencies will be automatically statically exported by \`next build\`, including pages powered by \`getStaticProps\`.`
) + ) +
`\nLearn more: https://err.sh/vercel/next.js/api-routes-static-export` `\n` +
chalk.yellow(
`Learn more: https://err.sh/vercel/next.js/api-routes-static-export`
)
) )
} }
}
const progress = !options.silent && createProgress(filteredPaths.length) const progress =
!options.silent &&
createProgress(
filteredPaths.length,
`${Log.prefixes.info} ${options.statusMessage}`
)
const pagesDataDir = options.buildExport const pagesDataDir = options.buildExport
? outDir ? outDir
: join(outDir, '_next/data', buildId) : join(outDir, '_next/data', buildId)
@ -353,7 +369,9 @@ export default async function exportApp(
const publicDir = join(dir, CLIENT_PUBLIC_FILES_PATH) const publicDir = join(dir, CLIENT_PUBLIC_FILES_PATH)
// Copy public directory // Copy public directory
if (!options.buildExport && existsSync(publicDir)) { if (!options.buildExport && existsSync(publicDir)) {
log(' copying "public" directory') if (!options.silent) {
Log.info('Copying "public" directory')
}
await recursiveCopy(publicDir, outDir, { await recursiveCopy(publicDir, outDir, {
filter(path) { filter(path) {
// Exclude paths used by pages // Exclude paths used by pages
@ -476,8 +494,6 @@ export default async function exportApp(
.join('\n\t')}` .join('\n\t')}`
) )
} }
// Add an empty line to the console for the better readability.
log('')
writeFileSync( writeFileSync(
join(distDir, EXPORT_DETAIL), join(distDir, EXPORT_DETAIL),

View file

@ -238,8 +238,7 @@ export default function loadConfig(
) )
if (Object.keys(userConfig).length === 0) { if (Object.keys(userConfig).length === 0) {
console.warn( Log.warn(
chalk.yellow.bold('Warning: ') +
'Detected next.config.js, no exported configuration found. https://err.sh/vercel/next.js/empty-configuration' 'Detected next.config.js, no exported configuration found. https://err.sh/vercel/next.js/empty-configuration'
) )
} }

View file

@ -430,12 +430,12 @@ function runTests(dev = false) {
}) })
} else { } else {
it('should show warning with next export', async () => { it('should show warning with next export', async () => {
const { stdout } = await nextExport( const { stderr } = await nextExport(
appDir, appDir,
{ outdir: join(appDir, 'out') }, { outdir: join(appDir, 'out') },
{ stdout: true } { stderr: true }
) )
expect(stdout).toContain( expect(stderr).toContain(
'https://err.sh/vercel/next.js/api-routes-static-export' 'https://err.sh/vercel/next.js/api-routes-static-export'
) )
}) })

View file

@ -19,9 +19,9 @@ describe('Empty configuration', () => {
stderr: true, stderr: true,
stdout: true, stdout: true,
}) })
expect(stdout).toMatch(/Compiled successfully./) expect(stdout).toMatch(/Compiled successfully/)
expect(stderr).toMatch( expect(stderr).toMatch(
/Warning: Detected next.config.js, no exported configuration found. https:\/\/err.sh\/vercel\/next.js\/empty-configuration/ /Detected next\.config\.js, no exported configuration found\. https:\/\/err\.sh\/vercel\/next\.js\/empty-configuration/
) )
}) })
@ -38,7 +38,7 @@ describe('Empty configuration', () => {
await killApp(app) await killApp(app)
expect(stderr).toMatch( expect(stderr).toMatch(
/Warning: Detected next.config.js, no exported configuration found. https:\/\/err.sh\/vercel\/next.js\/empty-configuration/ /Detected next\.config\.js, no exported configuration found\. https:\/\/err\.sh\/vercel\/next\.js\/empty-configuration/
) )
}) })
}) })

View file

@ -20,8 +20,8 @@ describe('Promise in next config', () => {
} }
`) `)
const { stdout } = await nextBuild(appDir, [], { stdout: true }) const { stderr } = await nextBuild(appDir, [], { stderr: true })
expect(stdout).not.toMatch(/experimental feature/) expect(stderr).not.toMatch(/experimental feature/)
}) })
it('should not show warning with config from object', async () => { it('should not show warning with config from object', async () => {
@ -30,8 +30,8 @@ describe('Promise in next config', () => {
target: 'server' target: 'server'
} }
`) `)
const { stdout } = await nextBuild(appDir, [], { stdout: true }) const { stderr } = await nextBuild(appDir, [], { stderr: true })
expect(stdout).not.toMatch(/experimental feature/) expect(stderr).not.toMatch(/experimental feature/)
}) })
it('should show warning with config from object with experimental', async () => { it('should show warning with config from object with experimental', async () => {
@ -43,8 +43,8 @@ describe('Promise in next config', () => {
} }
} }
`) `)
const { stdout } = await nextBuild(appDir, [], { stdout: true }) const { stderr } = await nextBuild(appDir, [], { stderr: true })
expect(stdout).toMatch(/experimental feature/) expect(stderr).toMatch(/experimental feature/)
}) })
it('should show warning with config from function with experimental', async () => { it('should show warning with config from function with experimental', async () => {
@ -56,7 +56,7 @@ describe('Promise in next config', () => {
} }
}) })
`) `)
const { stdout } = await nextBuild(appDir, [], { stdout: true }) const { stderr } = await nextBuild(appDir, [], { stderr: true })
expect(stdout).toMatch(/experimental feature/) expect(stderr).toMatch(/experimental feature/)
}) })
}) })

View file

@ -16,8 +16,8 @@ const appDir = path.join(__dirname, '..')
describe('Handles Duplicate Pages', () => { describe('Handles Duplicate Pages', () => {
describe('production', () => { describe('production', () => {
it('Throws an error during build', async () => { it('Throws an error during build', async () => {
const { stdout } = await nextBuild(appDir, [], { stdout: true }) const { stderr } = await nextBuild(appDir, [], { stderr: true })
expect(stdout).toContain('Duplicate page detected') expect(stderr).toContain('Duplicate page detected')
}) })
}) })

View file

@ -15,12 +15,12 @@ export default function (context) {
it('Should throw if a route is matched', async () => { it('Should throw if a route is matched', async () => {
const outdir = join(context.appDir, 'outApi') const outdir = join(context.appDir, 'outApi')
const { stdout } = await runNextCommand( const { stderr } = await runNextCommand(
['export', context.appDir, '--outdir', outdir], ['export', context.appDir, '--outdir', outdir],
{ stdout: true } { stderr: true }
) )
expect(stdout).toContain( expect(stderr).toContain(
'https://err.sh/vercel/next.js/api-routes-static-export' 'https://err.sh/vercel/next.js/api-routes-static-export'
) )
}) })

View file

@ -15,12 +15,12 @@ export default function (context) {
it('Should throw if a route is matched', async () => { it('Should throw if a route is matched', async () => {
const outdir = join(context.appDir, 'outApi') const outdir = join(context.appDir, 'outApi')
const { stdout } = await runNextCommand( const { stderr } = await runNextCommand(
['export', context.appDir, '--outdir', outdir], ['export', context.appDir, '--outdir', outdir],
{ stdout: true } { stderr: true }
) )
expect(stdout).toContain( expect(stderr).toContain(
'https://err.sh/vercel/next.js/api-routes-static-export' 'https://err.sh/vercel/next.js/api-routes-static-export'
) )
}) })

View file

@ -11,7 +11,7 @@ const appDir = join(__dirname, '..')
describe('jsconfig.json', () => { describe('jsconfig.json', () => {
it('should build normally', async () => { it('should build normally', async () => {
const res = await await nextBuild(appDir, [], { stdout: true }) const res = await await nextBuild(appDir, [], { stdout: true })
expect(res.stdout).toMatch(/Compiled successfully\./) expect(res.stdout).toMatch(/Compiled successfully/)
}) })
it('should fail on invalid jsconfig.json', async () => { it('should fail on invalid jsconfig.json', async () => {

View file

@ -19,25 +19,25 @@ describe('no anonymous default export warning', () => {
}) })
it('show correct warnings for page', async () => { it('show correct warnings for page', async () => {
let stdout = '' let stderr = ''
const appPort = await findPort() const appPort = await findPort()
const app = await launchApp(appDir, appPort, { const app = await launchApp(appDir, appPort, {
env: { __NEXT_TEST_WITH_DEVTOOL: true }, env: { __NEXT_TEST_WITH_DEVTOOL: true },
onStdout(msg) { onStderr(msg) {
stdout += msg || '' stderr += msg || ''
}, },
}) })
const browser = await webdriver(appPort, '/page') const browser = await webdriver(appPort, '/page')
const found = await check(() => stdout, /anonymous/i, false) const found = await check(() => stderr, /anonymous/i, false)
expect(found).toBeTruthy() expect(found).toBeTruthy()
await browser.close() await browser.close()
expect( expect(
getRegexCount( getRegexCount(
stdout, stderr,
/page.js\r?\n.*not preserve local component state\./g /page.js\r?\n.*not preserve local component state\./g
) )
).toBe(1) ).toBe(1)
@ -46,25 +46,25 @@ describe('no anonymous default export warning', () => {
}) })
it('show correct warnings for child', async () => { it('show correct warnings for child', async () => {
let stdout = '' let stderr = ''
const appPort = await findPort() const appPort = await findPort()
const app = await launchApp(appDir, appPort, { const app = await launchApp(appDir, appPort, {
env: { __NEXT_TEST_WITH_DEVTOOL: true }, env: { __NEXT_TEST_WITH_DEVTOOL: true },
onStdout(msg) { onStderr(msg) {
stdout += msg || '' stderr += msg || ''
}, },
}) })
const browser = await webdriver(appPort, '/child') const browser = await webdriver(appPort, '/child')
const found = await check(() => stdout, /anonymous/i, false) const found = await check(() => stderr, /anonymous/i, false)
expect(found).toBeTruthy() expect(found).toBeTruthy()
await browser.close() await browser.close()
expect( expect(
getRegexCount( getRegexCount(
stdout, stderr,
/Child.js\r?\n.*not preserve local component state\./g /Child.js\r?\n.*not preserve local component state\./g
) )
).toBe(1) ).toBe(1)
@ -73,31 +73,31 @@ describe('no anonymous default export warning', () => {
}) })
it('show correct warnings for both', async () => { it('show correct warnings for both', async () => {
let stdout = '' let stderr = ''
const appPort = await findPort() const appPort = await findPort()
const app = await launchApp(appDir, appPort, { const app = await launchApp(appDir, appPort, {
env: { __NEXT_TEST_WITH_DEVTOOL: true }, env: { __NEXT_TEST_WITH_DEVTOOL: true },
onStdout(msg) { onStderr(msg) {
stdout += msg || '' stderr += msg || ''
}, },
}) })
const browser = await webdriver(appPort, '/both') const browser = await webdriver(appPort, '/both')
const found = await check(() => stdout, /anonymous/i, false) const found = await check(() => stderr, /anonymous/i, false)
expect(found).toBeTruthy() expect(found).toBeTruthy()
await browser.close() await browser.close()
expect( expect(
getRegexCount( getRegexCount(
stdout, stderr,
/Child.js\r?\n.*not preserve local component state\./g /Child.js\r?\n.*not preserve local component state\./g
) )
).toBe(1) ).toBe(1)
expect( expect(
getRegexCount( getRegexCount(
stdout, stderr,
/both.js\r?\n.*not preserve local component state\./g /both.js\r?\n.*not preserve local component state\./g
) )
).toBe(1) ).toBe(1)

View file

@ -16,15 +16,11 @@ const appDir = join(__dirname, '../')
describe('no duplicate compile error output', () => { describe('no duplicate compile error output', () => {
it('should not show compile error on page refresh', async () => { it('should not show compile error on page refresh', async () => {
let stdout = ''
let stderr = '' let stderr = ''
const appPort = await findPort() const appPort = await findPort()
const app = await launchApp(appDir, appPort, { const app = await launchApp(appDir, appPort, {
env: { __NEXT_TEST_WITH_DEVTOOL: true }, env: { __NEXT_TEST_WITH_DEVTOOL: true },
onStdout(msg) {
stdout += msg || ''
},
onStderr(msg) { onStderr(msg) {
stderr += msg || '' stderr += msg || ''
}, },
@ -63,12 +59,11 @@ describe('no duplicate compile error output', () => {
const correctMessagesRegex = /error - [^\r\n]+\r?\n[^\r\n]+Unexpected token/g const correctMessagesRegex = /error - [^\r\n]+\r?\n[^\r\n]+Unexpected token/g
const totalMessagesRegex = /Unexpected token/g const totalMessagesRegex = /Unexpected token/g
const correctMessages = getRegexCount(stdout, correctMessagesRegex) const correctMessages = getRegexCount(stderr, correctMessagesRegex)
const totalMessages = getRegexCount(stdout, totalMessagesRegex) const totalMessages = getRegexCount(stderr, totalMessagesRegex)
expect(correctMessages).toBeGreaterThanOrEqual(1) expect(correctMessages).toBeGreaterThanOrEqual(1)
expect(correctMessages).toBe(totalMessages) expect(correctMessages).toBe(totalMessages)
expect(stderr).toBe('')
await killApp(app) await killApp(app)
}) })

View file

@ -36,7 +36,7 @@ describe('Non-Standard NODE_ENV', () => {
let output = '' let output = ''
app = await launchApp(appDir, await findPort(), { app = await launchApp(appDir, await findPort(), {
onStdout(msg) { onStderr(msg) {
output += msg || '' output += msg || ''
}, },
}) })
@ -52,7 +52,7 @@ describe('Non-Standard NODE_ENV', () => {
env: { env: {
NODE_ENV: 'development', NODE_ENV: 'development',
}, },
onStdout(msg) { onStderr(msg) {
output += msg || '' output += msg || ''
}, },
}) })
@ -69,7 +69,7 @@ describe('Non-Standard NODE_ENV', () => {
NODE_ENV: 'development', NODE_ENV: 'development',
}, },
{ {
onStdout(msg) { onStderr(msg) {
output += msg || '' output += msg || ''
}, },
} }
@ -86,7 +86,7 @@ describe('Non-Standard NODE_ENV', () => {
env: { env: {
NODE_ENV: 'abc', NODE_ENV: 'abc',
}, },
onStdout(msg) { onStderr(msg) {
output += msg || '' output += msg || ''
}, },
}) })
@ -103,7 +103,7 @@ describe('Non-Standard NODE_ENV', () => {
NODE_ENV: 'abc', NODE_ENV: 'abc',
}, },
{ {
onStdout(msg) { onStderr(msg) {
output += msg || '' output += msg || ''
}, },
} }