diff --git a/packages/next/build/index.ts b/packages/next/build/index.ts index 66bdf63a91a..63664ddfe19 100644 --- a/packages/next/build/index.ts +++ b/packages/next/build/index.ts @@ -91,8 +91,6 @@ import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' import { isWebpack5 } from 'next/dist/compiled/webpack/webpack' import { NextConfigComplete } from '../server/config-shared' -const staticWorker = require.resolve('./worker') - export type SsgRoute = { initialRevalidateSeconds: number | false srcRoute: string | null @@ -671,6 +669,10 @@ export default async function build( ) as BuildManifest const timeout = config.experimental.staticPageGenerationTimeout || 0 + const sharedPool = config.experimental.sharedPool || false + const staticWorker = sharedPool + ? require.resolve('./worker') + : require.resolve('./utils') let infoPrinted = false const staticWorkers = new Worker(staticWorker, { timeout: timeout * 1000, @@ -705,12 +707,14 @@ export default async function build( }, numWorkers: config.experimental.cpus, enableWorkerThreads: config.experimental.workerThreads, - exposedMethods: [ - 'hasCustomGetInitialProps', - 'isPageStatic', - 'getNamedExports', - 'exportPage', - ], + exposedMethods: sharedPool + ? [ + 'hasCustomGetInitialProps', + 'isPageStatic', + 'getNamedExports', + 'exportPage', + ] + : ['hasCustomGetInitialProps', 'isPageStatic', 'getNamedExports'], }) as Worker & Pick< typeof import('./worker'), @@ -1107,10 +1111,14 @@ export default async function build( pages: combinedPages, outdir: path.join(distDir, 'export'), statusMessage: 'Generating static pages', - exportPageWorker: staticWorkers.exportPage.bind(staticWorkers), - endWorker: async () => { - await staticWorkers.end() - }, + exportPageWorker: sharedPool + ? staticWorkers.exportPage.bind(staticWorkers) + : undefined, + endWorker: sharedPool + ? async () => { + await staticWorkers.end() + } + : undefined, } const exportConfig: any = { ...config, diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index a995e2d353e..1e530e71794 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -88,6 +88,7 @@ export type NextConfig = { [key: string]: any } & { swcMinify?: boolean swcLoader?: boolean cpus?: number + sharedPool?: boolean plugins?: boolean profiling?: boolean isrFlushToDisk?: boolean @@ -165,6 +166,7 @@ export const defaultConfig: NextConfig = { (Number(process.env.CIRCLE_NODE_TOTAL) || (os.cpus() || { length: 1 }).length) - 1 ), + sharedPool: false, plugins: false, profiling: false, isrFlushToDisk: true, diff --git a/test/integration/build-output/test/index.test.js b/test/integration/build-output/test/index.test.js index 4178613fd52..6e5a93b4897 100644 --- a/test/integration/build-output/test/index.test.js +++ b/test/integration/build-output/test/index.test.js @@ -13,182 +13,193 @@ const fixturesDir = join(__dirname, '..', 'fixtures') const nextConfig = new File(join(fixturesDir, 'basic-app/next.config.js')) describe('Build Output', () => { - for (const gzipSize of [true, false, undefined]) { - describe( - 'Basic Application Output' + - (gzipSize !== undefined - ? ` (with experimental.gzipSize: ${gzipSize})` - : ''), - () => { - let stdout - const appDir = join(fixturesDir, 'basic-app') - - beforeAll(async () => { - await remove(join(appDir, '.next')) - if (gzipSize !== undefined) { - nextConfig.write( - `module.exports = { experimental: { gzipSize: ${gzipSize} } };` - ) - } - }) + const configs = [{}] + for (const gzipSize of [true, false]) { + configs.push(...configs.map((c) => ({ ...c, gzipSize }))) + } + for (const sharedPool of [true]) { + configs.push(...configs.map((c) => ({ ...c, sharedPool }))) + } + for (const workerThreads of [true]) { + configs.push(...configs.map((c) => ({ ...c, workerThreads }))) + } - if (gzipSize !== undefined) { - afterAll(async () => { - nextConfig.delete() - }) + for (const experimental of configs) { + describe(`Basic Application Output (experimental: ${JSON.stringify( + experimental + )})`, () => { + let stdout + const appDir = join(fixturesDir, 'basic-app') + + const hasExperimentalConfig = Object.keys(experimental).length > 0 + + beforeAll(async () => { + await remove(join(appDir, '.next')) + if (hasExperimentalConfig) { + nextConfig.write( + `module.exports = { experimental: ${JSON.stringify( + experimental + )} };` + ) } + }) - it('should not include internal pages', async () => { - ;({ stdout } = await nextBuild(appDir, [], { - stdout: true, - })) + if (hasExperimentalConfig) { + afterAll(async () => { + nextConfig.delete() + }) + } - expect(stdout).toMatch(/\/ (.* )?\d{1,} B/) - expect(stdout).toMatch(/\+ First Load JS shared by all [ 0-9.]* kB/) - expect(stdout).toMatch(/ chunks\/main\.[0-9a-z]{6}\.js [ 0-9.]* kB/) - expect(stdout).toMatch( - / chunks\/framework\.[0-9a-z]{6}\.js [ 0-9. ]* kB/ - ) + it('should not include internal pages', async () => { + ;({ stdout } = await nextBuild(appDir, [], { + stdout: true, + })) - expect(stdout).not.toContain(' /_document') - expect(stdout).not.toContain(' /_app') - expect(stdout).not.toContain(' /_error') - expect(stdout).not.toContain('') + expect(stdout).toMatch(/\/ (.* )?\d{1,} B/) + expect(stdout).toMatch(/\+ First Load JS shared by all [ 0-9.]* kB/) + expect(stdout).toMatch(/ chunks\/main\.[0-9a-z]{6}\.js [ 0-9.]* kB/) + expect(stdout).toMatch( + / chunks\/framework\.[0-9a-z]{6}\.js [ 0-9. ]* kB/ + ) - expect(stdout).toContain('○ /') - }) + expect(stdout).not.toContain(' /_document') + expect(stdout).not.toContain(' /_app') + expect(stdout).not.toContain(' /_error') + expect(stdout).not.toContain('') + + expect(stdout).toContain('○ /') + }) - it('should not deviate from snapshot', async () => { - console.log(stdout) + it('should not deviate from snapshot', async () => { + console.log(stdout) - if (process.env.NEXT_PRIVATE_SKIP_SIZE_TESTS) { - return - } + if (process.env.NEXT_PRIVATE_SKIP_SIZE_TESTS) { + return + } + + const parsePageSize = (page) => + stdout.match( + new RegExp(` ${page} .*?((?:\\d|\\.){1,} (?:\\w{1,})) `) + )[1] - const parsePageSize = (page) => - stdout.match( - new RegExp(` ${page} .*?((?:\\d|\\.){1,} (?:\\w{1,})) `) - )[1] - - const parsePageFirstLoad = (page) => - stdout.match( - new RegExp( - ` ${page} .*?(?:(?:\\d|\\.){1,}) .*? ((?:\\d|\\.){1,} (?:\\w{1,}))` - ) - )[1] - - const parseSharedSize = (sharedPartName) => { - const matches = stdout.match( - new RegExp(`${sharedPartName} .*? ((?:\\d|\\.){1,} (?:\\w{1,}))`) + const parsePageFirstLoad = (page) => + stdout.match( + new RegExp( + ` ${page} .*?(?:(?:\\d|\\.){1,}) .*? ((?:\\d|\\.){1,} (?:\\w{1,}))` ) + )[1] - if (!matches) { - throw new Error(`Could not match ${sharedPartName}`) - } + const parseSharedSize = (sharedPartName) => { + const matches = stdout.match( + new RegExp(`${sharedPartName} .*? ((?:\\d|\\.){1,} (?:\\w{1,}))`) + ) - return matches[1] + if (!matches) { + throw new Error(`Could not match ${sharedPartName}`) } - const indexSize = parsePageSize('/') - const indexFirstLoad = parsePageFirstLoad('/') - - const err404Size = parsePageSize('/404') - const err404FirstLoad = parsePageFirstLoad('/404') - - const sharedByAll = parseSharedSize('shared by all') - const _appSize = parseSharedSize('_app\\..*?\\.js') - const webpackSize = parseSharedSize('webpack\\..*?\\.js') - const mainSize = parseSharedSize('main\\..*?\\.js') - const frameworkSize = parseSharedSize('framework\\..*?\\.js') - - for (const size of [ - indexSize, - indexFirstLoad, - err404Size, - err404FirstLoad, - sharedByAll, - _appSize, - webpackSize, - mainSize, - frameworkSize, - ]) { - expect(parseFloat(size)).toBeGreaterThan(0) - } + return matches[1] + } - // const gz = gzipSize !== false + const indexSize = parsePageSize('/') + const indexFirstLoad = parsePageFirstLoad('/') + + const err404Size = parsePageSize('/404') + const err404FirstLoad = parsePageFirstLoad('/404') + + const sharedByAll = parseSharedSize('shared by all') + const _appSize = parseSharedSize('_app\\..*?\\.js') + const webpackSize = parseSharedSize('webpack\\..*?\\.js') + const mainSize = parseSharedSize('main\\..*?\\.js') + const frameworkSize = parseSharedSize('framework\\..*?\\.js') + + for (const size of [ + indexSize, + indexFirstLoad, + err404Size, + err404FirstLoad, + sharedByAll, + _appSize, + webpackSize, + mainSize, + frameworkSize, + ]) { + expect(parseFloat(size)).toBeGreaterThan(0) + } - // expect(parseFloat(indexSize) / 1000).toBeCloseTo( - // gz ? 0.251 : 0.394, - // 2 - // ) - expect(indexSize.endsWith('B')).toBe(true) + // const gz = experimental.gzipSize !== false - // expect(parseFloat(indexFirstLoad)).toBeCloseTo(gz ? 64 : 196, 1) - expect(indexFirstLoad.endsWith('kB')).toBe(true) + // expect(parseFloat(indexSize) / 1000).toBeCloseTo( + // gz ? 0.251 : 0.394, + // 2 + // ) + expect(indexSize.endsWith('B')).toBe(true) - // expect(parseFloat(err404Size)).toBeCloseTo(gz ? 3.17 : 8.51, 1) - expect(err404Size.endsWith('B')).toBe(true) + // expect(parseFloat(indexFirstLoad)).toBeCloseTo(gz ? 64 : 196, 1) + expect(indexFirstLoad.endsWith('kB')).toBe(true) - // expect(parseFloat(err404FirstLoad)).toBeCloseTo(gz ? 66.9 : 204, 1) - expect(err404FirstLoad.endsWith('kB')).toBe(true) + // expect(parseFloat(err404Size)).toBeCloseTo(gz ? 3.17 : 8.51, 1) + expect(err404Size.endsWith('B')).toBe(true) - // expect(parseFloat(sharedByAll)).toBeCloseTo(gz ? 63.7 : 196, 1) - expect(sharedByAll.endsWith('kB')).toBe(true) + // expect(parseFloat(err404FirstLoad)).toBeCloseTo(gz ? 66.9 : 204, 1) + expect(err404FirstLoad.endsWith('kB')).toBe(true) - // const appSizeValue = _appSize.endsWith('kB') - // ? parseFloat(_appSize) - // : parseFloat(_appSize) / 1000 - // expect(appSizeValue).toBeCloseTo(gz ? 0.799 : 1.63, 1) - expect(_appSize.endsWith('kB') || _appSize.endsWith(' B')).toBe(true) + // expect(parseFloat(sharedByAll)).toBeCloseTo(gz ? 63.7 : 196, 1) + expect(sharedByAll.endsWith('kB')).toBe(true) - // const webpackSizeValue = webpackSize.endsWith('kB') - // ? parseFloat(webpackSize) - // : parseFloat(webpackSize) / 1000 - // expect(webpackSizeValue).toBeCloseTo(gz ? 0.766 : 1.46, 2) - expect(webpackSize.endsWith('kB') || webpackSize.endsWith(' B')).toBe( - true - ) + // const appSizeValue = _appSize.endsWith('kB') + // ? parseFloat(_appSize) + // : parseFloat(_appSize) / 1000 + // expect(appSizeValue).toBeCloseTo(gz ? 0.799 : 1.63, 1) + expect(_appSize.endsWith('kB') || _appSize.endsWith(' B')).toBe(true) - // expect(parseFloat(mainSize)).toBeCloseTo(gz ? 20.1 : 62.7, 1) - expect(mainSize.endsWith('kB')).toBe(true) + // const webpackSizeValue = webpackSize.endsWith('kB') + // ? parseFloat(webpackSize) + // : parseFloat(webpackSize) / 1000 + // expect(webpackSizeValue).toBeCloseTo(gz ? 0.766 : 1.46, 2) + expect(webpackSize.endsWith('kB') || webpackSize.endsWith(' B')).toBe( + true + ) - // expect(parseFloat(frameworkSize)).toBeCloseTo(gz ? 42.0 : 130, 1) - expect(frameworkSize.endsWith('kB')).toBe(true) - }) + // expect(parseFloat(mainSize)).toBeCloseTo(gz ? 20.1 : 62.7, 1) + expect(mainSize.endsWith('kB')).toBe(true) - it('should print duration when rendering or get static props takes long', () => { - const matches = stdout.match( - / \/slow-static\/.+\/.+(?: \(\d+ ms\))?| \[\+\d+ more paths\]/g - ) + // expect(parseFloat(frameworkSize)).toBeCloseTo(gz ? 42.0 : 130, 1) + expect(frameworkSize.endsWith('kB')).toBe(true) + }) - expect(matches).toEqual([ - // summary - expect.stringMatching( - /\/\[propsDuration\]\/\[renderDuration\] \(\d+ ms\)/ - ), - // ordered by duration, includes duration - expect.stringMatching(/\/2000\/10 \(\d+ ms\)$/), - expect.stringMatching(/\/10\/1000 \(\d+ ms\)$/), - expect.stringMatching(/\/300\/10 \(\d+ ms\)$/), - // kept in original order - expect.stringMatching(/\/5\/5$/), - expect.stringMatching(/\/25\/25$/), - expect.stringMatching(/\/20\/20$/), - expect.stringMatching(/\/10\/10$/), - // max of 7 preview paths - ' [+2 more paths]', - ]) - }) + it('should print duration when rendering or get static props takes long', () => { + const matches = stdout.match( + / \/slow-static\/.+\/.+(?: \(\d+ ms\))?| \[\+\d+ more paths\]/g + ) + + expect(matches).toEqual([ + // summary + expect.stringMatching( + /\/\[propsDuration\]\/\[renderDuration\] \(\d+ ms\)/ + ), + // ordered by duration, includes duration + expect.stringMatching(/\/2000\/10 \(\d+ ms\)$/), + expect.stringMatching(/\/10\/1000 \(\d+ ms\)$/), + expect.stringMatching(/\/300\/10 \(\d+ ms\)$/), + // kept in original order + expect.stringMatching(/\/5\/5$/), + expect.stringMatching(/\/25\/25$/), + expect.stringMatching(/\/20\/20$/), + expect.stringMatching(/\/10\/10$/), + // max of 7 preview paths + ' [+2 more paths]', + ]) + }) - it('should not emit extracted comments', async () => { - const files = await recursiveReadDir( - join(appDir, '.next'), - /\.txt|\.LICENSE\./ - ) - expect(files).toEqual([]) - }) - } - ) + it('should not emit extracted comments', async () => { + const files = await recursiveReadDir( + join(appDir, '.next'), + /\.txt|\.LICENSE\./ + ) + expect(files).toEqual([]) + }) + }) } describe('Custom App Output', () => {