Skip to content

Commit 4f06858

Browse files
committed
fix(document): adapt script loading (by proxy) for all edge cases
- preemptively pick up proxy for preload links - preemptively pick up async/defer variants that mext might insert during ISR - fix interleaving when proxfying scripts (...haveintegrity ...proxyfied ...have integrity ...)
1 parent 02ca36f commit 4f06858

File tree

11 files changed

+344
-296
lines changed

11 files changed

+344
-296
lines changed
Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
// eslint-disable-next-line @next/next/no-document-import-in-page
22
import { Head as NextHead } from "next/document";
3-
import { partition } from "ramda";
43
import React, { Fragment } from "react";
54
import { getExcludeList, isHashProxy } from "../cfg";
6-
import { isInlineScriptElement } from "../utils";
75
import { writeManifestToFileWithLock } from "./file-io";
8-
import { collectStyleElem, iterableScripts, pullManifest } from "./manifest";
6+
import { deepExtractStyleElemHashes, deepMapExtractScripts } from "./utils";
7+
import { collectStyleElem, pullManifest } from "./manifest";
98
import {
9+
createFragmentPaddedProxy,
10+
registerFragmentPaddedProxyForVariants,
11+
} from "./script-inlining";
12+
import {
13+
deepEnsureScriptElementsInManifest,
14+
deepMapScriptsToManifest,
1015
ensureNextPreloadLinksInManifest,
1116
ensureNextScriptsInManifest,
1217
loadNextByProxy,
18+
preNextScriptsByProxy,
1319
} from "./next-scripts";
14-
import { createTrustedLoadingProxy } from "./script-inlining";
15-
import {
16-
deepExtractStyleElemHashes,
17-
deepEnsureScriptElementsInManifest,
18-
deepExtractScripts,
19-
deepMapScriptsToManifest,
20-
} from "./utils";
2120

2221
export default class Head extends NextHead {
2322
private proxyfiedScripts: any[] = [];
@@ -29,7 +28,8 @@ export default class Head extends NextHead {
2928
return preloadScripts;
3029
}
3130
if (isHashProxy()) {
32-
return [];
31+
preloadScripts = createFragmentPaddedProxy(preloadScripts);
32+
return preloadScripts;
3333
}
3434
preloadScripts = ensureNextPreloadLinksInManifest(
3535
preloadScripts,
@@ -44,7 +44,8 @@ export default class Head extends NextHead {
4444
return preloadScripts;
4545
}
4646
if (isHashProxy()) {
47-
return [];
47+
preloadScripts = createFragmentPaddedProxy(preloadScripts);
48+
return preloadScripts;
4849
}
4950
preloadScripts = ensureNextPreloadLinksInManifest(
5051
preloadScripts,
@@ -59,7 +60,7 @@ export default class Head extends NextHead {
5960
if (getExcludeList().includes("scripts")) {
6061
return scripts;
6162
}
62-
scripts = deepMapScriptsToManifest(scripts);
63+
scripts = deepMapScriptsToManifest(scripts, "Head");
6364
return scripts;
6465
}
6566

@@ -70,17 +71,14 @@ export default class Head extends NextHead {
7071
return scripts;
7172
}
7273
if (isHashProxy()) {
73-
const scriptss = deepExtractScripts(scripts);
74-
const polyfillProxy = createTrustedLoadingProxy(scriptss);
75-
createTrustedLoadingProxy(
76-
scriptss.map((s) => React.cloneElement(s, { defer: false }))
77-
);
78-
return [polyfillProxy];
74+
const polyfillProxy = createFragmentPaddedProxy(scripts);
75+
registerFragmentPaddedProxyForVariants(scripts);
76+
return polyfillProxy;
7977
}
8078
return ensureNextScriptsInManifest(
8179
scripts,
82-
"Head",
83-
this.context.canonicalBase
80+
this.context.canonicalBase,
81+
"Head"
8482
);
8583
}
8684

@@ -95,23 +93,10 @@ export default class Head extends NextHead {
9593
}
9694
const isArray = Array.isArray(scripts);
9795
if (isHashProxy()) {
98-
let [inlineScripts, srcScripts] = partition(
99-
isInlineScriptElement,
100-
deepExtractScripts(scripts)
101-
);
102-
inlineScripts = deepMapScriptsToManifest(inlineScripts);
103-
const srcProxy = createTrustedLoadingProxy(srcScripts);
104-
createTrustedLoadingProxy(
105-
srcScripts.map((s) => React.cloneElement(s, { defer: false }))
106-
);
107-
inlineScripts = deepMapScriptsToManifest(inlineScripts);
108-
return isArray ? (
109-
[...inlineScripts, srcProxy]
110-
) : (
111-
<Fragment key={scripts.key}>{[...inlineScripts, srcProxy]}</Fragment>
112-
);
96+
scripts = preNextScriptsByProxy(scripts, "Head");
97+
return scripts;
11398
}
114-
scripts = deepMapScriptsToManifest(scripts);
99+
scripts = deepMapScriptsToManifest(scripts, "Head");
115100
return isArray ? scripts : <Fragment key={scripts.key}>{scripts}</Fragment>;
116101
}
117102

@@ -122,13 +107,13 @@ export default class Head extends NextHead {
122107
return scripts;
123108
}
124109
if (isHashProxy()) {
125-
this.proxyfiedScripts.push(...deepExtractScripts(scripts));
110+
this.proxyfiedScripts.push(...deepMapExtractScripts(scripts));
126111
return [];
127112
}
128113
return ensureNextScriptsInManifest(
129114
scripts,
130-
"Head",
131-
this.context.canonicalBase
115+
this.context.canonicalBase,
116+
"Head"
132117
);
133118
}
134119

