From b8460266733716d2d04dfc9657bdfccb197d34aa Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 26 Jul 2022 13:56:29 -0500 Subject: [PATCH] Handle getStaticPaths error inside worker to avoid serializing (#39032) * Handle getStaticPaths error inside worker to avoid serializing * handle invalid export case --- packages/next/build/utils.ts | 204 +++++++++--------- .../gsp-build-errors/test/index.test.js | 67 +++++- 2 files changed, 169 insertions(+), 102 deletions(-) diff --git a/packages/next/build/utils.ts b/packages/next/build/utils.ts index 8b421fd4469b003..95f8d413a000ae5 100644 --- a/packages/next/build/utils.ts +++ b/packages/next/build/utils.ts @@ -862,117 +862,125 @@ export async function isPageStatic( traceExcludes?: string[] }> { const isPageStaticSpan = trace('is-page-static-utils', parentId) - return isPageStaticSpan.traceAsyncFn(async () => { - require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig) - setHttpAgentOptions(httpAgentOptions) + return isPageStaticSpan + .traceAsyncFn(async () => { + require('../shared/lib/runtime-config').setConfig(runtimeEnvConfig) + setHttpAgentOptions(httpAgentOptions) - const mod = await loadComponents(distDir, page, serverless) - const Comp = mod.Component + const mod = await loadComponents(distDir, page, serverless) + const Comp = mod.Component - if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') { - throw new Error('INVALID_DEFAULT_EXPORT') - } + if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') { + throw new Error('INVALID_DEFAULT_EXPORT') + } - const hasGetInitialProps = !!(Comp as any).getInitialProps - const hasStaticProps = !!mod.getStaticProps - const hasStaticPaths = !!mod.getStaticPaths - const hasServerProps = !!mod.getServerSideProps - const hasLegacyServerProps = !!(await mod.ComponentMod - .unstable_getServerProps) - const hasLegacyStaticProps = !!(await mod.ComponentMod - .unstable_getStaticProps) - const hasLegacyStaticPaths = !!(await mod.ComponentMod - .unstable_getStaticPaths) - const hasLegacyStaticParams = !!(await mod.ComponentMod - .unstable_getStaticParams) - - if (hasLegacyStaticParams) { - throw new Error( - `unstable_getStaticParams was replaced with getStaticPaths. Please update your code.` - ) - } + const hasGetInitialProps = !!(Comp as any).getInitialProps + const hasStaticProps = !!mod.getStaticProps + const hasStaticPaths = !!mod.getStaticPaths + const hasServerProps = !!mod.getServerSideProps + const hasLegacyServerProps = !!(await mod.ComponentMod + .unstable_getServerProps) + const hasLegacyStaticProps = !!(await mod.ComponentMod + .unstable_getStaticProps) + const hasLegacyStaticPaths = !!(await mod.ComponentMod + .unstable_getStaticPaths) + const hasLegacyStaticParams = !!(await mod.ComponentMod + .unstable_getStaticParams) + + if (hasLegacyStaticParams) { + throw new Error( + `unstable_getStaticParams was replaced with getStaticPaths. Please update your code.` + ) + } - if (hasLegacyStaticPaths) { - throw new Error( - `unstable_getStaticPaths was replaced with getStaticPaths. Please update your code.` - ) - } + if (hasLegacyStaticPaths) { + throw new Error( + `unstable_getStaticPaths was replaced with getStaticPaths. Please update your code.` + ) + } - if (hasLegacyStaticProps) { - throw new Error( - `unstable_getStaticProps was replaced with getStaticProps. Please update your code.` - ) - } + if (hasLegacyStaticProps) { + throw new Error( + `unstable_getStaticProps was replaced with getStaticProps. Please update your code.` + ) + } - if (hasLegacyServerProps) { - throw new Error( - `unstable_getServerProps was replaced with getServerSideProps. Please update your code.` - ) - } + if (hasLegacyServerProps) { + throw new Error( + `unstable_getServerProps was replaced with getServerSideProps. Please update your code.` + ) + } - // A page cannot be prerendered _and_ define a data requirement. That's - // contradictory! - if (hasGetInitialProps && hasStaticProps) { - throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT) - } + // A page cannot be prerendered _and_ define a data requirement. That's + // contradictory! + if (hasGetInitialProps && hasStaticProps) { + throw new Error(SSG_GET_INITIAL_PROPS_CONFLICT) + } - if (hasGetInitialProps && hasServerProps) { - throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT) - } + if (hasGetInitialProps && hasServerProps) { + throw new Error(SERVER_PROPS_GET_INIT_PROPS_CONFLICT) + } - if (hasStaticProps && hasServerProps) { - throw new Error(SERVER_PROPS_SSG_CONFLICT) - } + if (hasStaticProps && hasServerProps) { + throw new Error(SERVER_PROPS_SSG_CONFLICT) + } - const pageIsDynamic = isDynamicRoute(page) - // A page cannot have static parameters if it is not a dynamic page. - if (hasStaticProps && hasStaticPaths && !pageIsDynamic) { - throw new Error( - `getStaticPaths can only be used with dynamic pages, not '${page}'.` + - `\nLearn more: https://nextjs.org/docs/routing/dynamic-routes` - ) - } + const pageIsDynamic = isDynamicRoute(page) + // A page cannot have static parameters if it is not a dynamic page. + if (hasStaticProps && hasStaticPaths && !pageIsDynamic) { + throw new Error( + `getStaticPaths can only be used with dynamic pages, not '${page}'.` + + `\nLearn more: https://nextjs.org/docs/routing/dynamic-routes` + ) + } - if (hasStaticProps && pageIsDynamic && !hasStaticPaths) { - throw new Error( - `getStaticPaths is required for dynamic SSG pages and is missing for '${page}'.` + - `\nRead more: https://nextjs.org/docs/messages/invalid-getstaticpaths-value` - ) - } + if (hasStaticProps && pageIsDynamic && !hasStaticPaths) { + throw new Error( + `getStaticPaths is required for dynamic SSG pages and is missing for '${page}'.` + + `\nRead more: https://nextjs.org/docs/messages/invalid-getstaticpaths-value` + ) + } - let prerenderRoutes: Array | undefined - let encodedPrerenderRoutes: Array | undefined - let prerenderFallback: boolean | 'blocking' | undefined - if (hasStaticProps && hasStaticPaths) { - ;({ - paths: prerenderRoutes, - fallback: prerenderFallback, - encodedPaths: encodedPrerenderRoutes, - } = await buildStaticPaths( - page, - mod.getStaticPaths!, - configFileName, - locales, - defaultLocale - )) - } + let prerenderRoutes: Array | undefined + let encodedPrerenderRoutes: Array | undefined + let prerenderFallback: boolean | 'blocking' | undefined + if (hasStaticProps && hasStaticPaths) { + ;({ + paths: prerenderRoutes, + fallback: prerenderFallback, + encodedPaths: encodedPrerenderRoutes, + } = await buildStaticPaths( + page, + mod.getStaticPaths!, + configFileName, + locales, + defaultLocale + )) + } - const isNextImageImported = (global as any).__NEXT_IMAGE_IMPORTED - const config: PageConfig = mod.pageConfig - return { - isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps, - isHybridAmp: config.amp === 'hybrid', - isAmpOnly: config.amp === true, - prerenderRoutes, - prerenderFallback, - encodedPrerenderRoutes, - hasStaticProps, - hasServerProps, - isNextImageImported, - traceIncludes: config.unstable_includeFiles || [], - traceExcludes: config.unstable_excludeFiles || [], - } - }) + const isNextImageImported = (global as any).__NEXT_IMAGE_IMPORTED + const config: PageConfig = mod.pageConfig + return { + isStatic: !hasStaticProps && !hasGetInitialProps && !hasServerProps, + isHybridAmp: config.amp === 'hybrid', + isAmpOnly: config.amp === true, + prerenderRoutes, + prerenderFallback, + encodedPrerenderRoutes, + hasStaticProps, + hasServerProps, + isNextImageImported, + traceIncludes: config.unstable_includeFiles || [], + traceExcludes: config.unstable_excludeFiles || [], + } + }) + .catch((err) => { + if (err.message === 'INVALID_DEFAULT_EXPORT') { + throw err + } + console.error(err) + throw new Error(`Failed to collect page data for ${page}`) + }) } export async function hasCustomGetInitialProps( diff --git a/test/integration/gsp-build-errors/test/index.test.js b/test/integration/gsp-build-errors/test/index.test.js index f585bc45540194b..2536576acb462db 100644 --- a/test/integration/gsp-build-errors/test/index.test.js +++ b/test/integration/gsp-build-errors/test/index.test.js @@ -1,15 +1,14 @@ /* eslint-env jest */ import fs from 'fs-extra' -import { join } from 'path' +import { dirname, join } from 'path' import { nextBuild } from 'next-test-utils' const appDir = join(__dirname, '..') const pagesDir = join(appDir, 'pages') -const testPage = join(pagesDir, 'test.js') -const writePage = async (content) => { - await fs.ensureDir(pagesDir) +const writePage = async (content, testPage = join(pagesDir, 'test.js')) => { + await fs.ensureDir(dirname(testPage)) await fs.writeFile(testPage, content) } @@ -107,4 +106,64 @@ describe('GSP build errors', () => { expect(code).toBe(1) expect(stderr).toContain('a string error') }) + + it('should handle non-serializable error in getStaticProps', async () => { + await writePage(` + export function getStaticProps() { + const err = new Error('my custom error') + err.hello = 'world' + err.a = [1,2,3] + err.original = err + err.b = err.a + + throw err + + return { + props: {} + } + } + + export default function () { + return null + } + `) + const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + expect(code).toBe(1) + expect(stderr).toContain('my custom error') + }) + + it('should handle non-serializable error in getStaticPaths', async () => { + await writePage( + ` + export function getStaticProps() { + return { + props: {} + } + } + + export function getStaticPaths() { + const err = new Error('my custom error') + err.hello = 'world' + err.a = [1,2,3] + err.original = err + err.b = err.a + + throw err + + return { + paths: [], + fallback: true + } + } + + export default function () { + return null + } + `, + join(pagesDir, '[slug].js') + ) + const { stderr, code } = await nextBuild(appDir, [], { stderr: true }) + expect(code).toBe(1) + expect(stderr).toContain('my custom error') + }) })