Skip to content

Commit

Permalink
Introduce a new URL for library pages, at index.html (#3895)
Browse files Browse the repository at this point in the history
Fixes #1346

This "moves" the URL of each library from, e.g.
'package-two_lib2/package-two_lib2-library.html' to
'package-two_lib2/index.html', such that we can have links with
'package-two_lib2'. The old URLs are preserved via redirecting HTML files.

(HTTP redirects are more standard and recommended, but dartdoc does not contain
an HTTP server; HTTP redirects could be implemented at the hosting server,
which is different for different doc hosts (Google Storage, Firebase, ...).
Instead we use simpler HTML redirects. In fact, a given backend host could use
the HTML redirecting files as a database to inform HTTP redirect rules, that
need to be implemented differently for each backend. So dartdoc now produces
redirecting files, but a given backend could instead return an HTTP 301
Permanent Redirect response when requesting one of the redirecting files.)

This adds a negligible number of files (1 per documented library), and a
negligible number of bytes (tiny redirect files), but actually is probably a
net negative in terms of bytes, as many links are now shortened, removing the
HTML file name of library URLs.
  • Loading branch information
srawlins authored Oct 15, 2024
1 parent 80c6f18 commit 7e503c1
Show file tree
Hide file tree
Showing 23 changed files with 1,940 additions and 1,678 deletions.
3,182 changes: 1,613 additions & 1,569 deletions lib/resources/docs.dart.js

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions lib/resources/docs.dart.js.map

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion lib/src/generator/generator_backend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,14 @@ abstract class GeneratorBackend {
runtimeStats.incrementAccumulator('writtenFunctionFileCount');
}

/// Emits documentation content for the [library].
/// Emits documentation content for the [library], and the content for the
/// library's previous location (which just redirects to the new location).
void generateLibrary(PackageGraph packageGraph, Library library) {
var data = LibraryTemplateData(options, packageGraph, library);
var content = templates.renderLibrary(data);
var redirectContent = templates.renderLibraryRedirect(data);
write(writer, library.filePath, data, content);
write(writer, library.redirectingPath, data, redirectContent);
runtimeStats.incrementAccumulator('writtenLibraryFileCount');
}

Expand Down
34 changes: 34 additions & 0 deletions lib/src/generator/templates.aot_renderers_for_html.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,40 @@ String renderLibrary(LibraryTemplateData context0) {
return buffer.toString();
}

String renderLibraryRedirect(LibraryTemplateData context0) {
final buffer = StringBuffer();
buffer.write('''<!DOCTYPE html>
<html lang="en">
<head>
<link rel="canonical" href="''');
if (context0.useBaseHref) {
var context1 = context0.htmlBase;
buffer.write(context0.htmlBase);
}
buffer.writeEscaped(context0.self.href);
buffer.write('''" />
<meta http-equiv="refresh" content="0; url=''');
if (context0.useBaseHref) {
var context2 = context0.htmlBase;
buffer.write(context0.htmlBase);
}
buffer.writeEscaped(context0.self.href);
buffer.write('''" />
</head>
<body>
<p><a href="''');
if (context0.useBaseHref) {
var context3 = context0.htmlBase;
buffer.write(context0.htmlBase);
}
buffer.writeEscaped(context0.self.href);
buffer.write('''">New URL</a></p>
</body>
</html>''');

return buffer.toString();
}

String renderMethod(MethodTemplateData context0) {
final buffer = StringBuffer();
buffer.write(_renderMethod_partial_head_0(context0));
Expand Down
11 changes: 11 additions & 0 deletions lib/src/generator/templates.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
@Renderer(#renderFunction, Context<FunctionTemplateData>(), 'function')
@Renderer(#renderIndex, Context<PackageTemplateData>(), 'index')
@Renderer(#renderLibrary, Context<LibraryTemplateData>(), 'library')
@Renderer(
#renderLibraryRedirect, Context<LibraryTemplateData>(), 'library_redirect')
@Renderer(#renderMethod, Context<MethodTemplateData>(), 'method')
@Renderer(#renderMixin, Context<MixinTemplateData>(), 'mixin')
@Renderer(#renderProperty, Context<PropertyTemplateData>(), 'property')
Expand Down Expand Up @@ -99,6 +101,7 @@ abstract class Templates {
String renderFunction(FunctionTemplateData context);
String renderIndex(PackageTemplateData context);
String renderLibrary(LibraryTemplateData context);
String renderLibraryRedirect(LibraryTemplateData context);
String renderMethod(MethodTemplateData context);
String renderMixin(MixinTemplateData context);
String renderProperty(PropertyTemplateData context);
Expand Down Expand Up @@ -174,6 +177,10 @@ class HtmlAotTemplates implements Templates {
String renderLibrary(LibraryTemplateData context) =>
aot_renderers_for_html.renderLibrary(context);

@override
String renderLibraryRedirect(LibraryTemplateData context) =>
aot_renderers_for_html.renderLibraryRedirect(context);

@override
String renderMethod(MethodTemplateData context) =>
aot_renderers_for_html.renderMethod(context);
Expand Down Expand Up @@ -251,6 +258,10 @@ class RuntimeTemplates implements Templates {
String renderLibrary(LibraryTemplateData context) =>
runtime_renderers.renderLibrary(context, _libraryTemplate);

@override
String renderLibraryRedirect(LibraryTemplateData context) =>
runtime_renderers.renderLibraryRedirect(context, _libraryTemplate);

@override
String renderMethod(MethodTemplateData context) =>
runtime_renderers.renderMethod(context, _methodTemplate);
Expand Down
112 changes: 59 additions & 53 deletions lib/src/generator/templates.runtime_renderers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2760,28 +2760,6 @@ class _Renderer_Container extends RendererBase<Container> {
parent: r);
},
),
'filePath': Property(
getValue: (CT_ c) => c.filePath,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty =
_Renderer_String.propertyMap().getValue(name);
return nextProperty.renderVariable(
self.getValue(c) as String,
nextProperty,
[...remainingNames.skip(1)]);
},
isNullValue: (CT_ c) => false,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.filePath, ast, r.template, sink,
parent: r);
},
),
'hasAvailableInstanceFields': Property(
getValue: (CT_ c) => c.hasAvailableInstanceFields,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -4283,6 +4261,28 @@ class _Renderer_Enum extends RendererBase<Enum> {
parent: r, getters: _invisibleGetters['EnumElement']!);
},
),
'fileName': Property(
getValue: (CT_ c) => c.fileName,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty =
_Renderer_String.propertyMap().getValue(name);
return nextProperty.renderVariable(
self.getValue(c) as String,
nextProperty,
[...remainingNames.skip(1)]);
},
isNullValue: (CT_ c) => false,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.fileName, ast, r.template, sink,
parent: r);
},
),
'hasPublicEnumValues': Property(
getValue: (CT_ c) => c.hasPublicEnumValues,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -8430,6 +8430,28 @@ class _Renderer_Library extends RendererBase<Library> {
parent: r));
},
),
'redirectingPath': Property(
getValue: (CT_ c) => c.redirectingPath,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty =
_Renderer_String.propertyMap().getValue(name);
return nextProperty.renderVariable(
self.getValue(c) as String,
nextProperty,
[...remainingNames.skip(1)]);
},
isNullValue: (CT_ c) => false,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.redirectingPath, ast, r.template, sink,
parent: r);
},
),
'referenceChildren': Property(
getValue: (CT_ c) => c.referenceChildren,
renderVariable: (CT_ c, Property<CT_> self,
Expand Down Expand Up @@ -8795,6 +8817,12 @@ class _Renderer_LibraryTemplateData extends RendererBase<LibraryTemplateData> {
}
}

String renderLibraryRedirect(LibraryTemplateData context, Template template) {
var buffer = StringBuffer();
_render_LibraryTemplateData(context, template.ast, template, buffer);
return buffer.toString();
}

class _Renderer_Locatable extends RendererBase<Locatable> {
static final Map<Type, Object> _propertyMapCache = {};
static Map<String, Property<CT_>> propertyMap<CT_ extends Locatable>() =>
Expand Down Expand Up @@ -11037,8 +11065,8 @@ class _Renderer_ModelFunctionTyped extends RendererBase<ModelFunctionTyped> {
parent: r);
},
),
'filePath': Property(
getValue: (CT_ c) => c.filePath,
'fileName': Property(
getValue: (CT_ c) => c.fileName,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
Expand All @@ -11055,7 +11083,7 @@ class _Renderer_ModelFunctionTyped extends RendererBase<ModelFunctionTyped> {
isNullValue: (CT_ c) => false,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.filePath, ast, r.template, sink,
_render_String(c.fileName, ast, r.template, sink,
parent: r);
},
),
Expand Down Expand Up @@ -12231,7 +12259,7 @@ class _Renderer_Package extends RendererBase<Package> {
}
}

