-
Notifications
You must be signed in to change notification settings - Fork 153
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14796 from artsy/damassi/feat/early-hints-plugin
feat(early-hints): Add support for emitting webpack .js early hint headers and passing via linkHeadersMiddleware
- Loading branch information
Showing
10 changed files
with
166 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import fs from "fs" | ||
import path from "path" | ||
import { getWebpackEarlyHints } from "Server/getWebpackEarlyHints" | ||
|
||
jest.mock("fs") | ||
jest.mock("Server/config", () => ({ CDN_URL: "https://cdn.example.com" })) | ||
|
||
const HINTS_PATH = path.join(process.cwd(), "public/assets", "early-hints.json") | ||
|
||
describe("getWebpackEarlyHints", () => { | ||
const mockReadFileSync = fs.readFileSync as jest.Mock | ||
|
||
afterEach(() => { | ||
jest.clearAllMocks() | ||
}) | ||
|
||
it("should return link headers and preload tags with CDN URL in production", () => { | ||
process.env.NODE_ENV = "production" | ||
const mockChunkFiles = ["/chunk1.js", "/chunk2.js"] | ||
|
||
mockReadFileSync.mockReturnValueOnce(JSON.stringify(mockChunkFiles)) | ||
|
||
const result = getWebpackEarlyHints() | ||
|
||
expect(fs.readFileSync).toHaveBeenCalledWith(HINTS_PATH, "utf-8") | ||
expect(result.linkHeaders).toEqual([ | ||
`<https://cdn.example.com/chunk1.js>; rel=preload; as=script; crossorigin`, | ||
`<https://cdn.example.com/chunk2.js>; rel=preload; as=script; crossorigin`, | ||
]) | ||
expect(result.linkPreloadTags).toEqual([ | ||
`<link rel="preload" as="script" href="https://cdn.example.com/chunk1.js" crossorigin>`, | ||
`<link rel="preload" as="script" href="https://cdn.example.com/chunk2.js" crossorigin>`, | ||
]) | ||
}) | ||
|
||
it("should return link headers and preload tags without CDN URL in development", () => { | ||
process.env.NODE_ENV = "development" | ||
const mockChunkFiles = ["/chunk1.js", "/chunk2.js"] | ||
|
||
mockReadFileSync.mockReturnValueOnce(JSON.stringify(mockChunkFiles)) | ||
|
||
const result = getWebpackEarlyHints() | ||
|
||
expect(fs.readFileSync).toHaveBeenCalledWith(HINTS_PATH, "utf-8") | ||
expect(result.linkHeaders).toEqual([ | ||
`</chunk1.js>; rel=preload; as=script; crossorigin`, | ||
`</chunk2.js>; rel=preload; as=script; crossorigin`, | ||
]) | ||
expect(result.linkPreloadTags).toEqual([ | ||
`<link rel="preload" as="script" href="/chunk1.js" crossorigin>`, | ||
`<link rel="preload" as="script" href="/chunk2.js" crossorigin>`, | ||
]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { CDN_URL } from "Server/config" | ||
import path from "path" | ||
import fs from "fs" | ||
|
||
const HINTS_PATH = path.join(process.cwd(), "public/assets", "early-hints.json") | ||
|
||
export const getWebpackEarlyHints = (): { | ||
linkHeaders: string[] | ||
linkPreloadTags: string[] | ||
} => { | ||
let chunkFiles | ||
|
||
try { | ||
chunkFiles = JSON.parse(fs.readFileSync(HINTS_PATH, "utf-8")) | ||
} catch (error) { | ||
console.error( | ||
"[getWebpackEarlyHints] Could not load webpack early-hints.json:", | ||
error | ||
) | ||
|
||
return { | ||
linkHeaders: [], | ||
linkPreloadTags: [], | ||
} | ||
} | ||
|
||
const cdnUrl = (() => { | ||
if (process.env.NODE_ENV === "development") { | ||
return "" | ||
} | ||
|
||
return CDN_URL | ||
})() | ||
|
||
const links = chunkFiles.reduce( | ||
(acc, file) => { | ||
acc.linkHeaders.push( | ||
`<${cdnUrl}${file}>; rel=preload; as=script; crossorigin` | ||
) | ||
acc.linkPreloadTags.push( | ||
`<link rel="preload" as="script" href="${cdnUrl}${file}" crossorigin>` | ||
) | ||
return acc | ||
}, | ||
{ | ||
linkHeaders: [], | ||
linkPreloadTags: [], | ||
} | ||
) | ||
|
||
return links | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
// @ts-check | ||
|
||
import webpack from "webpack" | ||
|
||
/** | ||
* This plugin generates a JSON file with the list of entry chunk files to | ||
* be used by the server to send early hints to the client. | ||
*/ | ||
|
||
export class EarlyHintsPlugin { | ||
/** | ||
* @param {webpack.Compiler} compiler | ||
*/ | ||
apply(compiler) { | ||
compiler.hooks.thisCompilation.tap("EarlyHintPlugin", compilation => { | ||
compilation.hooks.processAssets.tap( | ||
{ | ||
name: "EarlyHintPlugin", | ||
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, | ||
}, | ||
assets => { | ||
const publicPath = compilation.outputOptions.publicPath || "" | ||
|
||
/** | ||
* Collect entry chunk files (JavaScript files only) | ||
* @type {string[]} | ||
*/ | ||
const entryChunkFiles = Array.from(compilation.chunks) | ||
.filter(chunk => chunk.canBeInitial()) // Select only entry chunks | ||
.reduce((acc, chunk) => { | ||
const jsFiles = Array.from(chunk.files) | ||
.filter(file => file.endsWith(".js")) | ||
.map(file => `${publicPath}${file}`) | ||
return acc.concat(jsFiles) | ||
}, /** @type {string[]} */ ([])) | ||
|
||
// Output `early-hints.json` to webpack output/publicPath directory | ||
assets["early-hints.json"] = new webpack.sources.RawSource( | ||
JSON.stringify(entryChunkFiles), | ||
false | ||
) | ||
} | ||
) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5914,11 +5914,6 @@ | |
"@typescript-eslint/types" "4.9.1" | ||
eslint-visitor-keys "^2.0.0" | ||
|
||
"@vue/preload-webpack-plugin@^2.0.0": | ||
version "2.0.0" | ||
resolved "https://registry.yarnpkg.com/@vue/preload-webpack-plugin/-/preload-webpack-plugin-2.0.0.tgz#a43bfc087e91f7d0efb0086100148f4b16437b68" | ||
integrity sha512-RoorRB50WehYbsiWu497q8egZBYlrvOo9KBUG41uth4O023Cbs+7POLm9uw2CAiViBAIhvpw1Y4w4i+MZxOfXw== | ||
|
||
"@webassemblyjs/[email protected]": | ||
version "1.11.1" | ||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" | ||
|