@@ -138,14 +123,17 @@ export default class Head extends NextHead {
138123
if (getExcludeList().includes("scripts")) {
139124
return scripts;
140125
}
126+
// need to call preload links during build time to collect proxy hash
127+
this.getPreloadDynamicChunks();
128+
this.getPreloadMainLinks(files);
141129
if (isHashProxy()) {
142-
this.proxyfiedScripts.push(...deepExtractScripts(scripts));
143-
scripts = [loadNextByProxy(this.proxyfiedScripts, "Head")];
130+
this.proxyfiedScripts.push(...deepMapExtractScripts(scripts));
131+
scripts = loadNextByProxy(this.proxyfiedScripts, "Head");
144132
} else {
145133
scripts = ensureNextScriptsInManifest(
146134
scripts,
147-
"Head",
148135
this.context.canonicalBase,
136+
"Head",
149137
true
150138
);
151139
}
@@ -154,11 +142,17 @@ export default class Head extends NextHead {
154142
}
155143

156144
render() {
157-
deepEnsureScriptElementsInManifest(this.props.children, getExcludeList());
158-
collectStyleElem(
159-
...deepExtractStyleElemHashes(this.context.styles, getExcludeList()),
160-
...deepExtractStyleElemHashes(this.props.children, getExcludeList())
161-
);
145+
if (!getExcludeList().includes("scripts")) {
146+
deepEnsureScriptElementsInManifest(this.props.children);
147+
}
148+
149+
if (!getExcludeList().includes("styles")) {
150+
collectStyleElem(
151+
...deepExtractStyleElemHashes(this.context.styles),
152+
...deepExtractStyleElemHashes(this.props.children)
153+
);
154+
}
155+
162156
return super.render();
163157
}
164158
}

packages/next-safe-middleware/src/document/csp-trustify/hash/NextScript.tsx

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1+
import React, { Fragment } from "react";
12
import { NextScript as NextNextScript } from "next/document";
2-
import { ensureNextScriptsInManifest, loadNextByProxy } from "./next-scripts";
33
import { getExcludeList, isHashProxy } from "../cfg";
4+
import { deepMapExtractScripts } from "./utils";
5+
import { createFragmentPaddedProxy } from "./script-inlining";
46
import {
5-
deepExtractScripts,
7+
ensureNextScriptsInManifest,
8+
loadNextByProxy,
9+
preNextScriptsByProxy,
610
deepMapScriptsToManifest,
7-
} from "./utils";
8-
import { Fragment } from "react";
9-
import React from "react";
10-
import { partition } from "ramda";
11-
import { isInlineScriptElement } from "../utils";
12-
import { createTrustedLoadingProxy } from "./script-inlining";
11+
} from "./next-scripts";
1312

1413
export default class NextScript extends NextNextScript {
1514
private proxyfiedScripts: any[] = [];
@@ -21,16 +20,10 @@ export default class NextScript extends NextNextScript {
2120
return scripts;
2221
}
2322
if (isHashProxy()) {
24-
const polyfillProxy = createTrustedLoadingProxy(
25-
deepExtractScripts(scripts)
26-
);
27-
return [polyfillProxy];
23+
const polyfillProxy = createFragmentPaddedProxy(scripts);
24+
return polyfillProxy;
2825
}
29-
return ensureNextScriptsInManifest(
30-
scripts,
31-
"NextScript",
32-
this.context.canonicalBase
33-
);
26+
return ensureNextScriptsInManifest(scripts, this.context.canonicalBase);
3427
}
3528

