diff --git a/e2e-tests/development-runtime/SHOULD_NOT_SERVE b/e2e-tests/development-runtime/SHOULD_NOT_SERVE index 73068df3213cb..48b27a60e1401 100644 --- a/e2e-tests/development-runtime/SHOULD_NOT_SERVE +++ b/e2e-tests/development-runtime/SHOULD_NOT_SERVE @@ -1 +1 @@ -this file shouldn't be allowed to be served +this file shouldn't be allowed to be served. CYPRESS-MARKER diff --git a/e2e-tests/development-runtime/cypress/integration/hot-reloading/error-handling/overlay-endpoints.js b/e2e-tests/development-runtime/cypress/integration/hot-reloading/error-handling/overlay-endpoints.js new file mode 100644 index 0000000000000..16ced0ed57a7d --- /dev/null +++ b/e2e-tests/development-runtime/cypress/integration/hot-reloading/error-handling/overlay-endpoints.js @@ -0,0 +1,20 @@ +const cwd = Cypress.config(`projectRoot`) + +describe(`overlay handlers don't serve unrelated files`, () => { + it(`__file-code-frame`, () => { + cy.request( + `__file-code-frame?filePath=${cwd}/SHOULD_NOT_SERVE&lineNumber=0` + ).should(response => { + expect(response.body.codeFrame).not.to.match(/CYPRESS-MARKER/) + }) + }) + + it(`__original-stack-frame`, () => { + cy.request( + `__original-stack-frame?moduleId=${cwd}/SHOULD_NOT_SERVE&lineNumber=0&skipSourceMap=1` + ).should(response => { + expect(response.body.codeFrame).not.to.match(/CYPRESS-MARKER/) + expect(response.body.sourceContent).not.to.match(/CYPRESS-MARKER/) + }) + }) +}) diff --git a/packages/gatsby/src/commands/build-html.ts b/packages/gatsby/src/commands/build-html.ts index 85b061f4a65d0..3d5081577b4eb 100644 --- a/packages/gatsby/src/commands/build-html.ts +++ b/packages/gatsby/src/commands/build-html.ts @@ -26,6 +26,7 @@ import type { ISlicePropsEntry } from "../utils/worker/child/render-html" import { getPageMode } from "../utils/page-mode" import { extractUndefinedGlobal } from "../utils/extract-undefined-global" import { modifyPageDataForErrorMessage } from "../utils/page-data" +import { setFilesFromDevelopHtmlCompilation } from "../utils/webpack/utils/is-file-inside-compilations" type IActivity = any // TODO @@ -218,6 +219,10 @@ const doBuildRenderer = async ( ) } + if (stage === `develop-html`) { + setFilesFromDevelopHtmlCompilation(stats.compilation) + } + // render-page.js is hard coded in webpack.config return { rendererPath: `${directory}/${ROUTES_DIRECTORY}render-page.js`, diff --git a/packages/gatsby/src/utils/start-server.ts b/packages/gatsby/src/utils/start-server.ts index f1e6661c63bee..f1cec341ed901 100644 --- a/packages/gatsby/src/utils/start-server.ts +++ b/packages/gatsby/src/utils/start-server.ts @@ -1,7 +1,7 @@ import webpackHotMiddleware from "@gatsbyjs/webpack-hot-middleware" import webpackDevMiddleware from "webpack-dev-middleware" import got, { Method } from "got" -import webpack from "webpack" +import webpack, { Compilation } from "webpack" import express from "express" import compression from "compression" import { createHandler as createGraphqlEndpointHandler } from "graphql-http/lib/use/express" @@ -50,6 +50,7 @@ import { getPageMode } from "./page-mode" import { configureTrailingSlash } from "./express-middlewares" import type { Express } from "express" import { addImageRoutes } from "gatsby-plugin-utils/polyfill-remote-file" +import { isFileInsideCompilations } from "./webpack/utils/is-file-inside-compilations" type ActivityTracker = any // TODO: Replace this with proper type once reporter is typed @@ -413,6 +414,19 @@ export async function startServer( store.getState().program.directory, req.query.moduleId as string ) + + const compilation: Compilation = + res.locals?.webpack?.devMiddleware?.stats?.compilation + if (!compilation) { + res.json(emptyResponse) + return + } + + if (!isFileInsideCompilations(absolutePath, compilation)) { + res.json(emptyResponse) + return + } + try { sourceContent = fs.readFileSync(absolutePath, `utf-8`) } catch (e) { @@ -540,7 +554,24 @@ export async function startServer( return } - const sourceContent = await fs.readFile(filePath, `utf-8`) + const absolutePath = path.resolve( + store.getState().program.directory, + filePath + ) + + const compilation: Compilation = + res.locals?.webpack?.devMiddleware?.stats?.compilation + if (!compilation) { + res.json(emptyResponse) + return + } + + if (!isFileInsideCompilations(absolutePath, compilation)) { + res.json(emptyResponse) + return + } + + const sourceContent = await fs.readFile(absolutePath, `utf-8`) const codeFrame = codeFrameColumns( sourceContent, diff --git a/packages/gatsby/src/utils/webpack/utils/is-file-inside-compilations.ts b/packages/gatsby/src/utils/webpack/utils/is-file-inside-compilations.ts new file mode 100644 index 0000000000000..10fd470dac75e --- /dev/null +++ b/packages/gatsby/src/utils/webpack/utils/is-file-inside-compilations.ts @@ -0,0 +1,42 @@ +import { Compilation, NormalModule } from "webpack" + +const filesInsideDevelopHtmlCompilation = new Set() + +function removeQueryParams(path: string): string { + return path.split(`?`)[0] +} + +export function setFilesFromDevelopHtmlCompilation( + developHtmlCompilation: Compilation +): void { + filesInsideDevelopHtmlCompilation.clear() + + for (const module of developHtmlCompilation.modules) { + if (module instanceof NormalModule && module.resource) { + filesInsideDevelopHtmlCompilation.add(removeQueryParams(module.resource)) + } + } +} + +/** + * Checks if a file is inside either `develop` or `develop-html` compilation. Used to determine if + * we should generate codeframe for this file for error overlay. + */ +export function isFileInsideCompilations( + absolutePath: string, + developBrowserCompilation: Compilation +): boolean { + if (filesInsideDevelopHtmlCompilation.has(absolutePath)) { + return true + } + + for (const module of developBrowserCompilation.modules) { + if (module instanceof NormalModule && module.resource) { + if (absolutePath === removeQueryParams(module.resource)) { + return true + } + } + } + + return false +}