Skip to content

Commit 4429681

Browse files
committed
feat: replace early hints with header link reading
1 parent bb83e3f commit 4429681

10 files changed

+62
-481
lines changed

lib/client.js

-2
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,6 @@ const MAX_AGE = Infinity;
6262
* @property {boolean} [redirectable=false] Set to `true` to allow podlet to respond with a redirect. You need to look for the redirect response from the podlet and return a redirect response to the browser yourself.
6363
* @property {import('./resource.js').RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
6464
* @property {import('./resource.js').RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
65-
* @property {boolean} [earlyHints=true]
6665
*/
6766

6867
export default class PodiumClient extends EventEmitter {
@@ -213,7 +212,6 @@ export default class PodiumClient extends EventEmitter {
213212
httpAgent: this.#options.httpAgent,
214213
includeBy: this.#options.includeBy,
215214
excludeBy: this.#options.excludeBy,
216-
earlyHints: true,
217215
...options,
218216
};
219217

lib/http-outgoing.js

+19-22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { PassThrough } from 'stream';
22
import assert from 'assert';
3-
import { toPreloadAssetObjects } from './utils.js';
3+
import { toPreloadAssetObjects, filterAssets } from './utils.js';
44

55
/**
66
* @typedef {object} PodiumClientHttpOutgoingOptions
@@ -70,7 +70,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
7070
#uri;
7171
#js;
7272
#css;
73-
#hintsReceived = false;
7473

7574
/**
7675
* @constructor
@@ -154,7 +153,9 @@ export default class PodletClientHttpOutgoing extends PassThrough {
154153

155154
get js() {
156155
// return the internal js value or, fallback to the manifest for backwards compatibility
157-
return this.#js || this.#manifest.js;
156+
return this.#js && this.#js.length
157+
? this.#js
158+
: filterAssets('content', this.#manifest.js);
158159
}
159160

160161
set js(value) {
@@ -163,7 +164,9 @@ export default class PodletClientHttpOutgoing extends PassThrough {
163164

164165
get css() {
165166
// return the internal css value or, fallback to the manifest for backwards compatibility
166-
return this.#css || this.#manifest.css;
167+
return this.#css && this.#css.length
168+
? this.#css
169+
: filterAssets('content', this.#manifest.css);
167170
}
168171

169172
set css(value) {
@@ -301,20 +304,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
301304
this.#redirect = value;
302305
}
303306

304-
get hintsReceived() {
305-
return this.#hintsReceived;
306-
}
307-
308-
set hintsReceived(value) {
309-
this.#hintsReceived = value;
310-
if (this.#hintsReceived) {
311-
this.#incoming?.hints?.addReceivedHint(this.#name, {
312-
js: this.js,
313-
css: this.css,
314-
});
315-
}
316-
}
317-
318307
/**
319308
* Whether the podlet can signal redirects to the layout.
320309
*
@@ -346,10 +335,18 @@ export default class PodletClientHttpOutgoing extends PassThrough {
346335
pushFallback() {
347336
// @ts-expect-error Internal property
348337
this.push(this.#manifest._fallback);
349-
// @ts-expect-error Internal property
350-
this.js = this.#manifest._js;
351-
// @ts-expect-error Internal property
352-
this.css = this.#manifest._css;
338+
this.js =
339+
// @ts-expect-error Internal property
340+
this.#manifest._js && this.#manifest._js.length
341+
? // @ts-expect-error Internal property
342+
filterAssets('fallback', this.#manifest._js)
343+
: filterAssets('fallback', this.#manifest.js);
344+
this.css =
345+
// @ts-expect-error Internal property
346+
this.#manifest._css && this.#manifest._css.length
347+
? // @ts-expect-error Internal property
348+
filterAssets('fallback', this.#manifest._css)
349+
: filterAssets('fallback', this.#manifest.css);
353350
this.push(null);
354351
this.#isFallback = true;
355352
// assume the hints from the podlet have failed and fallback assets will be used

lib/resolver.content.js

+19-48
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,13 @@ const UA_STRING = `${pkg.name} ${pkg.version}`;
2727
* @property {string} clientName
2828
* @property {import('./http.js').default} [http]
2929
* @property {import('abslog').AbstractLoggerOptions} [logger]
30-
* @property {boolean} [earlyHints]
3130
*/
3231

3332
export default class PodletClientContentResolver {
3433
#log;
3534
#metrics;
3635
#histogram;
3736
#http;
38-
#earlyHints;
3937

4038
/**
4139
* @constructor
@@ -46,8 +44,6 @@ export default class PodletClientContentResolver {
4644
this.#http = options.http || new HTTP();
4745
const name = options.clientName;
4846
this.#log = abslog(options.logger);
49-
this.#earlyHints =
50-
typeof options.earlyHints === 'boolean' ? options.earlyHints : true;
5147
this.#metrics = new Metrics();
5248
this.#histogram = this.#metrics.histogram({
5349
name: 'podium_client_resolver_content_resolve',
@@ -96,10 +92,7 @@ export default class PodletClientContentResolver {
9692
outgoing.pushFallback();
9793
outgoing.emit(
9894
'beforeStream',
99-
new Response({
100-
js: utils.filterAssets('fallback', outgoing.manifest.js),
101-
css: utils.filterAssets('fallback', outgoing.manifest.css),
102-
}),
95+
new Response({ js: outgoing.js, css: outgoing.css }),
10396
);
10497
return outgoing;
10598
}
@@ -119,10 +112,7 @@ export default class PodletClientContentResolver {
119112
outgoing.pushFallback();
120113
outgoing.emit(
121114
'beforeStream',
122-
new Response({
123-
js: utils.filterAssets('fallback', outgoing.manifest.js),
124-
css: utils.filterAssets('fallback', outgoing.manifest.css),
125-
}),
115+
new Response({ js: outgoing.js, css: outgoing.css }),
126116
);
127117
return outgoing;
128118
}
@@ -146,26 +136,6 @@ export default class PodletClientContentResolver {
146136
method: 'GET',
147137
query: outgoing.reqOptions.query,
148138
headers,
149-
onInfo: ({ statusCode, headers }) => {
150-
if (statusCode === 103 && !outgoing.hintsReceived) {
151-
const parsedAssetObjects = parseLinkHeaders(headers.link);
152-
153-
const scriptObjects = parsedAssetObjects.filter(
154-
(asset) => asset instanceof AssetJs,
155-
);
156-
const styleObjects = parsedAssetObjects.filter(
157-
(asset) => asset instanceof AssetCss,
158-
);
159-
// set the content js asset objects
160-
outgoing.js = filterAssets('content', scriptObjects);
161-
// set the content css asset objects
162-
outgoing.css = filterAssets('content', styleObjects);
163-
// write the early hints to the browser
164-
if (this.#earlyHints) outgoing.writeEarlyHints();
165-
166-
outgoing.hintsReceived = true;
167-
}
168-
},
169139
};
170140

171141
if (outgoing.redirectable) {
@@ -189,6 +159,19 @@ export default class PodletClientContentResolver {
189159
body,
190160
} = await this.#http.request(uri, reqOptions);
191161

162+
const parsedAssetObjects = parseLinkHeaders(hdrs.link);
163+
164+
const scriptObjects = parsedAssetObjects.filter(
165+
(asset) => asset instanceof AssetJs,
166+
);
167+
const styleObjects = parsedAssetObjects.filter(
168+
(asset) => asset instanceof AssetCss,
169+
);
170+
// set the content js asset objects
171+
outgoing.js = filterAssets('content', scriptObjects);
172+
// set the content css asset objects
173+
outgoing.css = filterAssets('content', styleObjects);
174+
192175
// Remote responds but with an http error code
193176
const resError = statusCode >= 400;
194177
if (resError && outgoing.throwable) {
@@ -229,16 +212,7 @@ export default class PodletClientContentResolver {
229212
outgoing.pushFallback();
230213
outgoing.emit(
231214
'beforeStream',
232-
new Response({
233-
js: utils.filterAssets(
234-
'fallback',
235-
outgoing.manifest.js,
236-
),
237-
css: utils.filterAssets(
238-
'fallback',
239-
outgoing.manifest.css,
240-
),
241-
}),
215+
new Response({ js: outgoing.js, css: outgoing.css }),
242216
);
243217

244218
// Body must be consumed; https://github.com/nodejs/undici/issues/583#issuecomment-855384858
@@ -287,8 +261,8 @@ export default class PodletClientContentResolver {
287261
'beforeStream',
288262
new Response({
289263
headers: outgoing.headers,
290-
js: utils.filterAssets('content', outgoing.manifest.js),
291-
css: utils.filterAssets('content', outgoing.manifest.css),
264+
js: outgoing.js,
265+
css: outgoing.css,
292266
redirect: outgoing.redirect,
293267
}),
294268
);
@@ -328,10 +302,7 @@ export default class PodletClientContentResolver {
328302
outgoing.pushFallback();
329303
outgoing.emit(
330304
'beforeStream',
331-
new Response({
332-
js: utils.filterAssets('fallback', outgoing.js),
333-
css: utils.filterAssets('fallback', outgoing.css),
334-
}),
305+
new Response({ js: outgoing.js, css: outgoing.css }),
335306
);
336307

337308
return outgoing;

lib/resolver.fallback.js

+19-31
Original file line numberDiff line numberDiff line change
@@ -96,40 +96,12 @@ export default class PodletClientFallbackResolver {
9696
'User-Agent': UA_STRING,
9797
};
9898

99-
let hintsReceived = false;
100-
10199
/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
102100
const reqOptions = {
103101
rejectUnauthorized: outgoing.rejectUnauthorized,
104102
timeout: outgoing.timeout,
105103
method: 'GET',
106104
headers,
107-
onInfo({ statusCode, headers }) {
108-
if (statusCode === 103 && !hintsReceived) {
109-
const parsedAssetObjects = parseLinkHeaders(headers.link);
110-
111-
const scriptObjects = parsedAssetObjects.filter(
112-
(asset) => asset instanceof AssetJs,
113-
);
114-
const styleObjects = parsedAssetObjects.filter(
115-
(asset) => asset instanceof AssetCss,
116-
);
117-
// set the content js asset fallback objects
118-
// @ts-expect-error internal property
119-
outgoing.manifest._js = filterAssets(
120-
'fallback',
121-
scriptObjects,
122-
);
123-
// set the fallback css asset fallback objects
124-
// @ts-expect-error internal property
125-
outgoing.manifest._css = filterAssets(
126-
'fallback',
127-
styleObjects,
128-
);
129-
130-
hintsReceived = true;
131-
}
132-
},
133105
};
134106

135107
const timer = this.#histogram.timer({
@@ -142,10 +114,26 @@ export default class PodletClientFallbackResolver {
142114
this.#log.debug(
143115
`start reading fallback content from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
144116
);
145-
const { statusCode, body } = await this.#http.request(
146-
outgoing.fallbackUri,
147-
reqOptions,
117+
const {
118+
statusCode,
119+
body,
120+
headers: resHeaders,
121+
} = await this.#http.request(outgoing.fallbackUri, reqOptions);
122+
123+
const parsedAssetObjects = parseLinkHeaders(resHeaders.link);
124+
125+
const scriptObjects = parsedAssetObjects.filter(
126+
(asset) => asset instanceof AssetJs,
127+
);
128+
const styleObjects = parsedAssetObjects.filter(
129+
(asset) => asset instanceof AssetCss,
148130
);
131+
// set the content js asset fallback objects
132+
// @ts-expect-error internal property
133+
outgoing.manifest._js = filterAssets('fallback', scriptObjects);
134+
// set the fallback css asset fallback objects
135+
// @ts-expect-error internal property
136+
outgoing.manifest._css = filterAssets('fallback', styleObjects);
149137

150138
// Remote responds but with an http error code
151139
const resError = statusCode !== 200;

lib/resource.js

+3-14
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,6 @@ export default class PodiumClientResource {
105105
throw new TypeError(
106106
'you must pass an instance of "HttpIncoming" as the first argument to the .fetch() method',
107107
);
108-
// add the name of this resource as expecting a hint to be received
109-
// we use this to track across resources and emit a hint completion event once
110-
// all hints from all resources have been received.
111-
incoming.hints.addExpectedHint(this.#options.name);
112108
const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming);
113109

114110
if (this.#options.excludeBy) {
@@ -163,8 +159,7 @@ export default class PodiumClientResource {
163159

164160
this.#state.setInitializingState();
165161

166-
const { headers, redirect, isFallback } =
167-
await this.#resolver.resolve(outgoing);
162+
const { headers, redirect } = await this.#resolver.resolve(outgoing);
168163

169164
const chunks = [];
170165

@@ -179,14 +174,8 @@ export default class PodiumClientResource {
179174
return new Response({
180175
headers,
181176
content,
182-
css: utils.filterAssets(
183-
isFallback ? 'fallback' : 'content',
184-
outgoing.css,
185-
),
186-
js: utils.filterAssets(
187-
isFallback ? 'fallback' : 'content',
188-
outgoing.js,
189-
),
177+
css: outgoing.css,
178+
js: outgoing.js,
190179
redirect,
191180
});
192181
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"undici": "6.19.8"
4949
},
5050
"devDependencies": {
51-
"@podium/test-utils": "3.1.0-next.3",
51+
"@podium/test-utils": "3.1.0-next.5",
5252
"@semantic-release/changelog": "6.0.3",
5353
"@semantic-release/git": "10.0.1",
5454
"@semantic-release/github": "10.0.6",

0 commit comments

Comments
 (0)