hydraline 0.0.13
hydraline: ^0.0.13 copied to clipboard
Pure-Dart core of Hydraline: real semantic HTML for Flutter Web. DocumentNode, SafeUrl, SEO, JSON-LD, sitemap, robots, audit CLI. + hydraline_server, hydraline_flutter.
hydraline #
Part of Hydraline - real crawlable HTML for Flutter Web. Three packages, one toolkit.
hydraline(core, you are here) ·hydraline_server(SSR) ·hydraline_flutter(widgets)
Pure-Dart core - the foundation everything else builds on.
No Flutter, no dart:ui, no dart:html. Works on any Dart VM.
Build, serialize and audit real semantic HTML from Dart: headings, paragraphs,
images with alt, links, tables, sections, ordered/unordered lists, and island
placeholders - all typed, all safe, all crawlable.
Why #
Flutter Web renders into a <canvas> — crawlers see an empty shell. Hydraline
fixes this: your pages become real semantic HTML in view-source. This
package is the foundation — the DocumentNode tree that both the SSR server
and the SSG compiler consume. One model, two delivery paths, same bytes.
- Safe by construction.
SafeUrlrejectsjavascript:/data:at type level; context-aware escaping (text vs attribute);sanitizeHtml()strips scripts and event handlers. 1e6-input XSS fuzz suite. - Full SEO head. Open Graph (with image dimensions), Twitter Card, 9 JSON-LD types (Product, Article, FAQ, Recipe, Event...), sitemap.xml (auto-split at 50k URLs), robots.txt, hreflang alternates, audit CLI.
- Zero dependencies on Flutter. Works in any Dart VM — servers, CLIs, build
pipelines. The
hydraline_serverandhydraline_flutterpackages build on this core, adding SSR/streaming (server) and widgets/SSG/islands (Flutter).
What's inside #
| Module | Description |
|---|---|
DocumentNode |
Sealed immutable tree: HeadingNode, ParagraphNode, ImageNode, AnchorNode, SectionNode (<main>/<nav>/<article>), ListNode (<ol>/<ul> + ListItemNode), TableNode, DetailsNode, BlockquoteNode, PreNode, CodeNode, TimeNode, IslandPlaceholderNode, HtmxIslandNode, VanillaIslandNode, JsonLdNode |
HtmlSerializer |
Single-pass deterministic HTML serializer - buffered, streaming, and fragment modes. O(nodes + text), 10k paragraphs in 29 ms |
SafeUrl |
Type-safe URL - no public constructor, scheme allowlist blocks javascript:/data: at construction. A node can never hold an unchecked URL |
sanitizeHtml() |
Baseline HTML sanitizer: strips <script> and on* event handlers |
UnsafeHtmlNode / .trusted() |
Opt-in raw HTML escape hatch. .trusted() explicitly signals reviewed content |
escapeHtmlText / escapeHtmlAttribute |
Context-aware escaping - text vs attribute, never confused. Backed by a 1e6-input XSS fuzz suite |
SeoMeta / buildHead() |
Deterministic <head>: title, description, canonical, viewpoint, robots, full Open Graph (with image dimensions), Twitter Card, hreflang alternates, extra meta/link |
JsonLd |
Type-safe JSON-LD builders: Article, Product (+Offer/price), BreadcrumbList, WebPage, Organization, FAQPage, Event, Recipe, Review + raw() escape hatch |
Sitemap |
sitemap.xml with auto-split at 50k URLs / 50 MB, hreflang per URL, changefreq, priority, lastmod (UTC) |
Robots |
robots.txt with programmable rules, line-break validation |
RouteManifest |
hydraline.routes.yaml parser + Dart builder - document / hybrid / app modes |
IslandSpec / IslandStateCodec |
data-state props contract - JSON-safe, validated |
SsgCollector |
Widget-to-node registration with nested sectioning (beginSection, beginList) |
islandRuntime() |
One-liner for runtime script injection: engine config + dispatcher + custom element. Optional SRI (integrity/crossorigin) |
SeoValidator / Audit |
SEO validator: title/description length, alt text, duplicate canonicals, hreflang, unsafe HTML. Audit CLI: dart run hydraline:audit |
Csp |
Recommended Content-Security-Policy: script-src 'self' 'wasm-unsafe-eval' |
vanillaIslandsJs |
Level-1 vanilla islands bundle (≤ 8 KB) - accordion, tabs, carousel, theme, copy-button, lazy-image with hardened null guards |
htmxGlueJs |
HTMX bootstrap glue - loads self-hosted HTMX runtime on demand |
web/vanilla-islands.js |
Browser-consumable mirror, byte-identical to the Dart constant (locked by test) |
Rules #
No package:flutter, dart:ui, or dart:html - ever. This package is pure Dart.
Enforced at build time by melos run boundaries.
Quick start #
import 'package:hydraline/hydraline.dart';
final root = DocumentRootNode(
lang: 'en',
head: buildHead(SeoMeta(
title: 'Espresso Machine',
description: 'Compact 15-bar espresso machine.',
canonical: SafeUrl.parse('https://shop.example/'),
openGraph: OpenGraph(
type: 'product',
image: SafeUrl.parse('https://shop.example/og.jpg'),
),
), structuredData: [JsonLd.product(name: 'Espresso', price: 249, currency: 'EUR')]),
body: [
HeadingNode(level: 1, children: [TextNode('Espresso Machine')]),
ParagraphNode(children: [TextNode('Real HTML. Real rankings.')]),
SectionNode(role: SectionRole.main, children: [
ListNode(ordered: false, items: [
ListItemNode(children: [ParagraphNode(children: [TextNode('Feature one')])]),
]),
]),
IslandPlaceholderNode(
id: 'calculator',
directive: HydrationDirective.onVisible,
size: IslandSize(width: 640, height: 320),
state: {'price': 249},
),
...islandRuntime(),
],
);
const serializer = HtmlSerializer();
print(serializer.serialize(root));
// <!DOCTYPE html><html lang="en"><head>...<main><ol><li>...
dart run hydraline:audit dist/index.html # what a crawler sees
Proven #
- 229 unit/widget tests - including a 1e6-input XSS fuzz suite
- Single-pass serializer: 10k paragraphs 29 ms (16.7 MB/s), no quadratic concat
- Security:
SafeUrltype-level allowlist, context-aware escaping, HTML sanitizer - CI-gated: analyze (
--fatal-infos), format:check, boundaries (I1), coverage
Runnable example: example/main.dart.
Documentation #
- Document Model - full node hierarchy
- Configuration - route manifest, sitemap, SEO
- Security - SafeUrl, escaping, CSP, sanitizer, SRI
- Getting Started
License #
MIT - Yevhen Leonidov