Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drop early hints, use headers to send links instead. #430

Merged
merged 3 commits into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
6 changes: 3 additions & 3 deletions 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 All @@ -60,7 +60,7 @@
"eslint": "9.6.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-prettier": "5.1.3",
"express": "4.19.2",
"express": "4.21.1",
"get-stream": "9.0.1",
"globals": "15.8.0",
"http-proxy": "1.18.1",
Expand All @@ -69,6 +69,6 @@
"prettier": "3.3.2",
"semantic-release": "23.1.1",
"tap": "18.7.2",
"typescript": "5.4.5"
"typescript": "5.6.3"
}
}
Loading