Skip to content

Commit

Permalink
feat: replace early hints with header link reading
Browse files Browse the repository at this point in the history
  • Loading branch information
digitalsadhu committed Oct 11, 2024
1 parent bb83e3f commit 4429681
Show file tree
Hide file tree
Showing 10 changed files with 62 additions and 481 deletions.
2 changes: 0 additions & 2 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const MAX_AGE = Infinity;
* @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.
* @property {import('./resource.js').RequestFilterOptions} [excludeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
* @property {import('./resource.js').RequestFilterOptions} [includeBy] Used by `fetch` to conditionally skip fetching the podlet content based on values on the request.
* @property {boolean} [earlyHints=true]
*/

export default class PodiumClient extends EventEmitter {
Expand Down Expand Up @@ -213,7 +212,6 @@ export default class PodiumClient extends EventEmitter {
httpAgent: this.#options.httpAgent,
includeBy: this.#options.includeBy,
excludeBy: this.#options.excludeBy,
earlyHints: true,
...options,
};

Expand Down
41 changes: 19 additions & 22 deletions lib/http-outgoing.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PassThrough } from 'stream';
import assert from 'assert';
import { toPreloadAssetObjects } from './utils.js';
import { toPreloadAssetObjects, filterAssets } from './utils.js';

/**
* @typedef {object} PodiumClientHttpOutgoingOptions
Expand Down Expand Up @@ -70,7 +70,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
#uri;
#js;
#css;
#hintsReceived = false;

/**
* @constructor
Expand Down Expand Up @@ -154,7 +153,9 @@ export default class PodletClientHttpOutgoing extends PassThrough {

get js() {
// return the internal js value or, fallback to the manifest for backwards compatibility
return this.#js || this.#manifest.js;
return this.#js && this.#js.length
? this.#js
: filterAssets('content', this.#manifest.js);
}

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

get css() {
// return the internal css value or, fallback to the manifest for backwards compatibility
return this.#css || this.#manifest.css;
return this.#css && this.#css.length
? this.#css
: filterAssets('content', this.#manifest.css);
}

set css(value) {
Expand Down Expand Up @@ -301,20 +304,6 @@ export default class PodletClientHttpOutgoing extends PassThrough {
this.#redirect = value;
}

get hintsReceived() {
return this.#hintsReceived;
}

set hintsReceived(value) {
this.#hintsReceived = value;
if (this.#hintsReceived) {
this.#incoming?.hints?.addReceivedHint(this.#name, {
js: this.js,
css: this.css,
});
}
}

/**
* Whether the podlet can signal redirects to the layout.
*
Expand Down Expand Up @@ -346,10 +335,18 @@ export default class PodletClientHttpOutgoing extends PassThrough {
pushFallback() {
// @ts-expect-error Internal property
this.push(this.#manifest._fallback);
// @ts-expect-error Internal property
this.js = this.#manifest._js;
// @ts-expect-error Internal property
this.css = this.#manifest._css;
this.js =
// @ts-expect-error Internal property
this.#manifest._js && this.#manifest._js.length
? // @ts-expect-error Internal property
filterAssets('fallback', this.#manifest._js)
: filterAssets('fallback', this.#manifest.js);
this.css =
// @ts-expect-error Internal property
this.#manifest._css && this.#manifest._css.length
? // @ts-expect-error Internal property
filterAssets('fallback', this.#manifest._css)
: filterAssets('fallback', this.#manifest.css);
this.push(null);
this.#isFallback = true;
// assume the hints from the podlet have failed and fallback assets will be used
Expand Down
67 changes: 19 additions & 48 deletions lib/resolver.content.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,13 @@ const UA_STRING = `${pkg.name} ${pkg.version}`;
* @property {string} clientName
* @property {import('./http.js').default} [http]
* @property {import('abslog').AbstractLoggerOptions} [logger]
* @property {boolean} [earlyHints]
*/

export default class PodletClientContentResolver {
#log;
#metrics;
#histogram;
#http;
#earlyHints;

/**
* @constructor
Expand All @@ -46,8 +44,6 @@ export default class PodletClientContentResolver {
this.#http = options.http || new HTTP();
const name = options.clientName;
this.#log = abslog(options.logger);
this.#earlyHints =
typeof options.earlyHints === 'boolean' ? options.earlyHints : true;
this.#metrics = new Metrics();
this.#histogram = this.#metrics.histogram({
name: 'podium_client_resolver_content_resolve',
Expand Down Expand Up @@ -96,10 +92,7 @@ export default class PodletClientContentResolver {
outgoing.pushFallback();
outgoing.emit(
'beforeStream',
new Response({
js: utils.filterAssets('fallback', outgoing.manifest.js),
css: utils.filterAssets('fallback', outgoing.manifest.css),
}),
new Response({ js: outgoing.js, css: outgoing.css }),
);
return outgoing;
}
Expand All @@ -119,10 +112,7 @@ export default class PodletClientContentResolver {
outgoing.pushFallback();
outgoing.emit(
'beforeStream',
new Response({
js: utils.filterAssets('fallback', outgoing.manifest.js),
css: utils.filterAssets('fallback', outgoing.manifest.css),
}),
new Response({ js: outgoing.js, css: outgoing.css }),
);
return outgoing;
}
Expand All @@ -146,26 +136,6 @@ export default class PodletClientContentResolver {
method: 'GET',
query: outgoing.reqOptions.query,
headers,
onInfo: ({ statusCode, headers }) => {
if (statusCode === 103 && !outgoing.hintsReceived) {
const parsedAssetObjects = parseLinkHeaders(headers.link);

const scriptObjects = parsedAssetObjects.filter(
(asset) => asset instanceof AssetJs,
);
const styleObjects = parsedAssetObjects.filter(
(asset) => asset instanceof AssetCss,
);
// set the content js asset objects
outgoing.js = filterAssets('content', scriptObjects);
// set the content css asset objects
outgoing.css = filterAssets('content', styleObjects);
// write the early hints to the browser
if (this.#earlyHints) outgoing.writeEarlyHints();

outgoing.hintsReceived = true;
}
},
};

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

const parsedAssetObjects = parseLinkHeaders(hdrs.link);

const scriptObjects = parsedAssetObjects.filter(
(asset) => asset instanceof AssetJs,
);
const styleObjects = parsedAssetObjects.filter(
(asset) => asset instanceof AssetCss,
);
// set the content js asset objects
outgoing.js = filterAssets('content', scriptObjects);
// set the content css asset objects
outgoing.css = filterAssets('content', styleObjects);

// Remote responds but with an http error code
const resError = statusCode >= 400;
if (resError && outgoing.throwable) {
Expand Down Expand Up @@ -229,16 +212,7 @@ export default class PodletClientContentResolver {
outgoing.pushFallback();
outgoing.emit(
'beforeStream',
new Response({
js: utils.filterAssets(
'fallback',
outgoing.manifest.js,
),
css: utils.filterAssets(
'fallback',
outgoing.manifest.css,
),
}),
new Response({ js: outgoing.js, css: outgoing.css }),
);

// Body must be consumed; https://github.com/nodejs/undici/issues/583#issuecomment-855384858
Expand Down Expand Up @@ -287,8 +261,8 @@ export default class PodletClientContentResolver {
'beforeStream',
new Response({
headers: outgoing.headers,
js: utils.filterAssets('content', outgoing.manifest.js),
css: utils.filterAssets('content', outgoing.manifest.css),
js: outgoing.js,
css: outgoing.css,
redirect: outgoing.redirect,
}),
);
Expand Down Expand Up @@ -328,10 +302,7 @@ export default class PodletClientContentResolver {
outgoing.pushFallback();
outgoing.emit(
'beforeStream',
new Response({
js: utils.filterAssets('fallback', outgoing.js),
css: utils.filterAssets('fallback', outgoing.css),
}),
new Response({ js: outgoing.js, css: outgoing.css }),
);

return outgoing;
Expand Down
50 changes: 19 additions & 31 deletions lib/resolver.fallback.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,40 +96,12 @@ export default class PodletClientFallbackResolver {
'User-Agent': UA_STRING,
};

let hintsReceived = false;

/** @type {import('./http.js').PodiumHttpClientRequestOptions} */
const reqOptions = {
rejectUnauthorized: outgoing.rejectUnauthorized,
timeout: outgoing.timeout,
method: 'GET',
headers,
onInfo({ statusCode, headers }) {
if (statusCode === 103 && !hintsReceived) {
const parsedAssetObjects = parseLinkHeaders(headers.link);

const scriptObjects = parsedAssetObjects.filter(
(asset) => asset instanceof AssetJs,
);
const styleObjects = parsedAssetObjects.filter(
(asset) => asset instanceof AssetCss,
);
// set the content js asset fallback objects
// @ts-expect-error internal property
outgoing.manifest._js = filterAssets(
'fallback',
scriptObjects,
);
// set the fallback css asset fallback objects
// @ts-expect-error internal property
outgoing.manifest._css = filterAssets(
'fallback',
styleObjects,
);

hintsReceived = true;
}
},
};

const timer = this.#histogram.timer({
Expand All @@ -142,10 +114,26 @@ export default class PodletClientFallbackResolver {
this.#log.debug(
`start reading fallback content from remote resource - resource: ${outgoing.name} - url: ${outgoing.fallbackUri}`,
);
const { statusCode, body } = await this.#http.request(
outgoing.fallbackUri,
reqOptions,
const {
statusCode,
body,
headers: resHeaders,
} = await this.#http.request(outgoing.fallbackUri, reqOptions);

const parsedAssetObjects = parseLinkHeaders(resHeaders.link);

const scriptObjects = parsedAssetObjects.filter(
(asset) => asset instanceof AssetJs,
);
const styleObjects = parsedAssetObjects.filter(
(asset) => asset instanceof AssetCss,
);
// set the content js asset fallback objects
// @ts-expect-error internal property
outgoing.manifest._js = filterAssets('fallback', scriptObjects);
// set the fallback css asset fallback objects
// @ts-expect-error internal property
outgoing.manifest._css = filterAssets('fallback', styleObjects);

// Remote responds but with an http error code
const resError = statusCode !== 200;
Expand Down
17 changes: 3 additions & 14 deletions lib/resource.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,6 @@ export default class PodiumClientResource {
throw new TypeError(
'you must pass an instance of "HttpIncoming" as the first argument to the .fetch() method',
);
// add the name of this resource as expecting a hint to be received
// we use this to track across resources and emit a hint completion event once
// all hints from all resources have been received.
incoming.hints.addExpectedHint(this.#options.name);
const outgoing = new HttpOutgoing(this.#options, reqOptions, incoming);

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

this.#state.setInitializingState();

const { headers, redirect, isFallback } =
await this.#resolver.resolve(outgoing);
const { headers, redirect } = await this.#resolver.resolve(outgoing);

const chunks = [];

Expand All @@ -179,14 +174,8 @@ export default class PodiumClientResource {
return new Response({
headers,
content,
css: utils.filterAssets(
isFallback ? 'fallback' : 'content',
outgoing.css,
),
js: utils.filterAssets(
isFallback ? 'fallback' : 'content',
outgoing.js,
),
css: outgoing.css,
js: outgoing.js,
redirect,
});
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"undici": "6.19.8"
},
"devDependencies": {
"@podium/test-utils": "3.1.0-next.3",
"@podium/test-utils": "3.1.0-next.5",
"@semantic-release/changelog": "6.0.3",
"@semantic-release/git": "10.0.1",
"@semantic-release/github": "10.0.6",
Expand Down
Loading

0 comments on commit 4429681

Please sign in to comment.