blogger_theme 5.0.0 copy "blogger_theme: ^5.0.0" to clipboard
blogger_theme: ^5.0.0 copied to clipboard

A lightweight standalone Dart library to design Blogger themes with Jaspr-like syntax.

🚀 blogger_theme #

pub package pub points License GitHub Issues Sponsor


What is blogger_theme? #

blogger_theme is a lightweight Dart library for generating Blogger (Blogspot) theme XML using a clean, declarative component API inspired by Jaspr.

It removes the need to write raw Blogger template XML by hand and provides reusable Dart components for common Blogger template elements such as b:section, b:widget, b:if, b:loop, and b:skin.


Features #

  • Declarative theme authoring in Dart using Component, DomComponent, and Text.
  • Pure Dart implementation with zero runtime dependencies.
  • Safe XML rendering for Blogger templates, including escaping special characters and filtering XML 1.0 control characters.
  • Blogger-native helpers for sections, widgets, conditionals, loops, includes, and template fragments.
  • Client script support with BClientScript for Dart-to-JS inline script injection.
  • Flexible theme export using BloggerTheme.generate().

Installation #

Add blogger_theme to your pubspec.yaml:

dependencies:
  blogger_theme: ^5.0.0

Or add it directly with:

dart pub add blogger_theme

Run:

dart pub get

References #


Quick Start #

1. Define your layout component #

import 'package:blogger_theme/blogger_theme.dart';

class BlogLayout extends Component {
  const BlogLayout();

  @override
  Iterable<Component> build() => [
    Div(
      attributes: {'class': 'wrapper-pane'},
      children: [
        BSection(
          id: 'header-area',
          className: 'header-section',
          maxwidgets: 1,
          showaddelement: true,
          children: [],
        ),
        BSection(
          id: 'main-content',
          className: 'main-content-section',
          maxwidgets: 1,
          showaddelement: true,
          children: [
            H1(
              children: [
                Text('My AMP Blogger Theme having H1 Keycolor in '),
                //<b:eval expr="data:skin.vars.keycolor"/></h1>
                BEval(expr: 'data:skin.vars.keycolor'),
              ],
            ),
          ],
        ),
      ],
    ),
  ];
}


2. Generate Blogger theme XML (with AMP-compliant overrides) #

import 'package:blogger_theme/blogger_theme.dart';

void main() {
  final theme = BloggerTheme(
    attributes: {
      // static compiler flags
      'b:responsive': 'true',
      'b:defaultwidgetversion': '2',
      'b:layoutsversion': '3',
      'b:css': 'false', // Disables default Blogger CSS globally
      /// this removes that <script src=".../widgets.js">
      /// for Better do try  replace the closing </body> with
      /// => &lt;!--</body>--&gt;&lt;/body&gt;
      ///  => this will make those scripts as comment
      /// Reference: [JS as Comment] Below
      // 'b:js': 'false', // Disables default widgets.js globally
      //
      'xmlns': 'http://www.w3.org/1999/xhtml',
      'xmlns:b': 'http://www.google.com/2005/gml/b',
      'xmlns:data': 'http://www.google.com/2005/gml/data',
      'xmlns:expr': 'http://www.google.com/2005/gml/expr',
    },
    // Reset attributes to clean XML output for strict AMP validation
    children: [
      BAttr(name: 'xmlns', value: ''),
      BAttr(name: 'xmlns:b', value: ''),
      BAttr(name: 'xmlns:expr', value: ''),
      BAttr(name: 'xmlns:data', value: ''),
      // Conditionally adds amp="amp" (or lightning bolt symbol ⚡) to <html> on mobile requests
      BAttr(cond: 'data:blog.isMobileRequest', name: 'amp', value: 'amp'),

      // Alternatively, you can use the ⚡ symbol:
      // BAttr(cond: 'data:blog.isMobileRequest', name: '⚡', value: '⚡'),

      // BClass(exprName: '"nJs" + (data:blog.isMobileRequest ? " amp" : "")'), // TODO:[Kept to show we can assign class ]
    ],
    head: [
      Meta(attributes: {"expr:charset": "data:blog.encoding"}),
      Script(
        attributes: {
          'async': 'async',
          'src': 'https://cdn.ampproject.org/v0.js',
        },
      ),
      Meta(
        attributes: {
          "name": "viewport",
          "content": "width=device-width,minimum-scale=1,initial-scale=1",
        },
      ),

      Link(
        attributes: {"rel": "canonical", "expr:href": "data:blog.canonicalUrl"},
      ),
      Link(
        attributes: {
          "rel": "amphtml",
          "expr:href": 'data:blog.canonicalUrl params { m: "1" }',
        },
      ),

      Title(children: [Text('Generated Blogger Theme')]),

      // RawText(
      //   '<style amp-boilerplate="amp-boilerplate">body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style>'
      //   '<noscript><style amp-boilerplate="amp-boilerplate">body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>',
      // ),
      // OR For Better Tooling Support USE [In Future]
      Style(
        attributes: {"amp-boilerplate": "amp-boilerplate"},
        children: [
          Text(
            'body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}',
            escape: false,
          ),
        ],
      ),
      Noscript(
        children: [
          Style(
            attributes: {"amp-boilerplate": "amp-boilerplate"},
            children: [
              Text(
                'body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}',
                escape: false,
              ),
            ],
          ),
        ],
      ),

      // For AMP compliance, keep the CSS string empty ("") and use Style with `amp-custom` attribute and there put your css (limit 50KB)
      BIf(
        cond: false.toString(),
        children: [
          BSkin(
            "",
            variables: [
              //               Text('''

              // /*

              // <!-- Variable definitions -->

              // <Variable name="keycolor" description="Main Color" type="color" default="#8abc0d" value="#ee582e"/>

              // */
              //               '''),
              BVariable(
                name: "keycolor", // Variable identifier used in CSS
                description: "Main Color", // Title shown in Blogger Designer
                type: "color", // Tells Blogger to render a color picker
                defaultValue: "#8abc0d", // Default color
                value: "#ee582e",
              ),
              // TODO: Add variables and Group here
              // Disable default b:skin CSS output while preserving custom Blogger theme variables.
              // Keep the CSS string empty ("") for strict AMP validation.
            ],
          ),
        ],
      ),
      // Place Use custom styles in a <style amp-custom> tag instead (50 KB limit).
      Style(
        attributes: {
          'amp-custom': 'amp-custom',
          'id': "page-skin-1",
          "type": "text/css",
        },
        children: [
          // as we made skin:vars as vars there in top with
          // Your Custom css styles here, for example:
          Text(
            '''
          :root {
      --keycolor: <data:skin.vars.keycolor/>;
    }
           body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
           }
           h1 {
        color: var(--keycolor);
      }
      ''',
            escape: false, // This is Important as if escaped
            //then <data:skin.vars.keycolor/> will be converted and that will not work as aspected
          ),
        ],
      ),

      // here we have a Issue with amp if
      // your Earnings Tab > is Enabled from Blogger Dashboard,
      // then you need to remove the Adsense from that Earning Tab
      // and trick it to use with other methods. and also make sure you
      // don't click on that `Connect Adsense` button from Blogger Dashboard,z
    ],
    body: [const BlogLayout()],
  );

  var xml = theme.generate();
  // Reference: [JS as Comment]
  xml = xml.replaceLast(
    '</body>',
    // or use => &lt;!--</body>--&gt;&lt;/body&gt;
    '''&lt;textarea id=&#39;template_widgets_js&#39; disabled=&#39;disabled&#39; readonly=&#39;readonly&#39; hidden=&#39;hidden&#39; aria-hidden=&#39;true&#39; class=&#39;notranslate&#39;&gt;
  </body>
  &lt;/textarea&gt;
  &lt;/body&gt;''',
  );
  print(xml);
}



