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

fix(gatsby): don't fail builds when a component throws in Suspense #37648

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/gatsby-cli/src/structured-errors/error-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ const errors: Record<string, IErrorMapEntry> = {
category: ErrorCategory.USER,
type: Type.ENGINE_EXECUTION,
},
"95316": {
text: (): string => `Non fatal error during static HTML generation`,
level: Level.WARNING,
category: ErrorCategory.USER,
type: Type.HTML_GENERATION,
},
"98001": {
text: (): string =>
`Built Rendering Engines failed validation.\n\nPlease open an issue with a reproduction at https://gatsby.dev/new-issue for more help.`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ exports[`develop-static-entry onPreRenderHTML can be used to replace preBodyComp
exports[`static-entry onPreRenderHTML can be used to replace headComponents 1`] = `
Object {
"html": "<!DOCTYPE html><html><head><meta charSet=\\"utf-8\\"/><meta http-equiv=\\"x-ua-compatible\\" content=\\"ie=edge\\"/><meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1, shrink-to-fit=no\\"/><style> .style3 </style><style> .style2 </style><style> .style1 </style><meta name=\\"generator\\" content=\\"Gatsby 2.0.0\\"/></head><body><div id=\\"___gatsby\\"><div style=\\"outline:none\\" tabindex=\\"-1\\" id=\\"gatsby-focus-wrapper\\"></div><div id=\\"gatsby-announcer\\" style=\\"position:absolute;top:0;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0\\" aria-live=\\"assertive\\" aria-atomic=\\"true\\"></div></div><script id=\\"gatsby-script-loader\\">/*<![CDATA[*/window.pagePath=\\"/about/\\";window.___webpackCompilationHash=\\"1234567890abcdef1234\\";/*]]>*/</script><script id=\\"gatsby-chunk-mapping\\">/*<![CDATA[*/window.___chunkMapping={};/*]]>*/</script></body></html>",
"nonFatalErrors": Array [],
"sliceData": Object {},
"unsafeBuiltinsUsage": Array [],
}
Expand All @@ -23,6 +24,7 @@ Object {
exports[`static-entry onPreRenderHTML can be used to replace postBodyComponents 1`] = `
Object {
"html": "<!DOCTYPE html><html><head><meta charSet=\\"utf-8\\"/><meta http-equiv=\\"x-ua-compatible\\" content=\\"ie=edge\\"/><meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1, shrink-to-fit=no\\"/><meta name=\\"generator\\" content=\\"Gatsby 2.0.0\\"/></head><body><div id=\\"___gatsby\\"><div style=\\"outline:none\\" tabindex=\\"-1\\" id=\\"gatsby-focus-wrapper\\"></div><div id=\\"gatsby-announcer\\" style=\\"position:absolute;top:0;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0\\" aria-live=\\"assertive\\" aria-atomic=\\"true\\"></div></div><script id=\\"gatsby-chunk-mapping\\">/*<![CDATA[*/window.___chunkMapping={};/*]]>*/</script><script id=\\"gatsby-script-loader\\">/*<![CDATA[*/window.pagePath=\\"/about/\\";window.___webpackCompilationHash=\\"1234567890abcdef1234\\";/*]]>*/</script><div> div3 </div><div> div2 </div><div> div1 </div></body></html>",
"nonFatalErrors": Array [],
"sliceData": Object {},
"unsafeBuiltinsUsage": Array [],
}
Expand All @@ -31,6 +33,7 @@ Object {
exports[`static-entry onPreRenderHTML can be used to replace preBodyComponents 1`] = `
Object {
"html": "<!DOCTYPE html><html><head><meta charSet=\\"utf-8\\"/><meta http-equiv=\\"x-ua-compatible\\" content=\\"ie=edge\\"/><meta name=\\"viewport\\" content=\\"width=device-width, initial-scale=1, shrink-to-fit=no\\"/><meta name=\\"generator\\" content=\\"Gatsby 2.0.0\\"/></head><body><div> div3 </div><div> div2 </div><div> div1 </div><div id=\\"___gatsby\\"><div style=\\"outline:none\\" tabindex=\\"-1\\" id=\\"gatsby-focus-wrapper\\"></div><div id=\\"gatsby-announcer\\" style=\\"position:absolute;top:0;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0, 0, 0, 0);white-space:nowrap;border:0\\" aria-live=\\"assertive\\" aria-atomic=\\"true\\"></div></div><script id=\\"gatsby-script-loader\\">/*<![CDATA[*/window.pagePath=\\"/about/\\";window.___webpackCompilationHash=\\"1234567890abcdef1234\\";/*]]>*/</script><script id=\\"gatsby-chunk-mapping\\">/*<![CDATA[*/window.___chunkMapping={};/*]]>*/</script></body></html>",
"nonFatalErrors": Array [],
"sliceData": Object {},
"unsafeBuiltinsUsage": Array [],
}
Expand Down
24 changes: 23 additions & 1 deletion packages/gatsby/cache-dir/static-entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,8 @@ export default async function staticPage({
pathPrefix: __PATH_PREFIX__,
})

const nonFatalErrors = []

// If no one stepped up, we'll handle it.
if (!bodyHtml) {
try {
Expand All @@ -342,6 +344,12 @@ export default async function staticPage({
pipe(writableStream)
},
onError(error) {
// onError is triggered for fatal onShellError as well
// but in this case we do throw fatal error and non fatal errors
// are just discarded
nonFatalErrors.push(error.stack)
},
onShellError(error) {
writableStream.destroy(error)
},
})
Expand Down Expand Up @@ -528,6 +536,7 @@ export default async function staticPage({
return {
html,
unsafeBuiltinsUsage: global.unsafeBuiltinUsage,
nonFatalErrors,
sliceData: sliceProps,
}
} catch (e) {
Expand Down Expand Up @@ -575,6 +584,8 @@ export async function renderSlice({ slice, staticQueryContext, props = {} }) {
</SlicesContext.Provider>
)

const nonFatalErrors = []

const writableStream = new WritableAsPromise()
const { pipe } = renderToPipeableStream(
sliceWrappedWithWrapRootElementAndContexts,
Expand All @@ -583,10 +594,21 @@ export async function renderSlice({ slice, staticQueryContext, props = {} }) {
pipe(writableStream)
},
onError(error) {
// onError is triggered for fatal onShellError as well
// but in this case we do throw fatal error and non fatal errors
// are just discarded
nonFatalErrors.push(error.stack)
},
onShellError(error) {
writableStream.destroy(error)
},
}
)

return await writableStream
const html = await writableStream

return {
html,
nonFatalErrors,
}
}
48 changes: 47 additions & 1 deletion packages/gatsby/src/commands/build-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ export const deleteRenderer = async (rendererPath: string): Promise<void> => {
}
export interface IRenderHtmlResult {
unsafeBuiltinsUsageByPagePath: Record<string, Array<string>>
nonFatalErrorsByPagePath: Record<string, Array<string>>
previewErrors: Record<string, any>

slicesPropsPerPage: Record<
Expand Down Expand Up @@ -359,6 +360,7 @@ const renderHTMLQueue = async (
: workerPool.single.renderHTMLDev

const uniqueUnsafeBuiltinUsedStacks = new Set<string>()
const uniqueNonFatalErrorStacks = new Set<string>()

try {
await Bluebird.map(segments, async pageSegment => {
Expand Down Expand Up @@ -435,6 +437,27 @@ const renderHTMLQueue = async (
uniqueUnsafeBuiltinUsedStacks.add(unsafeUsageStack)
}
}

for (const arrayOfErrors of Object.values(
htmlRenderMeta.nonFatalErrorsByPagePath
)) {
for (const nonFatalErrorStack of arrayOfErrors) {
if (!uniqueNonFatalErrorStacks.has(nonFatalErrorStack)) {
uniqueNonFatalErrorStacks.add(nonFatalErrorStack)

const prettyError = createErrorFromString(
nonFatalErrorStack,
`${htmlComponentRendererPath}.map`
)

reporter.error({
id: `95316`,
context: {},
error: prettyError,
})
}
}
}
}

if (activity && activity.tick) {
Expand Down Expand Up @@ -765,6 +788,8 @@ export async function buildSlices({
}
}

const uniqueNonFatalErrorStacks = new Set<string>()

if (slicesProps.length > 0) {
const buildHTMLActivityProgress = reporter.activityTimer(
`Building slices HTML (${slicesProps.length})`,
Expand All @@ -778,12 +803,33 @@ export async function buildSlices({
try {
const slices = Array.from(state.slices.entries())

await workerPool.single.renderSlices({
const { nonFatalErrors } = await workerPool.single.renderSlices({
publicDir: path.join(program.directory, `public`),
htmlComponentRendererPath,
slices,
slicesProps,
})

try {
for (const nonFatalErrorStack of nonFatalErrors) {
if (!uniqueNonFatalErrorStacks.has(nonFatalErrorStack)) {
uniqueNonFatalErrorStacks.add(nonFatalErrorStack)

const prettyError = createErrorFromString(
nonFatalErrorStack,
`${htmlComponentRendererPath}.map`
)

reporter.error({
id: `95316`,
context: {},
error: prettyError,
})
}
}
} catch (e) {
console.log({ e })
}
} catch (err) {
const prettyError = createErrorFromString(
err.stack,
Expand Down
6 changes: 6 additions & 0 deletions packages/gatsby/src/utils/page-ssr-module/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,12 @@ export async function renderHTML({
sliceData,
})

if (results.nonFatalErrors && results.nonFatalErrors.length > 0) {
for (const error of results.nonFatalErrors) {
reporter.error(error)
}
}

return results.html.replace(
`<slice-start id="_gatsby-scripts-1"></slice-start><slice-end id="_gatsby-scripts-1"></slice-end>`,
GATSBY_SLICES_SCRIPT
Expand Down
37 changes: 26 additions & 11 deletions packages/gatsby/src/utils/worker/child/render-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ export const renderHTMLProd = async ({
const isPreview = process.env.GATSBY_IS_PREVIEW === `true`

const unsafeBuiltinsUsageByPagePath = {}
const nonFatalErrorsByPagePath = {}
const previewErrors = {}
const allSlicesProps = {}

Expand Down Expand Up @@ -408,7 +409,7 @@ export const renderHTMLProd = async ({
const pageData = await readPageData(publicDir, pagePath)
const resourcesForTemplate = await getResourcesForTemplate(pageData)

const { html, unsafeBuiltinsUsage, sliceData } =
const { html, unsafeBuiltinsUsage, sliceData, nonFatalErrors } =
await htmlComponentRenderer.default({
pagePath,
pageData,
Expand All @@ -425,6 +426,10 @@ export const renderHTMLProd = async ({
unsafeBuiltinsUsageByPagePath[pagePath] = unsafeBuiltinsUsage
}

if (nonFatalErrors.length > 0) {
nonFatalErrorsByPagePath[pagePath] = nonFatalErrors
}

await fs.outputFile(generateHtmlPath(publicDir, pagePath), html)
} catch (e) {
if (e.unsafeBuiltinsUsage && e.unsafeBuiltinsUsage.length > 0) {
Expand Down Expand Up @@ -466,6 +471,7 @@ export const renderHTMLProd = async ({
unsafeBuiltinsUsageByPagePath,
previewErrors,
slicesPropsPerPage: allSlicesProps,
nonFatalErrorsByPagePath,
}
}

Expand Down Expand Up @@ -708,8 +714,11 @@ export async function renderSlices({
slices: Array<[string, IGatsbySlice]>
slicesProps: Array<ISlicePropsEntry>
htmlComponentRendererPath: string
}): Promise<void> {
}): Promise<{
nonFatalErrors: Array<string>
}> {
const htmlComponentRenderer = require(htmlComponentRendererPath)
const nonFatalErrors: Array<string> = []

for (const { sliceId, props, sliceName, hasChildren } of slicesProps) {
const sliceEntry = slices.find(f => f[0] === sliceName)
Expand All @@ -729,17 +738,21 @@ export async function renderSlices({
const sliceData = await readSliceData(publicDir, slice.name)

try {
const html = await htmlComponentRenderer.renderSlice({
slice,
staticQueryContext,
props: {
data: sliceData?.result?.data,
...(hasChildren ? { children: MAGIC_CHILDREN_STRING } : {}),
...props,
},
})
const { html, nonFatalErrors: nonFatalErrorsForCurrentSlice } =
await htmlComponentRenderer.renderSlice({
slice,
staticQueryContext,
props: {
data: sliceData?.result?.data,
...(hasChildren ? { children: MAGIC_CHILDREN_STRING } : {}),
...props,
},
})
const split = html.split(MAGIC_CHILDREN_STRING)

if (nonFatalErrorsForCurrentSlice.length > 0) {
nonFatalErrors.push(...nonFatalErrorsForCurrentSlice)
}
// TODO always generate both for now
let index = 1
for (const htmlChunk of split) {
Expand All @@ -759,4 +772,6 @@ export async function renderSlices({
throw renderSliceError
}
}

return { nonFatalErrors }
}