Skip to content

Commit

Permalink
feat(gatsby,gatsby-cli): Slice HTML rendering error handling (#36822)
Browse files Browse the repository at this point in the history
  • Loading branch information
tyhopp authored Oct 14, 2022
1 parent a51af7f commit f21b54f
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 34 deletions.
2 changes: 1 addition & 1 deletion integration-tests/gatsby-cli/__tests__/build-ssr-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe(`gatsby build (SSR errors)`, () => {
logs.should.contain(`failed Building static HTML for pages`)
logs.should.contain(`ERROR #95312`)
logs.should.contain(
`"window" is not available during Server-Side Rendering.`
`"window" is not available during server-side rendering.`
)
logs.should.contain(
`See our docs page for more info on this error: https://gatsby.dev/debug-html`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,12 @@ Object {
"category": "USER",
"code": "95312",
"context": Object {
"ref": "navigator",
"undefinedGlobal": "navigator",
},
"docsUrl": "https://gatsby.dev/debug-html",
"level": "ERROR",
"stack": Array [],
"text": "\\"navigator\\" is not available during Server-Side Rendering. Enable \\"DEV_SSR\\" to debug this during \\"gatsby develop\\".",
"text": "\\"navigator\\" is not available during server-side rendering. Enable \\"DEV_SSR\\" to debug this during \\"gatsby develop\\".",
}
`;
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby-cli/src/reporter/__tests__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ describe(`report.error`, () => {
reporter.error({
id: `95312`,
context: {
ref: `navigator`,
undefinedGlobal: `navigator`,
},
})
const generatedError = getErrorMessages(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ test(`it constructs an error from the supplied errorMap`, () => {

test(`it does not overwrite internal error map`, () => {
const error = constructError(
{ details: { id: `95312`, context: { ref: `Error!` } } },
{ details: { id: `95312`, context: { undefinedGlobal: `window` } } },
{
"95312": {
text: (context): string => `Error text is ${context.someProp} `,
Expand Down
29 changes: 28 additions & 1 deletion packages/gatsby-cli/src/structured-errors/error-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const errors = {
},
"95312": {
text: (context): string =>
`"${context.ref}" is not available during Server-Side Rendering. Enable "DEV_SSR" to debug this during "gatsby develop".`,
`"${context.undefinedGlobal}" is not available during server-side rendering. Enable "DEV_SSR" to debug this during "gatsby develop".`,
level: Level.ERROR,
docsUrl: `https://gatsby.dev/debug-html`,
category: ErrorCategory.USER,
Expand Down Expand Up @@ -650,6 +650,33 @@ const errors = {
// TODO: change domain to gatsbyjs.com when it's released
docsUrl: `https://v5.gatsbyjs.com/docs/reference/config-files/actions#createSlice`,
},
"11339": {
text: (context): string =>
[
`Building static HTML failed for slice "${context.sliceName}".`,
`Slice metadata: ${JSON.stringify(context?.sliceData || {}, null, 2)}`,
`Slice props: ${JSON.stringify(context?.sliceProps || {}, null, 2)}`,
]
.filter(Boolean)
.join(`\n\n`),
level: Level.ERROR,
category: ErrorCategory.USER,
// TODO: change domain to gatsbyjs.com when it's released
docsUrl: `https://v5.gatsbyjs.com/docs/reference/config-files/actions#createSlice`,
},
"11340": {
text: (context): string =>
[
`Building static HTML failed for slice "${context.sliceName}".`,
`"${context.undefinedGlobal}" is not available during server-side rendering. Enable "DEV_SSR" to debug this during "gatsby develop".`,
]
.filter(Boolean)
.join(`\n\n`),
level: Level.ERROR,
category: ErrorCategory.USER,
// TODO: change domain to gatsbyjs.com when it's released
docsUrl: `https://v5.gatsbyjs.com/docs/reference/config-files/actions#createSlice`,
},
// node object didn't pass validation
"11467": {
text: (context): string =>
Expand Down
34 changes: 26 additions & 8 deletions packages/gatsby/src/commands/build-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import type { GatsbyWorkerPool } from "../utils/worker/pool"
import { stitchSliceForAPage } from "../utils/slices/stitching"
import type { ISlicePropsEntry } from "../utils/worker/child/render-html"
import { getPageMode } from "../utils/page-mode"
import { extractUndefinedGlobal } from "../utils/extract-undefined-global"

type IActivity = any // TODO

Expand Down Expand Up @@ -662,15 +663,14 @@ export async function buildHTMLPagesAndDeleteStaleArtifacts({
let id = `95313` // TODO: verify error IDs exist
const context = {
errorPath: err.context && err.context.path,
ref: ``,
undefinedGlobal: ``,
}

const match = err.message.match(
/ReferenceError: (window|document|localStorage|navigator|alert|location) is not defined/i
)
if (match && match[1]) {
const undefinedGlobal = extractUndefinedGlobal(err)

if (undefinedGlobal) {
id = `95312`
context.ref = match[1]
context.undefinedGlobal = undefinedGlobal
}

buildHTMLActivityProgress.panic({
Expand Down Expand Up @@ -800,8 +800,26 @@ export async function buildSlices({
slices,
slicesProps,
})
} catch (e) {
buildHTMLActivityProgress.panic(e)
} catch (err) {
const prettyError = createErrorFromString(
err.stack,
`${htmlComponentRendererPath}.map`
)

const undefinedGlobal = extractUndefinedGlobal(err)

let id = `11339`

if (undefinedGlobal) {
id = `11340`
err.context.undefinedGlobal = undefinedGlobal
}

buildHTMLActivityProgress.panic({
id,
context: err.context,
error: prettyError,
})
} finally {
buildHTMLActivityProgress.end()
}
Expand Down
26 changes: 26 additions & 0 deletions packages/gatsby/src/utils/__tests__/extract-undefined-global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { extractUndefinedGlobal } from "../extract-undefined-global"

const globals = [
`window`,
`document`,
`localStorage`,
`navigator`,
`alert`,
`location`,
]

it.each(globals)(`extracts %s`, global => {
const extractedGlobal = extractUndefinedGlobal(
new ReferenceError(`${global} is not defined`)
)

expect(extractedGlobal).toEqual(global)
})

it(`returns an empty string if no known global found`, () => {
const extractedGlobal = extractUndefinedGlobal(
new ReferenceError(`foo is not defined`)
)

expect(extractedGlobal).toEqual(``)
})
14 changes: 14 additions & 0 deletions packages/gatsby/src/utils/extract-undefined-global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Extract undefined global variables used in server context from a reference error.
*/
export function extractUndefinedGlobal(error: ReferenceError): string {
const match = error.message.match(
/(window|document|localStorage|navigator|alert|location) is not defined/i
)

if (match && match[1]) {
return match[1]
}

return ``
}
61 changes: 41 additions & 20 deletions packages/gatsby/src/utils/worker/child/render-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ declare global {
}
}

// Best effort typing the shape of errors we might throw
interface IRenderHTMLError extends Error {
message: string
name: string
Expand Down Expand Up @@ -465,6 +464,18 @@ export interface ISlicePropsEntry {
hasChildren: boolean
}

interface IRenderSliceHTMLError extends Error {
message: string
name: string
code?: string
stack?: string
context?: {
sliceName?: string
sliceData: unknown
sliceProps: unknown
}
}

export async function renderSlices({
slices,
htmlComponentRendererPath,
Expand Down Expand Up @@ -495,25 +506,35 @@ export async function renderSlices({
const MAGIC_CHILDREN_STRING = `__DO_NOT_USE_OR_ELSE__`
const sliceData = await readSliceData(publicDir, slice.name)

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

// TODO always generate both for now
let index = 1
for (const htmlChunk of split) {
await ensureFileContent(
path.join(publicDir, `_gatsby`, `slices`, `${sliceId}-${index}.html`),
htmlChunk
)
index++
try {
const html = await htmlComponentRenderer.renderSlice({
slice,
staticQueryContext,
props: {
data: sliceData?.result?.data,
...(hasChildren ? { children: MAGIC_CHILDREN_STRING } : {}),
...props,
},
})
const split = html.split(MAGIC_CHILDREN_STRING)

// TODO always generate both for now
let index = 1
for (const htmlChunk of split) {
await ensureFileContent(
path.join(publicDir, `_gatsby`, `slices`, `${sliceId}-${index}.html`),
htmlChunk
)
index++
}
} catch (err) {
const renderSliceError: IRenderSliceHTMLError = err
renderSliceError.context = {
sliceName,
sliceData,
sliceProps: props,
}
throw renderSliceError
}
}
}

0 comments on commit f21b54f

Please sign in to comment.