extension StringUtils on String {
  /// Replaces the last occurrence of [from] with [to].
  String replaceLast(Pattern from, String to) {
    final match = from.allMatches(this).lastOrNull;
    if (match == null) return this;
    return replaceRange(match.start, match.end, to);
  }
}


API Overview #

Core building blocks #

  • Component
  • DomComponent
  • Text
  • RawText
  • Fragment
  • Renderer

Blogger-specific components #

  • BSection, BWidget, BWidgetSettings, BWidgetSetting
  • BIf, BElseIf, BElse
  • BLoop, BData, BArg, BAttr, BClass
  • BInclude, BIncludable, BTag, BEval
  • BSkin
  • BClientScript

HTML helper components #

  • Standard HTML wrappers like Div plus other helpers in html_components.dart

AMP (Accelerated Mobile Pages) components #

  • Full-featured AMP HTML support under lib/src/amp/ category files.
  • 80+ AMP elements covered, including basic media (AmpImg, AmpVideo, AmpAudio), layouts (AmpCarousel, AmpBaseCarousel, AmpSidebar, AmpAccordion, AmpLightbox), social embeds (AmpYoutube, AmpInstagram), web stories (AmpStory, AmpStoryPage, AmpStoryGridLayer), dynamic bindings (AmpState, AmpList, AmpMustache), and paywalls (AmpAccess).
  • Core document layouts: AmpHtml, AmpCharset, AmpViewport, AmpCanonical.
  • Mandatory boilerplate loader elements: AmpBoilerplate, AmpRuntimeScript, AmpExtensionScript.
  • Pre-compilation Static Analysis: Integrated AmpValidator utility to audit and validate your rendered theme pages against standard AMP specifications.

Project Structure #

  • lib/blogger_theme.dart — public exports
  • lib/src/core.dart — component model and renderer
  • lib/src/blogger_components.dart — Blogger template helpers
  • lib/src/html_components.dart — HTML helper components
  • lib/src/client_script.dart — Dart-to-JS script support
  • lib/src/theme_utility.dart — theme generation utilities
  • example/main.dart — sample theme generation entrypoint

Contributing #

Contributions are welcome!


License #

blogger_theme is licensed under the MIT License. See LICENSE for details.

1
likes
160
points
209
downloads

Documentation

Documentation
API reference

Publisher

verified publisherhireflutter.uk

Weekly Downloads

A lightweight standalone Dart library to design Blogger themes with Jaspr-like syntax.

Repository (GitHub)
View/report issues

Topics

#html-generation #web #templates #blogger

Funding

Consider supporting this project:

github.com

License

MIT (license)

Dependencies

collection, path, web

More

Packages that depend on blogger_theme