dvShellFirstChunks function

Stream<String>? dvShellFirstChunks({
  1. required String shell,
  2. required String path,
  3. required String title,
  4. required Future<DVPageData?> resolving,
  5. List<String> text = const <String>[],
  6. String? refusedTitle,
  7. String? siteUrl,
  8. String? siteName,
  9. String? description,
  10. String? image,
  11. String preloads = '',
})

The page for path as three writes, the first of them before the data -- dartvel.web.server.streaming: shell.

  1. 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.
  2. What the data writes into the head -- title, description, canonical, Open Graph, structured data, the icon -- and </head>, once resolving has.
  3. 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();
}