blogger_theme 6.0.0
blogger_theme: ^6.0.0 copied to clipboard
A lightweight standalone Dart library to design Blogger themes with Jaspr-like syntax.
🚀 blogger_theme #
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, andText. - Pure Dart implementation with a small set of runtime dependencies and no UI framework dependency.
- Flexible argument order for DOM helpers: pass the children list or the attribute map first, such as
div([child]),div({'class': 'content'}, [child]), ordiv([child], {'class': 'content'}). - 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
BClientScriptfor Dart-to-JS inline script injection. - Flexible theme export using
BloggerTheme.generate().
Installation #
Add blogger_theme to your pubspec.yaml:
dependencies:
blogger_theme: ^6.0.0
Or add it directly with:
dart pub add blogger_theme
Run:
dart pub get
References #
- Documentation: https://bloggerquickstart.blogspot.com/
- Video tutorials: https://youtube.com/antinna
- Sponsor the project: https://github.com/sponsors/manishmg3994
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(
{'class': 'wrapper-pane'},
[
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(
[
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
/// => <!--</body>--></body>
/// => 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( {"expr:charset": "data:blog.encoding"}),
script(
{
'async': 'async',
'src': 'https://cdn.ampproject.org/v0.js',
},
),
meta(
{
"name": "viewport",
"content": "width=device-width,minimum-scale=1,initial-scale=1",
},
),
link(
{"rel": "canonical", "expr:href": "data:blog.canonicalUrl"},
),
link(
{
"rel": "amphtml",
"expr:href": 'data:blog.canonicalUrl params { m: "1" }',
},
),
title( [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>',
// ),
// The style component provides structured AMP boilerplate markup.
style(
{"amp-boilerplate": "amp-boilerplate"},
[
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(
[
style(
{"amp-boilerplate": "amp-boilerplate"},
[
Text(
'body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}',
escape: false,
),
],
),
],
),
// For AMP compliance, keep the CSS string empty ("") and put custom CSS
// in a style element with the `amp-custom` attribute (limit 50 KB). and amp-script can't exceed 10000 bytes
BIf(
false.toString(),
[
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 custom styles in a <style amp-custom> tag instead (50 KB limit).
style(
{
'amp-custom': 'amp-custom',
'id': "page-skin-1",
"type": "text/css",
},
[
// The skin variable above is available as data:skin.vars.keycolor.
// 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 will not work as expected
),
],
),
// There is an issue with AMP if
// your Earnings Tab > is Enabled from Blogger Dashboard,
// then you need to remove the Adsense from that Earning Tab
// and use another method. Also make sure you do not click the
// `Connect Adsense` button in the Blogger dashboard.
],
body: [const BlogLayout()],
);
var xml = theme.generate();
// Reference: [JS as Comment]
xml = xml.replaceLast(
'</body>',
// or use => <!--</body>--></body>
'''<textarea id='template_widgets_js' disabled='disabled' readonly='readonly' hidden='hidden' aria-hidden='true' class='notranslate'>
</body>
</textarea>
</body>''',
);
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 #
ComponentDomComponentTextRawTextFragmentRenderer
Blogger-specific components #
BSection,BWidget,BWidgetSettings,BWidgetSettingBIf,BElseIf,BElseBLoop,BData,BArg,BAttr,BClassBInclude,BIncludable,BTag,BEvalBSkinBClientScript
HTML helper components #
- Standard HTML helpers such as
div,h1,meta,script,style, andtitle, split across the files inlib/src/dom/html/.
AMP (Accelerated Mobile Pages) components #
- Full-featured AMP HTML support under
lib/src/dom/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 AMP preflight:
AmpValidatorperforms lightweight checks for common AMP document requirements in rendered theme pages. It is not a replacement for the official AMP validator.
Project Structure #
lib/blogger_theme.dart— public exportslib/src/framework/framework.dart— component modellib/src/framework/helpers/dom_render.dart— XML rendererlib/src/dom/blogger/blogger_components.dart— Blogger template helperslib/src/dom/html/— HTML helper componentslib/src/dom/amp/— AMP component helpers and preflight validatorlib/src/client_script.dart— Dart-to-JS script supportlib/src/theme_utility.dart— theme generation utilitiesexample/main.dart— sample theme generation entrypoint
Contributing #
Contributions are welcome!
- Found a bug? Open an issue: https://github.com/antinna/blogger_theme/issues
- Want to improve the library? Fork the repo and submit a pull request.
License #
blogger_theme is licensed under the MIT License. See LICENSE for details.