renderPage function

String renderPage({
  1. required String title,
  2. required String content,
  3. String? scriptName,
  4. List<String> additionalScripts = const [],
  5. List<String> stylesheets = const [],
  6. Object? inlineStyles,
  7. Object? headContent,
  8. String lang = 'en',
  9. String charset = 'UTF-8',
  10. String viewport = 'width=device-width, initial-scale=1',
  11. List<String> metaTags = const [],
  12. String? nonce,
})

Renders a complete HTML page with the given options.

This function generates a full HTML document with proper structure for server-side rendering with Declarative Shadow DOM support.

Example

final html = renderPage(
  title: 'Home',
  content: Counter(start: 100).render(),
  scriptName: 'home.dart.js',
);

Implementation

String renderPage({
  required String title,
  required String content,
  String? scriptName,
  List<String> additionalScripts = const [],
  List<String> stylesheets = const [],
  Object? inlineStyles,
  Object? headContent,
  String lang = 'en',
  String charset = 'UTF-8',
  String viewport = 'width=device-width, initial-scale=1',
  List<String> metaTags = const [],
  String? nonce,
}) {
  final buffer = StringBuffer();

  // Escape user inputs to prevent XSS
  final safeTitle = _escape(title);
  final safeLang = _escape(lang);
  final safeCharset = _escape(charset);
  final safeViewport = _escape(viewport);

  buffer.writeln('<!DOCTYPE html>');
  buffer.writeln('<html lang="$safeLang">');
  buffer.writeln('<head>');
  buffer.writeln('  <meta charset="$safeCharset">');
  buffer.writeln('  <meta name="viewport" content="$safeViewport">');

  // Additional meta tags
  for (final meta in metaTags) {
    buffer.writeln('  $meta');
  }

  buffer.writeln('  <title>$safeTitle</title>');

  // Stylesheets
  for (final stylesheet in stylesheets) {
    buffer.writeln('  <link rel="stylesheet" href="${_escape(stylesheet)}">');
  }

  // Inline styles
  String? stylesStr;
  if (inlineStyles is Stylesheet) {
    stylesStr = inlineStyles.toCss();
  } else if (inlineStyles is String) {
    stylesStr = inlineStyles;
  }

  if (stylesStr != null && stylesStr.isNotEmpty) {
    buffer.writeln(
      '  <style${nonce != null && nonce.isNotEmpty ? ' nonce="$nonce"' : ''}>',
    );
    buffer.writeln(stylesStr);
    buffer.writeln('  </style>');
  } else {
    // Default minimal styles
    buffer.writeln(
      '  <style${nonce != null && nonce.isNotEmpty ? ' nonce="$nonce"' : ''}>',
    );
    buffer.writeln('    body {');
    buffer.writeln(
      '      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;',
    );
    buffer.writeln('      margin: 0;');
    buffer.writeln('      padding: 20px;');
    buffer.writeln('      line-height: 1.5;');
    buffer.writeln('    }');
    buffer.writeln('  </style>');
  }

  // Additional head content
  if (headContent != null) {
    if (headContent is String && headContent.isNotEmpty) {
      buffer.writeln(headContent);
    } else if (headContent is Node) {
      buffer.writeln(headContent.toHtml());
    } else if (headContent is List) {
      for (final item in headContent) {
        if (item is Node) {
          buffer.writeln(item.toHtml());
        } else if (item != null) {
          buffer.writeln(item.toString());
        }
      }
    }
  }

  // Main script (deferred to allow HTML to render first)
  if (scriptName != null && scriptName.isNotEmpty) {
    buffer.writeln(
      '  <script defer src="/${_escape(scriptName)}"${nonce != null && nonce.isNotEmpty ? ' nonce="$nonce"' : ''}></script>',
    );
  }

  // Additional scripts
  for (final script in additionalScripts) {
    buffer.writeln(
      '  <script defer src="${_escape(script)}"${nonce != null && nonce.isNotEmpty ? ' nonce="$nonce"' : ''}></script>',
    );
  }

  // Live Reload Script (Injected in Dev Mode)
  // We avoid importing dart:io directly to keep this file platform-agnostic if possible,
  // but checking Platform.environment requires dart:io.
  // Since this is 'server/render_page.dart', dart:io is expected.
  // However, to be safe and clean, we can look for a global override or just use Platform.
  // implementation:
  _injectLiveReload(buffer, nonce);

  buffer.writeln('</head>');
  buffer.writeln('<body>');
  buffer.writeln(content);
  buffer.writeln('</body>');
  buffer.writeln('</html>');

  return buffer.toString();
}