Skip to content
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: 1 addition & 1 deletion site/lib/src/extensions/registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'glossary_link_processor.dart';
import 'header_extractor.dart';
import 'header_processor.dart';
import 'table_processor.dart';
import 'tutorial_prefetch_processor.dart';
import 'tutorial_navigation.dart';
import 'tutorial_structure_processor.dart';

/// A list of all node-processing, page extensions to applied to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_content/jaspr_content.dart';

import '../models/tutorial_model.dart';

/// A page extension for Jaspr Content that adds page navigation and a
/// prefetch link for the next unit to the current tutorial page.
/// A page extension for Jaspr Content that adds
/// page navigation to the current tutorial page.
final class TutorialNavigationExtension implements PageExtension {
const TutorialNavigationExtension();

Expand Down Expand Up @@ -67,19 +65,6 @@ final class TutorialNavigationExtension implements PageExtension {
},
);

if (nextChapter == null) {
return nodes;
}

return [
ComponentNode(
Document.head(
children: [
link(rel: 'prefetch', href: nextChapter.url),
],
),
),
...nodes,
];
return nodes;
}
}
75 changes: 75 additions & 0 deletions site/lib/src/layouts/dash_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:convert';

import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:jaspr_content/jaspr_content.dart';
Expand All @@ -25,6 +27,14 @@ abstract class FlutterDocsLayout extends PageLayoutBase {

String get defaultSidenav => 'default';

/// Returns page-specific URLs to eagerly speculate on, in addition to
/// the document-level rules that match all internal links.
///
/// Override in subclasses to provide page-specific URLs for
/// eager prerendering and prefetching.
({Set<String> prerender, Set<String> prefetch}) speculationUrls(Page page) =>
const (prerender: {}, prefetch: {});

@override
@mustCallSuper
Iterable<Component> buildHead(Page page) {
Expand Down Expand Up @@ -148,6 +158,9 @@ ga('create', 'UA-67589403-1', 'auto');
ga('send', 'pageview');
</script>
'''),
// Add speculation rules and prefetch fallback links for
// URLs provided by subclass overrides of speculationUrls.
..._buildSpeculationRulesHead(page),
];
}

Expand Down Expand Up @@ -263,4 +276,66 @@ if (sidenav) {
],
);
}

/// Builds the speculation rules `<script>` and `<link rel="prefetch">`
/// fallback tags for the given [page].
///
/// Includes page-specific list rules from [speculationUrls] and
/// document rules that prefetch internal links on hover (`moderate`)
/// and prerender them on click (`conservative`).
///
/// Add the `no-prerender` class to a link to
/// exclude it from document-level prerendering.
List<Component> _buildSpeculationRulesHead(Page page) {
final (:prerender, :prefetch) = speculationUrls(page);

// Exclude prerendered URLs from the prefetch list since
// prerendering is a superset of prefetching.
final prefetchOnly = prefetch.difference(prerender);

// Document rules to match same-origin links across the page.
const internalLink = {'href_matches': '/*'};
const notNoPrerender = {
'not': {'selector_matches': '.no-prerender'},
};

final rules = jsonEncode({
'prefetch': [
// Prefetch internal links on hover.
{
'where': internalLink,
'eagerness': 'moderate',
},
// Prefetch specific URLs from the page eagerly.
if (prefetchOnly.isNotEmpty)
{
'urls': [...prefetchOnly],
},
],
'prerender': [
// Prerender internal links on click,
// unless the link has the 'no-prerender' class.
{
'where': {
'and': [internalLink, notNoPrerender],
},
'eagerness': 'conservative',
},
// Prerender specific URLs from the page eagerly.
if (prerender.isNotEmpty)
{
'urls': [...prerender],
'eagerness': 'eager',
},
],
}).replaceAll('</', r'<\/');

return [
RawText('<script type="speculationrules">$rules</script>'),
Comment thread
parlough marked this conversation as resolved.
// Fall back to prefetch link tags for browsers without
// Speculation Rules API support.
for (final url in {...prerender, ...prefetch})
link(rel: 'prefetch', href: url),
];
}
}
30 changes: 30 additions & 0 deletions site/lib/src/layouts/doc_layout.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,27 @@ class DocLayout extends FlutterDocsLayout {

bool get allowBreadcrumbs => true;

@override
({Set<String> prerender, Set<String> prefetch}) speculationUrls(Page page) {
// On the homepage, prefetch pages commonly navigated to,
// such as entries from the top navigation menu.
if (page.path == 'index.md') {
return (
prerender: const {},
prefetch: const {
'/learn/pathway',
'/ai/create-with-ai',
},
);
}

final pageData = page.data.page;
return (
prerender: {?_pathFromPageInfo(pageData['next'])},
prefetch: {?_pathFromPageInfo(pageData['prev'])},
);
}

@override
Component buildBody(Page page, Component child) {
final pageData = page.data.page;
Expand Down Expand Up @@ -89,3 +110,12 @@ class DocLayout extends FlutterDocsLayout {
);
}
}

/// Extracts and returns the `path` value from a page info map,
/// or `null` if [data] is not a map or has no `path` entry.
String? _pathFromPageInfo(Object? data) {
if (data case {'path': final String path}) {
return path;
}
return null;
}
Loading