3629
// this will return partytown init scripts
@@ -42,20 +35,11 @@ export default class NextScript extends NextNextScript {
4235
if (getExcludeList().includes("scripts")) {
4336
return scripts;
4437
}
45-
const isArray = Array.isArray(scripts);
4638
if (isHashProxy()) {
47-
let [inlineScripts, srcScripts] = partition(
48-
isInlineScriptElement,
49-
deepExtractScripts(scripts)
50-
);
51-
const srcProxy = createTrustedLoadingProxy(srcScripts);
52-
inlineScripts = deepMapScriptsToManifest(inlineScripts);
53-
return isArray ? (
54-
[...inlineScripts, srcProxy]
55-
) : (
56-
<Fragment key={scripts.key}>{[...inlineScripts, srcProxy]}</Fragment>
57-
);
39+
scripts = preNextScriptsByProxy(scripts);
40+
return scripts;
5841
}
42+
const isArray = Array.isArray(scripts);
5943
scripts = deepMapScriptsToManifest(scripts);
6044
return isArray ? scripts : <Fragment key={scripts.key}>{scripts}</Fragment>;
6145
}
@@ -67,14 +51,10 @@ export default class NextScript extends NextNextScript {
6751
return scripts;
6852
}
6953
if (isHashProxy()) {
70-
this.proxyfiedScripts.push(...deepExtractScripts(scripts));
54+
this.proxyfiedScripts.push(...deepMapExtractScripts(scripts));
7155
return [];
7256
}
73-
return ensureNextScriptsInManifest(
74-
scripts,
75-
"NextScript",
76-
this.context.canonicalBase
77-
);
57+
return ensureNextScriptsInManifest(scripts, this.context.canonicalBase);
7858
}
7959

8060
getScripts(files: any) {
@@ -85,12 +65,12 @@ export default class NextScript extends NextNextScript {
8565
}
8666
if (isHashProxy()) {
8767
this.proxyfiedScripts.push(...scripts);
88-
scripts = [loadNextByProxy(this.proxyfiedScripts, "NextScript")];
68+
scripts = loadNextByProxy(this.proxyfiedScripts);
8969
} else {
9070
scripts = ensureNextScriptsInManifest(
9171
scripts,
92-
"NextScript",
9372
this.context.canonicalBase,
73+
undefined,
9474
true
9575
);
9676
}

packages/next-safe-middleware/src/document/csp-trustify/hash/file-io.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export const writeManifestToFileWithLock = (manifest: CspManifest) => {
6565
});
6666
};
6767

68+
const dotNextcontentCache: Record<string, string> = {};
6869
export const readURIFromDotNextFolder = (
6970
URI: string,
7071
basePath?: string
@@ -73,6 +74,10 @@ export const readURIFromDotNextFolder = (
7374
`${basePath || ""}/_next`,
7475
dotNextFolder()
7576
);
77+
let content = dotNextcontentCache[filePath];
78+
if (content) {
79+
return content;
80+
}
7681
const fs = getFs();
7782
const assert = fs && fs.existsSync(filePath);
7883
console.assert(assert, "readURIFromDotNextFolder: file does not exist", {
@@ -83,5 +88,7 @@ export const readURIFromDotNextFolder = (
8388
return "";
8489
}
8590

86-
return fs.readFileSync(filePath, "utf8");
91+
content = fs.readFileSync(filePath, "utf8");
92+
dotNextcontentCache[filePath] = content;
93+
return content;
8794
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export { hash, setHashAlgorithm } from "./algorithm";
22
export * from "./manifest";
33
export * from "./utils";
4+
export { deepEnsureScriptElementsInManifest } from "./next-scripts"
45
export { default as HashHead } from "./Head";
56
export { default as HashNextScript } from "./NextScript";

packages/next-safe-middleware/src/document/csp-trustify/hash/manifest.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { difference } from "ramda";
2-
2+
import type { CspManifest } from "../../../types";
3+
import type { Nullable, IterableScript } from "./types";
34
import {
45
getScriptValue,
56
iterableScriptFromProps,
67
sortIterableScriptByAttr,
78
} from "./script-inlining";
8-
import { CspManifest } from "../../../types";
9-
import { Nullable, IterableScript } from "./types";
109

1110
export let iterableScripts: IterableScript[] = [];
1211

0 commit comments

Comments
 (0)