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

feat: change next build to emit output with output: export #47376

Merged
merged 15 commits into from
Mar 23, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/advanced-features/static-html-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Update your `next.config.js` file to include `output: "export"` like the followi
*/
const nextConfig = {
output: 'export',
distDir: 'out',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be the default with output: 'export' to avoid differences from next export?

Copy link
Member Author

@styfle styfle Mar 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the default. I just wanted to demonstrate how to change it in the docs and it felt appropriate here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(see line packages/next/src/build/index.ts:285 for the default)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ijjk Updated in b5b9565

}

module.exports = nextConfig
Expand Down
65 changes: 46 additions & 19 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,16 @@ export default async function build(
.traceAsyncFn(() => loadConfig(PHASE_PRODUCTION_BUILD, dir))
NextBuildContext.config = config

let configOutDir = 'out'
if (config.output === 'export' && config.distDir !== '.next') {
// In the past, a user had to run "next build" to generate
// ".next" (or whatever the distDir) followed by "next export"
// to generate "out" (or whatever the outDir). However, when
// "output: export" is configured, "next build" does both steps.
// So the user-configured dirDir is actually the outDir.
configOutDir = config.distDir
config.distDir = '.next'
}
const distDir = path.join(dir, config.distDir)
setGlobal('phase', PHASE_PRODUCTION_BUILD)
setGlobal('distDir', distDir)
Expand Down Expand Up @@ -2302,24 +2312,7 @@ export default async function build(
)
const exportApp: typeof import('../export').default =
require('../export').default
const exportOptions: ExportOptions = {
silent: false,
buildExport: true,
debugOutput,
threads: config.experimental.cpus,
pages: combinedPages,
outdir: path.join(distDir, 'export'),
statusMessage: 'Generating static pages',
exportPageWorker: sharedPool
? staticWorkers.exportPage.bind(staticWorkers)
: undefined,
endWorker: sharedPool
? async () => {
await staticWorkers.end()
}
: undefined,
appPaths,
}

const exportConfig: NextConfigComplete = {
...config,
initialPageRevalidationMap: {},
Expand Down Expand Up @@ -2439,7 +2432,28 @@ export default async function build(
},
}

await exportApp(dir, exportOptions, nextBuildSpan, exportConfig)
const exportOptions: ExportOptions = {
isInvokedFromCli: false,
nextConfig: exportConfig,
silent: false,
buildExport: true,
debugOutput,
threads: config.experimental.cpus,
pages: combinedPages,
outdir: path.join(distDir, 'export'),
statusMessage: 'Generating static pages',
exportPageWorker: sharedPool
? staticWorkers.exportPage.bind(staticWorkers)
: undefined,
endWorker: sharedPool
? async () => {
await staticWorkers.end()
}
: undefined,
appPaths,
}

await exportApp(dir, exportOptions, nextBuildSpan)

const postBuildSpinner = createSpinner({
prefixText: `${Log.prefixes.info} Finalizing page optimization`,
Expand Down Expand Up @@ -3059,6 +3073,19 @@ export default async function build(
})
}

if (config.output === 'export') {
const exportApp: typeof import('../export').default =
require('../export').default
const options: ExportOptions = {
isInvokedFromCli: false,
nextConfig: config,
silent: true,
threads: config.experimental.cpus,
outdir: path.join(dir, configOutDir),
}
await exportApp(dir, options, nextBuildSpan)
}

await nextBuildSpan
.traceChild('telemetry-flush')
.traceAsyncFn(() => telemetry.flush())
Expand Down
1 change: 1 addition & 0 deletions packages/next/src/cli/next-export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const nextExport: CliCommand = (argv) => {
silent: args['--silent'] || false,
threads: args['--threads'],
outdir: args['--outdir'] ? resolve(args['--outdir']) : join(dir, 'out'),
isInvokedFromCli: true,
}

exportApp(dir, options, nextExportCliSpan)
Expand Down
30 changes: 19 additions & 11 deletions packages/next/src/export/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ const createProgress = (total: number, label: string) => {

export interface ExportOptions {
outdir: string
isInvokedFromCli: boolean
silent?: boolean
threads?: number
debugOutput?: boolean
Expand All @@ -154,13 +155,13 @@ export interface ExportOptions {
exportPageWorker?: typeof import('./worker').default
endWorker?: () => Promise<void>
appPaths?: string[]
nextConfig?: NextConfigComplete
}

export default async function exportApp(
dir: string,
options: ExportOptions,
span: Span,
configuration?: NextConfigComplete
span: Span
): Promise<void> {
const nextExportSpan = span.traceChild('next-export')
const hasAppDir = !!options.appPaths
Expand All @@ -174,10 +175,18 @@ export default async function exportApp(
.traceFn(() => loadEnvConfig(dir, false, Log))

const nextConfig =
configuration ||
options.nextConfig ||
(await nextExportSpan
.traceChild('load-next-config')
.traceAsyncFn(() => loadConfig(PHASE_EXPORT, dir)))

if (options.isInvokedFromCli && nextConfig.output === 'export') {
Log.warn(
'"next export" is no longer needed when "output: export" is configured in next.config.js'
)
return
}

const threads = options.threads || nextConfig.experimental.cpus
const distDir = join(dir, nextConfig.distDir)

Expand Down Expand Up @@ -627,7 +636,7 @@ export default async function exportApp(
)
}

const timeout = configuration?.staticPageGenerationTimeout || 0
const timeout = nextConfig?.staticPageGenerationTimeout || 0
let infoPrinted = false
let exportPage: typeof import('./worker').default
let endWorker: () => Promise<void>
Expand Down Expand Up @@ -714,23 +723,22 @@ export default async function exportApp(
errorPaths.push(page !== path ? `${page}: ${path}` : path)
}

if (options.buildExport && configuration) {
if (options.buildExport) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might have been a bug before.

Previously, it only read nextConfig if it was passed in as an arg, but not the case when it read the file on line 181 above

if (typeof result.fromBuildExportRevalidate !== 'undefined') {
configuration.initialPageRevalidationMap[path] =
nextConfig.initialPageRevalidationMap[path] =
result.fromBuildExportRevalidate
}

if (typeof result.fromBuildExportMeta !== 'undefined') {
configuration.initialPageMetaMap[path] =
result.fromBuildExportMeta
nextConfig.initialPageMetaMap[path] = result.fromBuildExportMeta
}

if (result.ssgNotFound === true) {
configuration.ssgNotFoundPaths.push(path)
nextConfig.ssgNotFoundPaths.push(path)
}

const durations = (configuration.pageDurationMap[pathMap.page] =
configuration.pageDurationMap[pathMap.page] || {})
const durations = (nextConfig.pageDurationMap[pathMap.page] =
nextConfig.pageDurationMap[pathMap.page] || {})
durations[path] = result.duration
}

Expand Down
119 changes: 112 additions & 7 deletions test/integration/app-dir-export/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,19 @@ import {

const glob = promisify(globOrig)
const appDir = join(__dirname, '..')
const distDir = join(__dirname, '.next')
const distDir = join(appDir, '.next')
const exportDir = join(appDir, 'out')
const nextConfig = new File(join(appDir, 'next.config.js'))
const slugPage = new File(join(appDir, 'app/another/[slug]/page.js'))
const apiJson = new File(join(appDir, 'app/api/json/route.js'))

async function getFiles(cwd = exportDir) {
const opts = { cwd, nodir: true }
const files = ((await glob('**/*', opts)) as string[])
.filter((f) => !f.startsWith('_next/static/chunks/'))
.sort()
return files
}
async function runTests({
isDev,
trailingSlash,
Expand Down Expand Up @@ -65,7 +72,6 @@ async function runTests({
stopOrKill = async () => await killApp(app)
} else {
await nextBuild(appDir)
await nextExport(appDir, { outdir: exportDir })
const app = await startStaticServer(exportDir, null, appPort)
stopOrKill = async () => await stopApp(app)
}
Expand Down Expand Up @@ -159,11 +165,7 @@ describe('app dir with output export', () => {
{ dynamic: "'force-static'" },
])('should work with dynamic $dynamic on page', async ({ dynamic }) => {
await runTests({ dynamicPage: dynamic })
const opts = { cwd: exportDir, nodir: true }
const files = ((await glob('**/*', opts)) as string[])
.filter((f) => !f.startsWith('_next/static/chunks/'))
.sort()
expect(files).toEqual([
expect(await getFiles()).toEqual([
'404.html',
'404/index.html',
'_next/static/media/test.3f1a293b.png',
Expand Down Expand Up @@ -256,4 +258,107 @@ describe('app dir with output export', () => {
'The "exportPathMap" configuration cannot be used with the "app" directory. Please use generateStaticParams() instead.'
)
})
it('should warn about "next export" is no longer needed', async () => {
await fs.remove(distDir)
await fs.remove(exportDir)
await nextBuild(appDir)
expect(await getFiles()).toEqual([
'404.html',
'404/index.html',
'_next/static/media/test.3f1a293b.png',
'_next/static/test-build-id/_buildManifest.js',
'_next/static/test-build-id/_ssgManifest.js',
'another/first/index.html',
'another/first/index.txt',
'another/index.html',
'another/index.txt',
'another/second/index.html',
'another/second/index.txt',
'api/json',
'api/txt',
'favicon.ico',
'image-import/index.html',
'image-import/index.txt',
'index.html',
'index.txt',
'robots.txt',
])
let stdout = ''
let stderr = ''
await nextExport(
appDir,
{ outdir: exportDir },
{
onStdout(msg) {
stdout += msg
},
onStderr(msg) {
stderr += msg
},
}
)
expect(stderr).toContain(
'warn - "next export" is no longer needed when "output: export" is configured in next.config.js'
)
expect(stdout).toContain('Export successful. Files written to')
expect(await getFiles()).toEqual([
'404.html',
'404/index.html',
'_next/static/media/test.3f1a293b.png',
'_next/static/test-build-id/_buildManifest.js',
'_next/static/test-build-id/_ssgManifest.js',
'another/first/index.html',
'another/first/index.txt',
'another/index.html',
'another/index.txt',
'another/second/index.html',
'another/second/index.txt',
'api/json',
'api/txt',
'favicon.ico',
'image-import/index.html',
'image-import/index.txt',
'index.html',
'index.txt',
'robots.txt',
])
})
it('should correctly emit exported assets to config.distDir', async () => {
const outputDir = join(appDir, 'output')
await fs.remove(distDir)
await fs.remove(outputDir)
nextConfig.replace(
'trailingSlash: true,',
`trailingSlash: true,
distDir: 'output',`
)
try {
await nextBuild(appDir)
expect(await getFiles(outputDir)).toEqual([
'404.html',
'404/index.html',
'_next/static/media/test.3f1a293b.png',
'_next/static/test-build-id/_buildManifest.js',
'_next/static/test-build-id/_ssgManifest.js',
'another/first/index.html',
'another/first/index.txt',
'another/index.html',
'another/index.txt',
'another/second/index.html',
'another/second/index.txt',
'api/json',
'api/txt',
'favicon.ico',
'image-import/index.html',
'image-import/index.txt',
'index.html',
'index.txt',
'robots.txt',
])
} finally {
nextConfig.restore()
await fs.remove(distDir)
await fs.remove(outputDir)
}
})
})
10 changes: 8 additions & 2 deletions test/lib/next-test-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,14 +216,17 @@ export function runNextCommand(argv, options = {}) {
let mergedStdio = ''

let stderrOutput = ''
if (options.stderr) {
if (options.stderr || options.onStderr) {
instance.stderr.on('data', function (chunk) {
mergedStdio += chunk
stderrOutput += chunk

if (options.stderr === 'log') {
console.log(chunk.toString())
}
if (typeof options.onStderr === 'function') {
options.onStderr(chunk.toString())
}
})
} else {
instance.stderr.on('data', function (chunk) {
Expand All @@ -232,14 +235,17 @@ export function runNextCommand(argv, options = {}) {
}

let stdoutOutput = ''
if (options.stdout) {
if (options.stdout || options.onStdout) {
instance.stdout.on('data', function (chunk) {
mergedStdio += chunk
stdoutOutput += chunk

if (options.stdout === 'log') {
console.log(chunk.toString())
}
if (typeof options.onStdout === 'function') {
options.onStdout(chunk.toString())
}
})
} else {
instance.stdout.on('data', function (chunk) {
Expand Down