dvShellFirstChunks function
The page for path as three writes, the first of them before the data --
dartvel.web.server.streaming: shell.
- The head as no render changes it -- base, charset, every preload, the
splash, a preload for each script the body loads -- and
preloads, the route's own parts and images, sent at once. - What the data writes into the head -- title, description, canonical,
Open Graph, structured data, the icon -- and
</head>, onceresolvinghas. - The body.
The first part is identical in every render of the shell (dvHeadParts
is the rule), so the browser assembles exactly the page that was
rendered, with the head's elements in a different order. preloads
depend on the route alone, never on the data, which is what lets them go
first.
200 is on the wire before the data exists, so a record that turns out
hidden or unauthorized, or data that could not be fetched, is sent as the
page with none of the data and noindex -- a soft 404, which is the
price of this mode. refusedTitle is the title such a page wears.
resolving is started by the caller before this returns, so the query
runs while the head is on its way; its failure is caught here at once, so
it is never an unhandled error.
Null when the shell has no head to split, and the caller falls back to
waiting for the data. One function for both servers -- the deployed one
and dartvel dev --release -- because two copies of this drifted once already.
Implementation
Stream<String>? dvShellFirstChunks({
required String shell,
required String path,
required String title,
required Future<DVPageData?> resolving,
List<String> text = const <String>[],
String? refusedTitle,
String? siteUrl,
String? siteName,
String? description,
String? image,
String preloads = '',
}) {
final DVHeadParts? early = dvHeadParts(
dvRenderRoute(shell: shell, path: path, title: '', siteUrl: siteUrl, siteName: siteName),
);
if (early == null) return null;
// Handled here rather than where it is awaited: the stream below does not
// run until it is listened to, and an error with no handler by then is an
// unhandled one.
final Future<(DVPageData?, bool)> outcome = resolving.then(
(DVPageData? data) => (data, false),
onError: (Object _, StackTrace __) => (null, true),
);
Stream<String> chunks() async* {
yield '${early.opening}${early.fixed}${_bodyScriptPreloads(early)}$preloads';
final (DVPageData? data, bool failed) = await outcome;
final bool refused = data != null && data.visibility != DVPageVisibility.public;
final String page = refused
// None of the data, as the refusal would have shown: it describes a
// page the reader is not being shown.
? dvRenderRoute(shell: shell, path: path, title: refusedTitle ?? title, siteUrl: siteUrl, siteName: siteName)
: data == null
? dvRenderRoute(shell: shell, path: path, title: title, text: text, siteUrl: siteUrl, siteName: siteName, description: description, image: image)
: dvRenderPage(shell: shell, path: path, data: data, siteUrl: siteUrl, siteName: siteName, description: description, image: image);
final DVHeadParts late = dvHeadParts(page) ?? early;
// 200 is already on the wire, so a record that cannot be shown -- or data
// that could not be fetched -- is kept out of an index this way instead.
final String noindex = refused || failed ? '<meta name="robots" content="noindex">\n' : '';
const String close = '</head>';
yield '$noindex${late.written}$close';
yield late.rest.startsWith(close) ? late.rest.substring(close.length) : late.rest;
}
return chunks();
}