String renderIndex(PackageTemplateData context, Template template) {
String renderError(PackageTemplateData context, Template template) {
var buffer = StringBuffer();
_render_PackageTemplateData(context, template.ast, template, buffer);
return buffer.toString();
Expand Down Expand Up @@ -12469,13 +12497,13 @@ class _Renderer_PackageTemplateData extends RendererBase<PackageTemplateData> {
}
}

String renderSearchPage(PackageTemplateData context, Template template) {
String renderIndex(PackageTemplateData context, Template template) {
var buffer = StringBuffer();
_render_PackageTemplateData(context, template.ast, template, buffer);
return buffer.toString();
}

String renderError(PackageTemplateData context, Template template) {
String renderSearchPage(PackageTemplateData context, Template template) {
var buffer = StringBuffer();
_render_PackageTemplateData(context, template.ast, template, buffer);
return buffer.toString();
Expand Down Expand Up @@ -14653,28 +14681,6 @@ class _Renderer_TopLevelVariable extends RendererBase<TopLevelVariable> {
parent: r);
},
),
'filePath': Property(
getValue: (CT_ c) => c.filePath,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
return self.getValue(c).toString();
}
var name = remainingNames.first;
var nextProperty =
_Renderer_String.propertyMap().getValue(name);
return nextProperty.renderVariable(
self.getValue(c) as String,
nextProperty,
[...remainingNames.skip(1)]);
},
isNullValue: (CT_ c) => false,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.filePath, ast, r.template, sink,
parent: r);
},
),
'getter': Property(
getValue: (CT_ c) => c.getter,
renderVariable:
Expand Down Expand Up @@ -15347,8 +15353,8 @@ class _Renderer_Typedef extends RendererBase<Typedef> {
parent: r);
},
),
'filePath': Property(
getValue: (CT_ c) => c.filePath,
'fileName': Property(
getValue: (CT_ c) => c.fileName,
renderVariable:
(CT_ c, Property<CT_> self, List<String> remainingNames) {
if (remainingNames.isEmpty) {
Expand All @@ -15365,7 +15371,7 @@ class _Renderer_Typedef extends RendererBase<Typedef> {
isNullValue: (CT_ c) => false,
renderValue: (CT_ c, RendererBase<CT_> r,
List<MustachioNode> ast, StringSink sink) {
_render_String(c.filePath, ast, r.template, sink,
_render_String(c.fileName, ast, r.template, sink,
parent: r);
},
),
Expand Down
3 changes: 0 additions & 3 deletions lib/src/model/container.dart
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,6 @@ abstract class Container extends ModelElement
@override
Iterable<CommentReferable> get referenceParents => [library];

@override
String get filePath => '${canonicalLibraryOrThrow.dirName}/$fileName';

/// The full path of this element's sidebar file.
String get sidebarPath;

Expand Down
4 changes: 4 additions & 0 deletions lib/src/model/enum.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class Enum extends InheritingContainer with Constructable, MixedInTypes {
...interfaceElements.expandInheritanceChain,
];

@override
// Prevent a collision with the library file.
String get fileName => name == 'index' ? '$name-enum.html' : '$name.html';

@override
String get sidebarPath =>
'${canonicalLibraryOrThrow.dirName}/$name-enum-sidebar.html';
Expand Down
6 changes: 5 additions & 1 deletion lib/src/model/getter_setter_combo.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ mixin GetterSetterCombo on ModelElement {
String get name;

@override
String get fileName => isConst ? '$name-constant.html' : '$name.html';
String get fileName => isConst
? '$name-constant.html'
: name == 'index'
? '$name-property.html'
: '$name.html';

/// Whether this has a constant value which should be displayed.
bool get hasConstantValueForDisplay => false;
Expand Down
15 changes: 13 additions & 2 deletions lib/src/model/library.dart
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ class Library extends ModelElement
String get filePath => '$dirName/$fileName';

@override
String get fileName => '$dirName-library.html';
String get fileName => 'index.html';

String get sidebarPath => '$dirName/$dirName-library-sidebar.html';

Expand All @@ -216,9 +216,20 @@ class Library extends ModelElement
if (!identical(canonicalModelElement, this)) {
return canonicalModelElement?.href;
}
return '${package.baseHref}$filePath';
// The file name for a library is 'index.html', so we just link to the
// directory name. This keeps the URL looking short, _without_ the
// 'index.html' in the URL.
return '${package.baseHref}$dirName';
}

/// The previous value of [filePath].
///
/// This path is used to write a file that ontains an HTML redirect (not an
/// HTTP redirect) to a library's current [filePath].
String get redirectingPath => '$dirName/$dirName-library.html';

/// Whether a libary is anonymous, either because it has no library directive
/// or it has a library directive without a name.
bool get isAnonymous => element.name.isEmpty;

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/src/model/model_element.dart
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,7 @@ abstract class ModelElement

/// The full path of the output file in which this element will be primarily
/// documented.
String get filePath;
String get filePath => '${canonicalLibraryOrThrow.dirName}/$fileName';

@override
String get fullyQualifiedName =>
Expand Down
3 changes: 2 additions & 1 deletion lib/src/model/model_function.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class ModelFunctionTyped extends ModelElement with TypeParameters {
Library get enclosingElement => library;

@override
String get filePath => '${canonicalLibrary?.dirName}/$fileName';
// Prevent a collision with the library file.
String get fileName => name == 'index' ? '$name-function.html' : '$name.html';

@override
String get aboveSidebarPath => canonicalLibraryOrThrow.sidebarPath;
Expand Down
10 changes: 7 additions & 3 deletions lib/src/model/package.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,13 @@ class Package extends LibraryContainer
bool get isPublic =>
_isLocalPublicByDefault || libraries.any((l) => l.isPublic);

/// Return true if this is the default package, this is part of an embedder
/// SDK, or if [DartdocOptionContext.autoIncludeDependencies] is true -- but
/// only if the package was not excluded on the command line.
/// Whether this package is local.
///
/// A package can be local in three ways:
/// * this is the default package,
/// * this is part of an embedder SDK, or
/// * [DartdocOptionContext.autoIncludeDependencies] is true, and this
/// package is not excluded with `exclude-packages`.
bool get isLocal {
// Do not document as local if we excluded this package by name.
if (_isExcluded) return false;
Expand Down
Loading

0 comments on commit 7e503c1

Please sign in to comment.