Skip to content

Commit fdda3a1

Browse files
marvinjudepieh
andauthored
perf(gatsby,gatsby-plugin-gatsby-cloud): prioritize content/asset fetching early, drop script preloads so js doesn't compete with assets (#35408)
Co-authored-by: Michal Piechowiak <[email protected]>
1 parent f4a7ca5 commit fdda3a1

File tree

13 files changed

+95
-233
lines changed

13 files changed

+95
-233
lines changed

e2e-tests/path-prefix/cypress/integration/asset-prefix.js

-4
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@ describe(`assetPrefix`, () => {
1111
})
1212

1313
describe(`runtime`, () => {
14-
it(`prefixes preloads`, () => {
15-
assetPrefixMatcher(cy.get(`head link[rel="preload"]`))
16-
})
17-
1814
it(`prefixes styles`, () => {
1915
assetPrefixMatcher(cy.get(`head style[data-href]`), `data-href`)
2016
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
describe(`Preloads`, () => {
2+
it(`should not have preloads in head`, () => {
3+
cy.visit(`/`).waitForRouteChange()
4+
cy.get(`head link[rel="preload"]`).should("not.exist")
5+
})
6+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
describe(`Preloads`, () => {
2+
it(`should not have page scripts in HTML`, () => {
3+
cy.visit(`/`).waitForRouteChange()
4+
cy.get(`body script`).each(script => {
5+
cy.wrap(script).should(`not.have.attr`, `src`, /component---/)
6+
})
7+
})
8+
})

integration-tests/artifacts/__tests__/index.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ describe(`First run (baseline)`, () => {
533533
)
534534
})
535535

536-
describe(`should add <link> for webpack's magic comments`, () => {
536+
describe(`should add <link> for webpack's magic comments inside "app" bundle`, () => {
537537
let htmlContent
538538
beforeAll(() => {
539539
htmlContent = fs.readFileSync(
@@ -547,14 +547,26 @@ describe(`First run (baseline)`, () => {
547547
)
548548
})
549549

550-
it(`has prefetch link`, () => {
550+
it(`has prefetch link (imported in "app")`, () => {
551551
expect(htmlContent).toMatch(
552-
/<link\s+as="script"\s+rel="prefetch"\s+href="\/magic-comment-prefetch-\w+.js"\s*\/>/g
552+
/<link\s+as="script"\s+rel="prefetch"\s+href="\/magic-comment-app-prefetch-\w+.js"\s*\/>/g
553553
)
554554
})
555555

556-
it(`has preload link`, () => {
556+
it(`has preload link (imported in "app")`, () => {
557557
expect(htmlContent).toMatch(
558+
/<link\s+as="script"\s+rel="preload"\s+href="\/magic-comment-app-preload-\w+.js"\s*\/>/g
559+
)
560+
})
561+
562+
it(`doesn't have prefetch link (imported in template)`, () => {
563+
expect(htmlContent).not.toMatch(
564+
/<link\s+as="script"\s+rel="prefetch"\s+href="\/magic-comment-prefetch-\w+.js"\s*\/>/g
565+
)
566+
})
567+
568+
it(`doesn't have preload link (imported in template)`, () => {
569+
expect(htmlContent).not.toMatch(
558570
/<link\s+as="script"\s+rel="preload"\s+href="\/magic-comment-preload-\w+.js"\s*\/>/g
559571
)
560572
})

integration-tests/artifacts/gatsby-browser.js

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ const Github = require(`./src/components/github`).default
66
// TODO: Uncomment imported.css to test issue https://github.com/gatsbyjs/gatsby/issues/33450
77
// require("./imported.css")
88

9+
import(
10+
/* webpackChunkName: "magic-comment-app-prefetch", webpackPrefetch: true */ `./src/components/magic-comments/app-prefetch`
11+
).then(moduleForPrefetch => {
12+
console.log({ forPrefetch: moduleForPrefetch.forPrefetch() })
13+
})
14+
15+
import(
16+
/* webpackChunkName: "magic-comment-app-preload", webpackPreload: true */ `./src/components/magic-comments/app-preload`
17+
).then(moduleForPreload => {
18+
console.log({ forPreload: moduleForPreload.forPreload() })
19+
})
20+
921
exports.wrapRootElement = ({ element }) => {
1022
return (
1123
<>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function forAppPrefetch() {
2+
return `export-for-app-prefetch`
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function forAppPreload() {
2+
return `export-for-app-preload`
3+
}

packages/gatsby-plugin-gatsby-cloud/src/__tests__/__snapshots__/build-headers-program.js.snap

+5-5
Large diffs are not rendered by default.

packages/gatsby-plugin-gatsby-cloud/src/__tests__/build-headers-program.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,8 @@ describe(`build-headers-program`, () => {
218218
`utf8`
219219
)
220220
expect(output).toMatchSnapshot()
221-
expect(output).toMatch(/app-data\.json/)
222-
expect(output).toMatch(/page-data\.json/)
223-
// we should only check app-data once which leads to 1 time
224-
expect(fs.existsSync).toBeCalledTimes(1)
221+
expect(output).not.toMatch(/app-data\.json/)
222+
expect(output).not.toMatch(/page-data\.json/)
225223
})
226224

227225
it(`with manifest['pages-manifest']`, async () => {
@@ -254,7 +252,7 @@ describe(`build-headers-program`, () => {
254252
expect(output).toMatchSnapshot()
255253
expect(output).toMatch(/\/pages-manifest-ab11f09e0ca7ecd3b43e\.js/g)
256254
expect(output).not.toMatch(/\/app-data\.json/g)
257-
expect(output).toMatch(/\/page-data\.json/g)
255+
expect(output).not.toMatch(/\/page-data\.json/g)
258256
expect(output).not.toMatch(/\/undefined/g)
259257
})
260258

packages/gatsby-plugin-gatsby-cloud/src/build-headers-program.js

-162
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import _ from "lodash"
22
import { createWriteStream, existsSync } from "fs-extra"
3-
import { parse, posix } from "path"
4-
import kebabHash from "kebab-hash"
5-
import { fixedPagePath } from "gatsby-core-utils"
63
import { IMMUTABLE_CACHING_HEADER } from "./constants"
74

85
import {
9-
COMMON_BUNDLES,
106
SECURITY_HEADERS,
117
CACHING_HEADERS,
128
LINK_REGEX,
139
HEADERS_FILENAME,
14-
PAGE_DATA_DIR,
1510
} from "./constants"
1611
import { emitHeaders } from "./ipc"
1712

@@ -20,131 +15,10 @@ function getHeaderName(header) {
2015
return matches && matches[1]
2116
}
2217

23-
function validHeaders(headers, reporter) {
24-
if (!headers || !_.isObject(headers)) {
25-
return false
26-
}
27-
28-
return _.every(
29-
headers,
30-
(headersList, path) =>
31-
_.isArray(headersList) &&
32-
_.every(headersList, header => {
33-
if (_.isString(header)) {
34-
if (!getHeaderName(header)) {
35-
// TODO panic on builds on v3
36-
reporter.warn(
37-
`[gatsby-plugin-gatsby-cloud] ${path} contains an invalid header (${header}). Please check your plugin configuration`
38-
)
39-
}
40-
41-
return true
42-
}
43-
44-
return false
45-
})
46-
)
47-
}
48-
49-
function linkTemplate(assetPath, type = `script`) {
50-
return `Link: <${assetPath}>; rel=preload; as=${type}${
51-
type === `fetch` ? `; crossorigin` : ``
52-
}; nopush`
53-
}
54-
55-
function pathChunkName(path) {
56-
const name = path === `/` ? `index` : kebabHash(path)
57-
return `path---${name}`
58-
}
59-
60-
function getPageDataPath(path) {
61-
return posix.join(`page-data`, fixedPagePath(path), `page-data.json`)
62-
}
63-
64-
function getScriptPath(file, manifest) {
65-
const chunk = manifest[file]
66-
67-
if (!chunk) {
68-
return []
69-
}
70-
71-
// convert to array if it's not already
72-
const chunks = _.isArray(chunk) ? chunk : [chunk]
73-
74-
return chunks.filter(script => {
75-
const parsed = parse(script)
76-
// handle only .js, .css content is inlined already
77-
// and doesn't need to be pushed
78-
return parsed.ext === `.js`
79-
})
80-
}
81-
82-
function linkHeaders(files, pathPrefix, assetPrefix) {
83-
const linkHeaders = []
84-
for (const resourceType in files) {
85-
files[resourceType].forEach(file => {
86-
linkHeaders.push(
87-
linkTemplate(
88-
`${assetPrefix ? assetPrefix + `/` : ``}${pathPrefix}/${file}`,
89-
resourceType
90-
)
91-
)
92-
})
93-
}
94-
95-
return linkHeaders
96-
}
97-
9818
function headersPath(pathPrefix, path) {
9919
return `${pathPrefix}${path}`
10020
}
10121

102-
function preloadHeadersByPage({
103-
pages,
104-
manifest,
105-
pathPrefix,
106-
publicFolder,
107-
assetPrefix,
108-
}) {
109-
const linksByPage = {}
110-
111-
const appDataPath = publicFolder(PAGE_DATA_DIR, `app-data.json`)
112-
const hasAppData = existsSync(appDataPath)
113-
114-
pages.forEach(page => {
115-
const scripts = _.flatMap(COMMON_BUNDLES, file =>
116-
getScriptPath(file, manifest)
117-
)
118-
scripts.push(...getScriptPath(pathChunkName(page.path), manifest))
119-
scripts.push(...getScriptPath(page.componentChunkName, manifest))
120-
121-
const json = []
122-
if (hasAppData) {
123-
json.push(posix.join(PAGE_DATA_DIR, `app-data.json`))
124-
}
125-
126-
// page-data gets inline for SSR, so we won't be doing page-data request
127-
// and we shouldn't add preload link header for it.
128-
if (page.mode !== `SSR`) {
129-
json.push(getPageDataPath(page.path))
130-
}
131-
132-
const filesByResourceType = {
133-
script: scripts.filter(Boolean),
134-
fetch: json,
135-
}
136-
137-
const pathKey = headersPath(pathPrefix, page.path)
138-
linksByPage[pathKey] = linkHeaders(
139-
filesByResourceType,
140-
pathPrefix,
141-
assetPrefix
142-
)
143-
})
144-
145-
return linksByPage
146-
}
147-
14822
function defaultMerge(...headers) {
14923
function unionMerge(objValue, srcValue) {
15024
if (_.isArray(objValue)) {
@@ -200,21 +74,6 @@ function transformLink(manifest, publicFolder, pathPrefix) {
20074
})
20175
}
20276

203-
function stringifyHeaders(headers) {
204-
return _.reduce(
205-
headers,
206-
(text, headerList, path) => {
207-
const headersString = _.reduce(
208-
headerList,
209-
(accum, header) => `${accum} ${header}\n`,
210-
``
211-
)
212-
return `${text}${path}\n${headersString}`
213-
},
214-
``
215-
)
216-
}
217-
21877
// program methods
21978

22079
const mapUserLinkHeaders =
@@ -247,26 +106,6 @@ const mapUserLinkAllPageHeaders =
247106
return defaultMerge(headers, duplicateHeadersByPage)
248107
}
249108

250-
const applyLinkHeaders =
251-
(pluginData, { mergeLinkHeaders }) =>
252-
headers => {
253-
if (!mergeLinkHeaders) {
254-
return headers
255-
}
256-
257-
const { pages, manifest, pathPrefix, publicFolder, assetPrefix } =
258-
pluginData
259-
const perPageHeaders = preloadHeadersByPage({
260-
pages,
261-
manifest,
262-
pathPrefix,
263-
publicFolder,
264-
assetPrefix,
265-
})
266-
267-
return defaultMerge(headers, perPageHeaders)
268-
}
269-
270109
const applySecurityHeaders =
271110
({ mergeSecurityHeaders }) =>
272111
headers => {
@@ -371,7 +210,6 @@ export default function buildHeadersProgram(pluginData, pluginOptions) {
371210
applySecurityHeaders(pluginOptions),
372211
applyCachingHeaders(pluginData, pluginOptions),
373212
mapUserLinkAllPageHeaders(pluginData, pluginOptions),
374-
applyLinkHeaders(pluginData, pluginOptions),
375213
applyTransfromHeaders(pluginOptions),
376214
saveHeaders(pluginData)
377215
)(pluginOptions.headers)

packages/gatsby/cache-dir/__tests__/__snapshots__/static-entry.js.snap

+3-3
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,21 @@ exports[`develop-static-entry onPreRenderHTML can be used to replace preBodyComp
1414
1515
exports[`static-entry onPreRenderHTML can be used to replace headComponents 1`] = `
1616
Object {
17-
"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\\"/><link as=\\"fetch\\" rel=\\"preload\\" href=\\"/page-data/app-data.json\\" crossorigin=\\"anonymous\\"/><link as=\\"fetch\\" rel=\\"preload\\" href=\\"/page-data/about/page-data.json\\" crossorigin=\\"anonymous\\"/><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>",
17+
"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>",
1818
"unsafeBuiltinsUsage": Array [],
1919
}
2020
`;
2121
2222
exports[`static-entry onPreRenderHTML can be used to replace postBodyComponents 1`] = `
2323
Object {
24-
"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\\"/><link as=\\"fetch\\" rel=\\"preload\\" href=\\"/page-data/about/page-data.json\\" crossorigin=\\"anonymous\\"/><link as=\\"fetch\\" rel=\\"preload\\" href=\\"/page-data/app-data.json\\" crossorigin=\\"anonymous\\"/></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>",
24+
"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>",
2525
"unsafeBuiltinsUsage": Array [],
2626
}
2727
`;
2828
2929
exports[`static-entry onPreRenderHTML can be used to replace preBodyComponents 1`] = `
3030
Object {
31-
"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\\"/><link as=\\"fetch\\" rel=\\"preload\\" href=\\"/page-data/about/page-data.json\\" crossorigin=\\"anonymous\\"/><link as=\\"fetch\\" rel=\\"preload\\" href=\\"/page-data/app-data.json\\" crossorigin=\\"anonymous\\"/></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>",
31+
"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>",
3232
"unsafeBuiltinsUsage": Array [],
3333
}
3434
`;

0 commit comments

Comments
 (0)