From c3fd712ebd93b8135447bbbde1305d1eff545eba Mon Sep 17 00:00:00 2001 From: Jared White Date: Sun, 1 Sep 2024 12:29:15 -0700 Subject: [PATCH 1/3] First pass at revamping view layer docs --- bridgetown-website/src/_docs/components.md | 10 +- .../src/_docs/components/ruby.md | 251 +++++------------- .../src/_docs/components/view-component.md | 164 ++++++++++++ 3 files changed, 232 insertions(+), 193 deletions(-) create mode 100644 bridgetown-website/src/_docs/components/view-component.md diff --git a/bridgetown-website/src/_docs/components.md b/bridgetown-website/src/_docs/components.md index af1393cd9..f1899504b 100644 --- a/bridgetown-website/src/_docs/components.md +++ b/bridgetown-website/src/_docs/components.md @@ -13,17 +13,17 @@ Just as a web page can be thought of as the interconnected product of the three **Bridgetown provides three environments for writing components:** -### [Liquid](/docs/components/liquid) +### [Ruby](/docs/components/ruby) -Use the [Liquid template engine](/docs/template-engines/liquid) to write simple components without a lot of custom logic or for maximum compatibility with all template engines. Liquid components are not recommended when complexity is required for frontend logic. +Use a [Ruby-based template engine](/docs/template-engines/erb-and-beyond) in conjunction with a dedicated Ruby class to facilitate more comprehensive scenarios and take full advantage of Ruby's feature set and object-oriented nature. -### [Ruby](/docs/components/ruby) +### [Liquid](/docs/components/liquid) -Use a [Ruby-based template engine](/docs/template-engines/erb-and-beyond) in conjunction with a dedicated Ruby class to facilitate more comprehensive scenarios and take full advantage of Ruby's feature set and object-oriented nature. Bridgetown also supports a compatibility shim for the ViewComponent library, a popular Rails extension created by GitHub. +Use the [Liquid template engine](/docs/template-engines/liquid) to write simple components without a lot of custom logic. Liquid components are not recommended when complexity is required for frontend logic. ### [Lit (Web Components)](/docs/components/lit) -After installing the Lit Renderer plugin, you can write "hybrid" components which support both a backend lifecycle (during SSG & SSR) and a frontend lifecycle (via Hydration). This technique is recommended for components which must support a high degree of interactivity or data timeliness. You can also take full advantage of web component APIs such as the "shadow DOM" for encapsulated styling (meaning your component styles won't "leak out" and accidentally effect other parts of the website). +After installing the Lit Renderer plugin, you can write "hybrid" components which support both a backend lifecycle (during SSG & SSR) and a frontend lifecycle (via Hydration). This technique is recommended for components which must support a high degree of interactivity or data timelines. So pick your flavor and dive in, or keep reading for more conceptual overview of Bridgetown's component architecture. diff --git a/bridgetown-website/src/_docs/components/ruby.md b/bridgetown-website/src/_docs/components/ruby.md index e0859312d..9c0e425f4 100644 --- a/bridgetown-website/src/_docs/components/ruby.md +++ b/bridgetown-website/src/_docs/components/ruby.md @@ -8,7 +8,7 @@ order: 0 A component is a reusable piece of template logic that can be included in any part of the site, and a full suite of components can comprise what is often called a "design system". You can render Ruby component objects directly in your Ruby-based templates, and you can render components from within other components. This provides the basis for a fully-featured view component architecture for ERB and beyond. -Ruby components can be combined with front-end component strategies using **web components** or other JavaScript libraries/frameworks. For one particular spin on this, check out our [Lit Components](/docs/components/lit) documentation. +Ruby components can be combined with front-end component strategies using **web components** or other JavaScript libraries/frameworks. <%= toc %> @@ -16,7 +16,7 @@ Ruby components can be combined with front-end component strategies using **web Bridgetown automatically loads `.rb` files you add to the `src/_components` folder, so that's likely where you'll want to save your component class definitions. It also load components from plugins which provide a `components` source manifest. Bridgetown's component loader is based on [Zeitwerk](https://github.com/fxn/zeitwerk), so you'll need to make sure your class names and namespaces line up with your component folder hierarchy (e.g., `_components/shared/navbar.rb` should define `Shared::Navbar`.). -To create a Ruby component, all you have to do is define a `render_in` method which accepts a single `view_context` argument as well as optional block. Whatever string value you return from the method will be inserted into the template. For example: +To create the simplest of Ruby components, all you have to do is define a `render_in` method which accepts a single `view_context` argument as well as optional block. Whatever string value you return from the method will be inserted into the template. For example: ```ruby class MyComponent @@ -32,25 +32,49 @@ end output: Hello from MyComponent! ``` -To pass variables along to a component, simply write an `initialize` method. You can also use Ruby's "squiggly heredoc" syntax as a kind of template language: +The `view_context` is whichever template or component processor is in charge of rendering this object. + +Typically though, you won't be writing Ruby components as standalone objects. Introducing `Bridgetown::Component`! + +<%= render Note.new type: "warning" do %> +Bear in mind that Ruby components aren't accessible from Liquid templates. So if you need a component which can be used in either templating system, consider writing a Liquid component. [Read more information here.](/docs/components/liquid) +<% end %> + +## Use Bridgetown::Component for Template Rendering + +By subclassing `Bridgetown::Component`, you gain [the ability to write a template](/docs/templates/erb-and-beyond) in ERB, Serbea, or Streamlined. + +For template engines like ERB, all you need to do is add a template file right next to the component's `.rb` file. The template will automatically get rendered by the component (and you won't need to define a `render_in` method yourself). For example, using ERB: ```ruby -class FieldComponent +# src/_components/field_component.rb +class FieldComponent < Bridgetown::Component def initialize(type: "text", name:, label:) @type, @name, @label = type, name, label end - - def render_in(view_context) - <<~HTML - - - - - HTML - end end ``` +```erb + + + + + +``` + +Here's the same example using Serbea template syntax: + +```serb + + + + + +``` + +Rendering out the component in a parent template and passing along arguments is straightforward: + ```erb <%%= render FieldComponent.new(type: "email", name: "email_address", label: "Email Address") %> @@ -61,36 +85,37 @@ end ``` -<%= render Note.new type: "warning" do %> -Bear in mind that Ruby components aren't accessible from Liquid templates. So if you need a component which can be used in either templating system, consider writing a Liquid component. [Read more information here.](/docs/components/liquid) -<% end %> - -## Use Bridgetown::Component for Advanced Component Templates - -While squggly heredocs are nice, what most people probably want to [the ability to write a template](/docs/templates/erb-and-beyond) in ERB, Haml, Slim, or Serbea. - -You can subclass your components from `Bridgetown::Component` and then add a template file right next to the component's `.rb` file. The template will automatically get rendered by the component and you won't need to define a `render_in` method yourself. For example, if we were to translate the previous heredoc to a template-based component: +You can use Ruby's "squiggly heredoc" syntax as a template language as well! It's easy to do with our Streamlined template engine: ```ruby -# src/_components/field_component.rb -class FieldComponent < Bridgetown::Component +class FieldComponent + attr_reader :type, :name, :label + def initialize(type: "text", name:, label:) @type, @name, @label = type, name, label end + + def template + html -> { <<~HTML + + + + + HTML + } + end end ``` -```erb - - - - - -``` +Streamlined adds some special helpers so that writing properly-escaped HTML as well as rendering out a hash as attributes or looping through an array is much easier than with plain heredoc syntax. We've found that for complex interplay between Ruby & HTML code, Streamlined is easier to deal with than either ERB or Serbea. + +<%= render Note.new do %> +**But wait, there's more!** Need to add component compatibility with Rails projects? [Try our experimental ViewComponent shim](/docs/components/view-component). +<% end %> ### Content -You also have access to a `content` variable within your component .rb/template file which is the output of the block passed into the component via `render`: +Bridgetown components are provided access to a `content` variable. It's simply the output of the block passed into the component via the parent `render`: ```erb @@ -100,7 +125,7 @@ You also have access to a `content` variable within your component .rb/template - <%%= content %> + <%%= content %> ``` @@ -212,165 +237,15 @@ In addition to simply rendering a template for you, `Bridgetown::Component` prov * `render?` – if you define this method and return `false`, the component will not get rendered at all. * `before_render` – called right before the component is rendered when the view_context is known and all helpers available. -## Need Compatibility with Rails? Try ViewComponent (experimental) - -If you've used GitHub's [ViewComponent](https://viewcomponent.org) in the past, you might be thinking by now that `Bridgetown::Component` feels an awful lot like `ViewComponent::Base`. And you're right! We've _intentionally_ modeled our component class off of what we think is one of the most exciting developments in Ruby on Rails view technology in a decade. - -But we didn't stop there. Besides being able to use `Brigetown::Component` in your Bridgetown sites, you can actually use ViewComponent itself! How is this even possible?! - -By creating a compatibility shim which "fools" ViewComponent into thinking it's booted up in a Rails app when it's actually not. ViewComponent itself is mainly only reliant on the ActionView framework within Rails, so we include that along with the shim, and then you're off to the races. (Note: this functionality is still considered _experimental_.) - -Let's break it down! +## Sidecar JS/CSS Assets -### Quick Tutorial +Some of the components you write will comprise more than pure markup. You may want to affect the styling and behavior of a component as well. -First, you'll need to add the plugin to your Gemfile. In a Bridgetown project folder, run the following command: +The easiest way to do that using "vanilla" web APIs is to wrap your component in a custom element. You can then apply CSS directly to that component from a stylesheet, and even add interactivity via JavaScript. -``` -bundle add bridgetown-view-component -``` +==TODO: add HTML/CSS/JS example here== -and then add `init :"bridgetown-view-component"` to `config/initializers.rb`. +For another spin on this, check out our [Lit Components](/docs/components/lit) documentation. You can also read up on how Bridgetown's [frontend build pipeline works](/docs/frontend-assets). -Next create a `shared` folder in `src/_components` and add the following two files: - -```ruby -# src/_components/shared/header.rb -module Shared - class Header < ViewComponent::Base - include Bridgetown::ViewComponentHelpers - def initialize(title:, description:) - @title, @description = title, description - end - end -end -``` -```erb - -
-

<%%= @title %>

- - <%%= markdownify @description %> -
-``` - -Now let's set up a new layout to render our component. Add `src/_layouts/vc.erb`: - -```erb ---- -layout: default ---- - -<%%= render(Shared::Header.new( - title: resource.data.title, - description: resource.data.description - )) %> - -<%%= yield %> -``` - -Finally, update your home page (`src/index.md`) like so: - -```md ---- -layout: vc -title: ViewComponent -description: It's _here_ and it **works**! ---- - -Yay! 😃 -``` - -Now run `bin/bridgetown start`, load your website at localhost:4000, and you should see the new homepage with the `Shared::Header` ViewComponent rendered into the layout! - -### Helpers - -So far, pretty standard fare for ViewComponent, but you'll notice we had to add `include Bridgetown::ViewComponentHelpers` to the definition of our `Shared::Header` class. That's because, out of the box, ViewComponent doesn't know about any of Bridgetown's helpers. We could have injected helpers directly into the base class, but that might adversely affect components written with Rails in mind, so at least in this early phase we're including the module manually. - -<%= render Note.new do %> -As a shortcut, you could create your own base class, say `SiteViewComponent`, which inherits from `ViewComponent::Base`, include the `Bridgetown::ViewComponentHelpers` module, and then subclass all your site components from `SiteViewComponent`. -<% end %> - -### Rails Helpers - -Including `Bridgetown::ViewComponentHelpers` in a ViewComponent provides access to Bridgetown helpers within the component. However, to facilitate that, most of the default [Action View Helpers](https://guides.rubyonrails.org/action_view_helpers.html) get disabled, since many helpers rely on Rails and will not work with Bridgetown. - -`Bridgetown::ViewComponentHelpers#allow_rails_helpers` provides an API to enable supplied Action View Helpers like `ActionView::Helpers::TagHelper`: - -```ruby -class HeaderComponent < ViewComponent::Base - Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag - include Bridgetown::ViewComponentHelpers - - def call - tag.h1 content, class: "my-8 text-3xl font-bold tracking-tight text-primary-white sm:text-4xl" - end -end -``` -<%= render Note.new do %> -The Rails helpers must be included _before_ the Bridgetown View Component helpers, as shown in this example. -<% end %> - -In this example, `Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag` enables `ActionView::Helpers::TagHelper`. We can create an inline ViewComponent that leverages `tag.h1` to create an `

` element with our supplied content. - -In your template, `<%%= render HeaderComponent.new.with_content("👋") %>` would output: - -```html -

👋

-``` - -Like helpers, you can include `Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag` in a base class that your components inherit from to reduce duplication. - -### Using Primer - -[Primer](https://primer.style) is a component library and design system published by GitHub, and you can use it now with Bridgetown! However, you'll need to do a bit of extra "shim" work to get Primer view components loaded within the Bridgetown context. - -First, add the following to your Gemfile: - -```ruby -gem "railties" # required by Primer -gem "actionpack" # required by Primer -gem "primer_view_components" -``` - -Next, add the following file to your plugins folder: - -```ruby -# plugins/builders/primer_builder.rb - -require "action_dispatch" -require "rails/engine" -require "primer/view_components/engine" - -class Builders::PrimerBuilder < SiteBuilder - def build - site.config.loaded_primer ||= begin - primer_loader = Zeitwerk::Loader.new - Primer::ViewComponents::Engine.config.eager_load_paths.each do |path| - primer_loader.push_dir path - end - primer_loader.setup - Rails.application.config = Primer::ViewComponents::Engine.config - true - end - end -end -``` - -What this does is import a couple of additional Rails dependencies, set up the autoloading functionality provided by Zeitwerk, and ensure Primer's engine config is added to the Rails shim. We also want to guarantee this code only runs once when in Bridgetown's watch mode. - -Let's also add the Primer CSS link tag to your site's head: - -``` - -``` - -Now you can use Primer components in any Ruby template in your Bridgetown project! - -```erb -<%%= render(Primer::FlashComponent.new(scheme: :success)) do %> - This is a **success** flash message! -<%% end %> -``` diff --git a/bridgetown-website/src/_docs/components/view-component.md b/bridgetown-website/src/_docs/components/view-component.md new file mode 100644 index 000000000..0f70ed6ee --- /dev/null +++ b/bridgetown-website/src/_docs/components/view-component.md @@ -0,0 +1,164 @@ +--- +title: ViewComponent (Rails Compatibility Layer) +template_engine: erb +category: components +top_section: Designing Your Site +order: 0 +--- + +If you've used GitHub's [ViewComponent](https://viewcomponent.org) on existing Rails projects, you're in luck! We've created a compatibility shim which "fools" ViewComponent into thinking it's booted up in a Rails app when it's actually not. ViewComponent itself is mainly only reliant on the ActionView framework within Rails, so we include that along with the shim, and then you're off to the races. (Note: this functionality is still considered _experimental_.) + +Let's break it down. + +## Quick Tutorial + +First, you'll need to add the plugin to your Gemfile. In a Bridgetown project folder, run the following command: + +``` +bundle add bridgetown-view-component +``` + +and then add `init :"bridgetown-view-component"` to `config/initializers.rb`. + +Next create a `shared` folder in `src/_components` and add the following two files: + +```ruby +# src/_components/shared/header.rb +module Shared + class Header < ViewComponent::Base + include Bridgetown::ViewComponentHelpers + + def initialize(title:, description:) + @title, @description = title, description + end + end +end +``` + +```erb + +
+

<%%= @title %>

+ + <%%= markdownify @description %> +
+``` + +Now let's set up a new layout to render our component. Add `src/_layouts/vc.erb`: + +```erb +--- +layout: default +--- + +<%%= render(Shared::Header.new( + title: resource.data.title, + description: resource.data.description + )) %> + +<%%= yield %> +``` + +Finally, update your home page (`src/index.md`) like so: + +```md +--- +layout: vc +title: ViewComponent +description: It's _here_ and it **works**! +--- + +Yay! 😃 +``` + +Now run `bin/bridgetown start`, load your website at localhost:4000, and you should see the new homepage with the `Shared::Header` ViewComponent rendered into the layout! + +## Helpers + +So far, pretty standard fare for ViewComponent, but you'll notice we had to add `include Bridgetown::ViewComponentHelpers` to the definition of our `Shared::Header` class. That's because, out of the box, ViewComponent doesn't know about any of Bridgetown's helpers. We could have injected helpers directly into the base class, but that might adversely affect components written with Rails in mind, so at least in this early phase we're including the module manually. + +<%= render Note.new do %> +As a shortcut, you could create your own base class, say `SiteViewComponent`, which inherits from `ViewComponent::Base`, include the `Bridgetown::ViewComponentHelpers` module, and then subclass all your site components from `SiteViewComponent`. +<% end %> + +## Rails Helpers + +Including `Bridgetown::ViewComponentHelpers` in a ViewComponent provides access to Bridgetown helpers within the component. However, to facilitate that, most of the default [Action View Helpers](https://guides.rubyonrails.org/action_view_helpers.html) get disabled, since many helpers rely on Rails and will not work with Bridgetown. + +`Bridgetown::ViewComponentHelpers#allow_rails_helpers` provides an API to enable supplied Action View Helpers like `ActionView::Helpers::TagHelper`: + +```ruby +class HeaderComponent < ViewComponent::Base + Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag + include Bridgetown::ViewComponentHelpers + + def call + tag.h1 content, class: "my-8 text-3xl font-bold tracking-tight text-primary-white sm:text-4xl" + end +end +``` +<%= render Note.new do %> +The Rails helpers must be included _before_ the Bridgetown View Component helpers, as shown in this example. +<% end %> + +In this example, `Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag` enables `ActionView::Helpers::TagHelper`. We can create an inline ViewComponent that leverages `tag.h1` to create an `

` element with our supplied content. + +In your template, `<%%= render HeaderComponent.new.with_content("👋") %>` would output: + +```html +

👋

+``` + +Like helpers, you can include `Bridgetown::ViewComponentHelpers.allow_rails_helpers :tag` in a base class that your components inherit from to reduce duplication. + +## Using Primer + +[Primer](https://primer.style) is a component library and design system published by GitHub, and you can use it now with Bridgetown! However, you'll need to do a bit of extra "shim" work to get Primer view components loaded within the Bridgetown context. + +First, add the following to your Gemfile: + +```ruby +gem "railties" # required by Primer +gem "actionpack" # required by Primer +gem "primer_view_components" +``` + +Next, add the following file to your plugins folder: + +```ruby +# plugins/builders/primer_builder.rb + +require "action_dispatch" +require "rails/engine" +require "primer/view_components/engine" + +class Builders::PrimerBuilder < SiteBuilder + def build + site.config.loaded_primer ||= begin + primer_loader = Zeitwerk::Loader.new + Primer::ViewComponents::Engine.config.eager_load_paths.each do |path| + primer_loader.push_dir path + end + primer_loader.setup + Rails.application.config = Primer::ViewComponents::Engine.config + true + end + end +end +``` + +What this does is import a couple of additional Rails dependencies, set up the autoloading functionality provided by Zeitwerk, and ensure Primer's engine config is added to the Rails shim. We also want to guarantee this code only runs once when in Bridgetown's watch mode. + +Let's also add the Primer CSS link tag to your site's head: + +``` + +``` + +Now you can use Primer components in any Ruby template in your Bridgetown project! + +```erb +<%%= render(Primer::FlashComponent.new(scheme: :success)) do %> + This is a **success** flash message! +<%% end %> +``` From ff03ae63c08d272524fd3bb2d17b4490133e0509 Mon Sep 17 00:00:00 2001 From: Jared White Date: Sun, 1 Sep 2024 12:50:42 -0700 Subject: [PATCH 2/3] Some text placement fixes --- bridgetown-website/src/_docs/components.md | 12 ++++++------ bridgetown-website/src/_docs/components/ruby.md | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bridgetown-website/src/_docs/components.md b/bridgetown-website/src/_docs/components.md index f1899504b..0765bd2ef 100644 --- a/bridgetown-website/src/_docs/components.md +++ b/bridgetown-website/src/_docs/components.md @@ -9,7 +9,7 @@ Thinking of your website design as a collection of loosely-coupled, independent Component-based design systems are at the forefront of major leaps forward in the architecture used to enable agile, scalable codebases. Techniques such as "island architecture" and "hydration" have entered into the modern vernacular. We increasingly see the shift from "page-level" to "component-level" thinking as projects unfold. -Just as a web page can be thought of as the interconnected product of the three primary web technologies (HTML, CSS, & JavaScript), components are themselves individual products made out of those three technologies—or for simpler components, perhaps just one or two. Components also carry with them the concept of a "lifecycle". Understanding the lifecycle of a component on both the backend (SSR & SSG) and the frontend—and perhaps the component's "children" as well—is crucial in determining which toolset you should use to build the component. This touches on the concept we like to call "progressive generation". ([Read our tech specs intro for additional context.](/docs#more-about-the-tech-specs)) +Just as a web page can be thought of as the interconnected product of the three primary web technologies (HTML, CSS, & JavaScript), components are themselves individual products made out of those three technologies—or for simpler components, perhaps just one or two. Components also carry with them the concept of a "lifecycle". Understanding the lifecycle of a component on both the backend (SSR / SSG) and the frontend—and perhaps the component's "children" as well—is crucial in determining which toolset you should use to build the component. This touches on the concept we like to call "progressive generation". ([Read our tech specs intro for additional context.](/docs#more-about-the-tech-specs)) **Bridgetown provides three environments for writing components:** @@ -35,7 +35,7 @@ An emerging technology which has the potential to change how we approach develop As previously mentioned, a component will often encompass not just the output HTML coming from the component's logic/template, but styling via CSS, and client-side interactivity via JavaScript. -In those cases, where you place your CSS and JS code will vary depending on the environment. For Liquid and Ruby components, you will write what are called "sidecar" files which live alongside your component classes/templates. In contrast, Lit components fall under the category of Single-File Components. The logic, template, and styling is all part of the same unit of code. Lit components can be written in either vanilla JavaScript or Ruby2JS (a Ruby-like syntax and set of idioms which then transpiles to JavaScript). However, with a smidge of extra configuration, you do have the option of splitting the CSS of a Lit component out to its own sidecar file if you so choose. +In those cases, where you place your CSS and JS code will vary depending on the environment. For most components, you will write what are called "sidecar" files which live alongside your component classes/templates. In contrast, Lit components fall under the category of Single-File Components. The logic, template, and styling is all part of the same unit of code. Lit components can be written in either vanilla JavaScript or Ruby2JS (a Ruby-like syntax and set of idioms which then transpiles to JavaScript). However, with a smidge of extra configuration, you do have the option of splitting the CSS of a Lit component out to its own sidecar file if you so choose. Here's an example file structure showing all three environments in use: @@ -73,15 +73,15 @@ Regarding that last item, due to various performance concerns both on the static Ready to dive more into a particular component flavor? Let's go!

- + - Liquid + Ruby - + - Ruby + Liquid diff --git a/bridgetown-website/src/_docs/components/ruby.md b/bridgetown-website/src/_docs/components/ruby.md index 9c0e425f4..8732edd82 100644 --- a/bridgetown-website/src/_docs/components/ruby.md +++ b/bridgetown-website/src/_docs/components/ruby.md @@ -129,7 +129,7 @@ Bridgetown components are provided access to a `content` variable. It's simply t ``` -## Slotted Content +### Slotted Content New in Bridgetown 1.2, you can now provide specific named content from within the calling template to a component. If the `content` variable above could be considered the "default" slot, you'll now learn how to work with named content slots. @@ -201,7 +201,7 @@ end Don't let the naming fool you…Bridgetown's slotted content feature is not related to the concept of slots in custom elements and shadow DOM (aka web components). But there are some surface-level similarities. Many view-related frameworks provide some notion of slots (perhaps called something else like content or layout blocks), as it's helpful to be able to render named "child" content within "parent" views. <% end %> -### Helpers +## Helpers As expected, helpers are available as well just like in standard templates: @@ -230,7 +230,7 @@ class ExternalWidget < Bridgetown::Component end ``` -### Lifecycle +## Lifecycle In addition to simply rendering a template for you, `Bridgetown::Component` provides a couple lifecycle hooks: @@ -239,9 +239,9 @@ In addition to simply rendering a template for you, `Bridgetown::Component` prov ## Sidecar JS/CSS Assets -Some of the components you write will comprise more than pure markup. You may want to affect the styling and behavior of a component as well. +Some of the components you write will comprise more than pure markup. You may want to affect the styling and behavior of a component as well. For a conceptual overview of this architecture, [read our Components introduction](/docs/components#the-subtle-interplay-of-html-css--javascript). -The easiest way to do that using "vanilla" web APIs is to wrap your component in a custom element. You can then apply CSS directly to that component from a stylesheet, and even add interactivity via JavaScript. +The easiest way to write frontend component code using "vanilla" web APIs is to wrap your component in a custom element. You can then apply CSS directly to that component from a stylesheet, and even add interactivity via JavaScript. ==TODO: add HTML/CSS/JS example here== From bc0d917142613e6d8cce244206b7309dc5f0efe4 Mon Sep 17 00:00:00 2001 From: Jared White Date: Mon, 9 Sep 2024 13:51:48 -0700 Subject: [PATCH 3/3] Additional content around template engines and Streamlined --- .../src/_docs/template-engines.md | 48 +++-- .../_docs/template-engines/erb-and-beyond.md | 182 ++++++++++++++---- 2 files changed, 168 insertions(+), 62 deletions(-) diff --git a/bridgetown-website/src/_docs/template-engines.md b/bridgetown-website/src/_docs/template-engines.md index da8734da4..ac2dfea43 100644 --- a/bridgetown-website/src/_docs/template-engines.md +++ b/bridgetown-website/src/_docs/template-engines.md @@ -5,59 +5,57 @@ top_section: Designing Your Site category: template-engines --- -Bridgetown's default configured template language is **Liquid**. Liquid's simple syntax and safe execution context make it ideal for designer-led template creation. +Bridgetown's default configured template language is **ERB** (Embedded RuBy). If you're familiar with PHP or other string-based template syntaxes in various programming languages, you should feel right at home. -However, you can use a variety of different template engines within Bridgetown simply by using the appropriate file extension (aka `.erb`), or by specifying the template engine in your resource's front matter. Out of the box, Bridgetown provides support for both **ERB** and **Serbea**, and you can also use Haml or Slim by installing additional plugins. +However, you can use a variety of different template engines within Bridgetown simply by using the appropriate file extension (aka `.liquid` for Liquid), or by specifying the template engine in your resource's front matter. Out of the box, Bridgetown provides support for both **ERB**, **Serbea**, and **Liquid**, as well as a pure Ruby template type enhanced by **Streamlined**. -To configure a new Bridgetown site to use a language other than Liquid as the default template engine regardless of file extension, use the `-t`/`--templates` option when running `bridgetown new`. +You can mix 'n' match template types easily. For example, Liquid's simple syntax and safe execution context make it ideal for designer-led template creation, so you could use Liquid for layouts but stick to ERB for code-intensive pages and other resources. -For documentation on how to use Liquid or Ruby-based syntax in Bridgetown content and templates: +To configure a new Bridgetown site to use a language other than ERB as the default template engine regardless of file extension, use the `-t`/`--templates` option when running `bridgetown new`. + +For documentation on how to use Ruby or Liquid syntax in Bridgetown content and templates:

- + - Liquid + ERB, Serbea, & More - + - ERB, Serbea, & More + Liquid

-## Why Switch from Liquid? - -Liquid is a great way to start out if you're not that familiar with Ruby, because it feels more akin to template engines like Mustache, Jinja, Nunjucks, Twig, and so forth. Simple tags and filters, along with loops and conditional statements, let you construct templates quickly and easily. - -But if you need more power (especially when writing [components](/docs/components)) or you're already familiar with Ruby and engines such as ERB, then the cognitive overhead to learn and stick with Liquid can actually become a hindrance. In addition, it's an important goal for Bridgetown to integrate well with a development workflow which already incorporates the [Ruby on Rails framework](https://rubyonrails.org). Or perhaps you're looking to switch from [Middleman](https://middlemanapp.com) which uses ERB by default. - -In any case, the ability to "pick your flavor" of template engines on a site-by-site or file-by-file basis is one of Bridgetown's core strengths as a web framework. - ## Per-file Engine Configuration -When the default Liquid template engine is configured, Bridgetown processes files through Liquid even when they don't have a `.liquid` extension. For example, `posts.json` or `about.md` or `authors.html` will all get processed through Liquid during the build process. +When the default ERB template engine is configured, Bridgetown processes files through ERB even when they don't have an `.erb` extension. For example, `posts.json` or `about.md` or `authors.html` will all get processed through ERB during the build process (assuming front matter is present as is required by all resources). -As an initial step, you can use a different template engine based on extension alone. For example, `authors.erb` would get processed through ERB and output as `authors.html`. But there are a couple of drawbacks to that approach. If you wanted `posts.erb` to be output as `posts.json`, you'd have to manually set a `permalink` in your front matter. In addition, if you wanted to write a document in Markdown but add ERB tags in as well, you couldn't do that via file extension alone because the Markdown converter looks for files ending in `.md`. +As an initial step, you can use a different template engine based on extension alone. For example, `authors.liquid` would get processed through Liquid and output as `authors.html`. But there are a couple of drawbacks to that approach. If you wanted `posts.liquid` to be output as `posts.json`, you'd have to manually set a `permalink` in your front matter. In addition, if you wanted to write a document in Markdown but add Liquid tags in as well, you couldn't do that via file extension alone because the Markdown converter looks for files ending in `.md`. -So instead of doing that, you can switch template engines directly. All you need to do is use the `template_engine` front matter variable. You can do this on any page, document, or layout. For example, you could write `posts.json` but add `template_engine: erb` to the front matter, and then you'd be all set. Write your template using ERB syntax, get `posts.json` on output. In the Markdown scenario, you could still author `about.md` while adding ERB tags to the content, and it would work exactly as you expect. +So instead of doing that, you can switch template engines directly. All you need to do is use the `template_engine` front matter variable. You can do this on any page, document, or layout. For example, you could write `posts.json` but add `template_engine: liquid` to the front matter, and then you'd be all set. Write your template using Liquid syntax, get `posts.json` on output. In the Markdown scenario, you could still author `about.md` while adding Liquid tags to the content, and it would work exactly as you expect. ## Front Matter Defaults -Besides adding `template_engine` directly in your file's front matter, you could use [front matter defaults](/docs/content/front-matter-defaults) to specify a template engine for a folder or folder tree or files which match a particular "glob pattern". That way you could, say, use Liquid for most of the site but use ERB just for a certain group of files. +Besides adding `template_engine` directly in your file's front matter, you could use [front matter defaults](/docs/content/front-matter-defaults) to specify a template engine for a folder or folder tree or files which match a particular "glob pattern". That way you could, say, use ERB for most of the site but use Serbea just for a certain group of files. ## Site-wide Configuration -Most likely, however, you'll want to switch your site wholesale from one engine to another. That's where `bridgetown.config.yml` comes in. Simply add `template_engine: erb` right in your config, and suddenly *everything* will get processed through ERB regardless of file extension. (This will have been done for you if you used the `-t` option when running `bridgetown new`.) Serbea works in the same manner: `template_engine: serbea`. Write HTML, XML, Markdown, JSON, CSV, whatever you like—and _still_ access the full power of your Ruby template language of choice. You don't even need to give up on Liquid completely—just save files with `.liquid` or use `template_engine: liquid` front matter. +Most likely, however, you'll want to switch your site wholesale from one engine to another. That's where `config/initializers.rb` (or `bridgetown.config.yml`) comes in. Let's say you want to default to Serbea. Simply add `template_engine :serbea` right in your config, and suddenly *everything* will get processed through Serbea regardless of file extension. (This will have been done for you if you used the `-t` option when running `bridgetown new`.) Liquid works in the same manner: `template_engine: liquid`. Write HTML, XML, Markdown, JSON, CSV, whatever you like—and _still_ access the full power of your template engine of choice. It's worth noting that by combining Markdown, ERB/Serbea, components, and frontend JavaScript "sprinkles" (or "spices" as we like to say), you can author extremely sophisticated documents which boast stunning performance and SEO scores while at the same time providing impressive interactivity in the browser. This is quickly becoming a "best practice" in the web development industry, and Bridgetown will help get you there. -{%@ Note type: :warning do %} - While it's true you can use ERB or Serbea site-wide, the [Haml](https://github.com/bridgetownrb/bridgetown-haml) and [Slim](https://github.com/bridgetownrb/bridgetown-slim) plugins do _not_ allow site-wide configuration. That's because both Haml and Slim start with pure HTML/XML output using special syntax, and if you want to do something else like write Markdown or JSON, you'll have to use their "embedded" language support. Read their documentation for further details. -{% end %} +## Why Did Bridgetown Switch from Liquid? + +Prior to Bridgetown 2.0, Liquid was the default template type. Liquid feels more akin to template engines like Mustache, Jinja, Nunjucks, Twig, and so forth—and it was the only default option in Bridgetown's progenitor, Jekyll. + +But most Bridgetown developers will need more power (especially when writing [components](/docs/components)) or may already be familiar with Ruby and engines such as ERB. And some developers are looking to switch from [Middleman](https://middlemanapp.com) which uses ERB by default. Thus it makes sense to standardize around ERB. + +In any case, the ability to "pick your flavor" of template engines on a site-by-site or file-by-file basis is one of Bridgetown's core strengths as a web framework. ## It's Up to You -Regardless of which template engine you pick, whether it's [Liquid](/docs/template-engines/liquid), [ERB, Serbea](/docs/template-engines/erb-and-beyond), or something else, Bridgetown has got you covered. We continue to look for ways to make switching engines easier while reducing the number of "sharp edges" that can arise to differences in how various template engines process content, so please don't hesitate to [let us know](/community) if you run in to any issues. +Regardless of which template engine you pick, whether it's [ERB / Serbea / Streamlined](/docs/template-engines/erb-and-beyond), [Liquid](/docs/template-engines/liquid), or something else, Bridgetown has got you covered. We continue to look for ways to make switching engines easier while reducing the number of "sharp edges" that can arise to differences in how various template engines process content, so please don't hesitate to [let us know](/community) if you run in to any issues. diff --git a/bridgetown-website/src/_docs/template-engines/erb-and-beyond.md b/bridgetown-website/src/_docs/template-engines/erb-and-beyond.md index b76a8794d..d291f1cac 100644 --- a/bridgetown-website/src/_docs/template-engines/erb-and-beyond.md +++ b/bridgetown-website/src/_docs/template-engines/erb-and-beyond.md @@ -1,40 +1,48 @@ --- -title: ERB and Beyond +title: Ruby-Based Template Types order: 0 top_section: Designing Your Site category: template-engines template_engine: erb --- -Bridgetown's implementation language, Ruby, has a rich history of providing [ERB](https://docs.ruby-lang.org/en/2.7.0/ERB.html) for templates and view layers across a wide variety of tools and frameworks. Other Ruby-based template languages such as [Haml](https://haml.info), [Slim](http://slim-lang.com), and [Serbea](https://www.serbea.dev) garner enthusiastic usage as well. +Bridgetown's implementation language, Ruby, has a rich history of providing "Embedded RuBy" aka ERB templates and view layers across a wide variety of tools and frameworks. In addition to ERB, Bridgetown provides two additional Ruby-based template types: [Serbea](https://www.serbea.dev) (a superset of ERB), and **Streamlined** (which is a form of pure Ruby code). -Bridgetown makes it easy to add both ERB-based and Serbea-based templates and components to any site. In additional, there are plugins you can easily install for Haml and Slim support. Under the hood, Bridgetown uses the [Tilt gem](https://github.com/rtomayko/tilt) to load and process these Ruby templates. +<%= render Note.new do %> + New Bridgetown sites are configured with ERB by default. But you can start off a project with another engine like Serbea or Liquid [with just a simple configuration change](/docs/template-engines#site-wide-configuration). You can also mix 'n' match template types in a single project! + + Note that Streamlined itself can't be specified as a "template engine" because it's not string-based (so you couldn't "embed" Streamlined code in, say, a Markdown file). Streamlined works well as an _augmentation_ to a site configured with either ERB or Serbea. +<% end %> <%= render Note.new do %> - Interested in switching your entire site to use ERB or Serbea by default? [It's possible to do that with just a simple configuration change.](/docs/template-engines#site-wide-configuration) + Under the hood, Bridgetown uses the [Tilt gem](https://github.com/rtomayko/tilt) to load and process ERB & Serbea. Plugin authors can leverage Tilt to add support for other template types. <% end %> <%= toc %> -## Usage +## ERB Basics -For ERB, simply define a page/document with an `.erb` extension, rather than `.html`. You'll still need to add front matter to the top of the file (or at the very least two lines of triple dashes `---`) for the file to get processed. In the Ruby code you embed, you'll be interacting with the underlying Ruby API for Bridgetown objects (aka `Bridgetown::Page`, `Bridgetown::Site`, etc.). Here's an example: +For ERB, resources are typically saved with an `.erb` extension. Other extensions like `.html` or `.md` will be processed through ERB unless another template engine is configured. To embed Ruby code in your template, use the delimiters `<%% %>` for code blocks and `<%%= %>` for output expressions. + +As with all resources, you'll need to add front matter to the top of the file (or at the very least two lines of triple dashes `---`) for the file to get processed. In the Ruby code you embed, you'll be interacting with the underlying Ruby API for Bridgetown objects (aka `Bridgetown::Page`, `Bridgetown::Site`, etc.). Here's an example: ```eruby --- title: I'm a page! --- -

<%%= resource.data.title %>

+

<%%= data.title %>

Welcome to <%%= Bridgetown.name.to_s %>!

Authored by <%%= site.data.authors.first.name %>
``` -Front matter is accessible via the `data` method on pages, posts, layouts, and other documents. Site config values are accessible via the `site.config` method, and loaded data files via `site.data` as you would expect. +Front matter is accessible via the `data` method on pages, posts, layouts, and other documents. The resource itself is available via `resource`. Site config values are accessible via the `site.config` method, and loaded data files via `site.data` as you would expect. -In addition to `site`, you can also access the `site_drop` object which will provide similar access to various data and config values similar to the `site` variable in Liquid. +<%= render Note.new do %> + In addition to `site`, you can also access the `site_drop` object which will provide similar access to various data and config values similar to the `site` variable in Liquid. +<% end %> If you need to escape an ERB tag (to use it in a code sample for example), use two percent signs: @@ -49,7 +57,7 @@ And my <%%%= "ERB code sample" %> You can easily loop through resources in a collection: ```eruby -<%% collections.posts.resources.each do |post| %> +<%% collections.posts.each do |post| %>
  • <%%= post.data.title %>
  • <%% end %> ``` @@ -57,28 +65,28 @@ You can easily loop through resources in a collection: Or using the [paginator](/docs/content/pagination), along with the `link_to` helper: ```eruby -<%% paginator.resources.each do |post| %> +<%% paginator.each do |post| %>
  • <%%= link_to post.data.title, post %>
  • <%% end %> ``` ### Serbea -Serbea is a "superset" of ERB which provides the same benefits as ERB but uses curly braces like Liquid `{% %}` or `{{ }}` and adds support for filters and render directives. Use the file extension `.serb`. Here's an example of the above ERB code rewritten in Serbea: +Serbea is a "superset" of ERB which provides the same benefits as ERB but uses curly braces: `{% %}` or `{{ }}` and adds support for filters and render directives. Use the file extension `.serb`. Here's an example of the above ERB code rewritten in Serbea: ```serb -{% collections.posts.resources.each do |post| %} +{% collections.posts.each do |post| %}
  • {{ post.data.title }}
  • {% end %} ---- -{% paginator.resources.each do |post| %} +{% paginator.each do |post| %}
  • {{ post.data.title | link_to: post }}
  • {% end %} ``` -Notice this is using the Liquid-like filter syntax for `link_to`. You can use this kind of syntax with _any_ helpers available in all Ruby templates, as well as methods on objects themselves. Examples: +Notice this is using the filter syntax similar to Liquid for `link_to`. You can use this kind of syntax with _any_ helpers available in all Ruby templates, as well as methods on objects themselves. Examples: ```serb {{ resource.data.description | markdownify }} @@ -117,14 +125,14 @@ For details on HTML output safety, see below (Serbea and ERB differ slightly on ## Dot Access Hashes -Data hashes support standard hash key access, but most of the time you can use "dot access" instead for a more familar look. For example: +Data hashes support standard hash key access, but most of the time you can use "dot access" instead for a more familiar look. For example: ```eruby <%%= post.data.title %> (but <%%= post.data[:title] %> or <%%= post.data["title"] %> also work) <%%= resource.data.author %> -<%%= site.data.authors.lakshmi.twitter.handle %> +<%%= site.data.authors.lakshmi.mastodon.handle %> <%% # You can freely mix hash access and dot access: %> @@ -145,7 +153,7 @@ To include a partial in your ERB template, add a `_partials` folder to your sour title: I'm a page! --- -

    <%%= resource.data.title %>

    +

    <%%= data.title %>

    Welcome to <%%= Bridgetown.name %>!

    @@ -171,7 +179,7 @@ Partials also support capture blocks, which can then be referenced via the `cont ## Rendering Ruby Components -For better encapsulation and reuse of Ruby-based templates as part of a "design system" for your site, we encourage you to write Ruby components using either `Bridgetown::Component` or GitHub's ViewComponent library. [Check out the documentation and code examples here](/docs/components/ruby). +For better encapsulation and reuse of Ruby-based templates as part of a "design system" for your site, we encourage you to write Ruby components using `Bridgetown::Component`. [Check out the documentation and code examples here](/docs/components/ruby). ## Liquid Filters, Tags, and Components @@ -202,7 +210,7 @@ In addition to using Liquid helpers, you can also render [Liquid components](/do ## Layouts -You can add an `.erb` layout and use it in much the same way as a Liquid-based layout. You can freely mix'n'match ERB layouts with Liquid-based documents and Liquid-based layouts with ERB documents. +You can add an `.erb` layout to the `_layouts` folder for use by resources even other layouts. You can freely mix 'n' match ERB layouts with Liquid-based documents and Liquid-based layouts with ERB documents. `src/_layouts/testing.erb` @@ -212,7 +220,7 @@ layout: default somevalue: 123 --- -

    <%%= resource.data.title %>

    +

    <%%= data.title %>

    An ERB layout! <%%= layout.name %> / somevalue: <%%= layout.data.somevalue %>
    @@ -237,7 +245,7 @@ If in your layout or a layout partial you need to output the paths to your front ## Markdown -When authoring a document using ERB, you might find yourself wanting to embed some Markdown within the document content. That's easy to do using a `markdownify` block: +When authoring a resource using ERB that's not itself a Markdown file (`.md`), you might find yourself wanting to embed some Markdown within the content. That's easy to do using a `markdownify` block: ```eruby <%%= markdownify do %> @@ -254,13 +262,13 @@ You can also pass in any string variable as a method argument: <%%= markdownify some_string_var %> ``` -Alternatively, you can author a document with a `.md` extension and configure it via `template_engine: erb` to get processed through ERB. (Continue reading for additional information.) - ## Extensions and Permalinks -Sometimes you may want to output a file that doesn't end in `.html`. Perhaps you want to create a JSON index of a collection, or a special XML feed. If you have familiarity with other Ruby site generators or frameworks, you might instinctively reach for the solution where you use a double extension, say, `posts.json.erb` to indicate the final extension (`json`) and the template type (`erb`). +Sometimes you may want to output a file that doesn't end in `.html` when published. Perhaps you want to create a JSON index of a collection, or a special XML feed. If you have familiarity with other Ruby site generators or frameworks, you might instinctively reach for the solution where you use a double extension, say, `posts.json.erb` to indicate the final extension (`json`) and the template type (`erb`). + +Bridgetown doesn't support double extensions but rather provides a couple of alternative mechanisms to specify your template engine of choice. The first option is to utilize the default ERB processing, so your `posts.json` file will be processed through ERB automatically as long as it includes the triple-dashes front matter. -Bridgetown doesn't support double extensions but rather provides a couple of alternative mechanisms to specify your template engine of choice. The first option is to set the file's permalink using front matter. Here's an example of `posts.json.erb` using a [custom permalink](/docs/content/permalinks): +The second option is to set the file's permalink using front matter. Here's an example of a `posts.erb` file using a [custom permalink](/docs/content/permalinks): ```eruby --- @@ -281,8 +289,6 @@ permalink: /posts.json This ensures the final relative URL will be `/posts.json`. (Of course you can also set the permalink to anything you want, regardless of the filename itself.) -The second option is to switch template engines using front matter or site-wide configuration. That will allow you to write `posts.json` and have it use ERB automatically (instead of the default which is Liquid). [Find out more about choosing template engines here.](/docs/template-engines) - ## Link and URL Helpers The `link_to` and `url_for` helpers let you create anchor tags which will link to any source page/document/static file (or any relative/absolute URL you pass in). @@ -324,7 +330,7 @@ In order to simplify more complex lists of HTML attributes you may also pass a h `link_to` uses [`html_attributes`](#html_attributes) under the hood to handle this converstion. -You can also pass relative or aboslute URLs to `link_to` and they'll just pass-through to the anchor tag without change: +You can also pass relative or absolute URLs to `link_to` and they'll just pass-through to the anchor tag without change: ```eruby <%%= link_to "Visit Bridgetown", "https://www.bridgetownrb.com" %> @@ -405,9 +411,7 @@ Within the hook, you can call `slot.context` to access the definition context fo ### `html_attributes` -_Available as part of the bundled Streamlined gem._ - -`html_attributes` allows you to pass a hash and have it converted to a string of HTML attributes: +`html_attributes` is a helper provided by Streamlined, but you can use it in any Ruby template type. It allows you to pass a hash and have it converted to a string of HTML attributes: ```eruby

    >Hello, World!

    @@ -546,11 +550,115 @@ str = "

    Escape me!

    " {%= escape(str) %} ``` -## Haml and Slim +## Streamlined + +Streamlined is a new Ruby template type introduced in Bridgetown 2.0. It allow you to embed HTML templates in pure Ruby code using "squiggly heredocs" along with a set of helpers to ensure output safety (proper escaping) and easier interplay between HTML markup and Ruby code. And on average it executes at 1.5x the speed of ERB, making it a good performance choice for large builds. + +You can use Streamlined directly in resources saved as pure Ruby (`.rb`), as well as in Bridgetown components. Here's an example of what that looks like: + +```ruby +class SectionComponent < Bridgetown::Component + def initialize(variant:, heading:, **options) + @variant, @heading, @options = variant, heading, options + end + + def heading + <<~HTML +

    #{text -> { @heading }}

    + HTML + end + + def template + html -> { <<~HTML +
    + #{html -> { heading }} + + #{html -> { content }} + +
    + HTML + } + end +end +``` + +A few things going on here: + +* The `template` method is a standard part of Bridgetown's component system, and here it's being leveraged to render HTML via Streamlined. +* The `html` method's only argument is a stabby lambda (`->`) which in term contains a squiggly heredoc. (_Isn't Ruby terminology fun?_ 😅) +* Within the heredoc, there's a use of the `html_attributes` helper to convert keyword arguments/hashes into HTML attributes, along with additional embeds of Ruby utilizing `html`. +* On top of that, we're able to break our overall template up by defining a "partial" elsewhere (the `heading` method). Calling out to other Ruby methods to incrementally build up HTML markup is a key feature of Streamlined, and offers a DX reminiscent of JavaScript's tagged template literals or JSX. +* The `text` method escapes all values unless they've been explicitly marked as "HTML safe", whereas `html` simply outputs values without preemptive escaping. This requires the template author to think clearly about escaping rules. Default to always using `text` unless you know you're outputting vetted HTML code. + +Beyond these patterns, Streamlined has another couple tricks up its sleeve. You can break up template code into multiple `render` passes and also render external components: + +```ruby +def template + render html -> { <<~HTML +

    I am HTML markup.

    + HTML + } + + render AnotherComponent.new if @should_render_this + + render html ->{ <<~HTML +

    I am more HTML markup.

    + HTML + } +end +``` + +You can even embed rendering logic inside of `render` blocks: + +```ruby +def template + render html -> { <<~HTML +

    I am HTML markup.

    + HTML + } + + render do + render AnotherComponent.new + + render html ->{ <<~HTML +

    I am more HTML markup.

    + HTML + } + end if @should_render_more_stuff +end +``` + +Rendering blocks can be nested as well. It's all part of allowing your markup generation to become more modular. -Bridgetown comes with ERB support out-of-the-box, but you can easily add support for either Haml or Slim by installing our officially supported plugins. +Looping over an array or hash within a heredoc is made easier by the `html_map` helper: -* [`bridgetown-haml`](https://github.com/bridgetownrb/bridgetown-haml){:rel="noopener"} -* [`bridgetown-slim`](https://github.com/bridgetownrb/bridgetown-slim){:rel="noopener"} +```ruby +def template + html -> { <<~HTML + + HTML + } +end +``` + +<%= render Note.new(type: :warning) do %> + While Ruby heredocs can use any uppercase text as delimiters, Streamlined requires you to use `HTML`. It's helpful for syntax highlighting in many code editors, and it's also relevant to linting as explained below. +<% end %> -All you'd need to do is run `bundle add bridgetown-haml` (or `bridgetown-slim`) and add `init :"bridgetown-haml"` or `init :"bridgetown-slim"` to `config/initializers.rb`, and then you can immediately start using `.haml` or `.slim` pages, layouts, partials, and [components](/docs/components/ruby) in your Bridgetown site. +### Enforcing Streamlined helpers using Rubocop + +Streamlined provides a Rubocop linter to make sure template authors are utilizing the `text`, `html`, etc. helpers in HTML heredocs. + +==TODO: write about config here== + +<%= render Note.new do %> +**Q:** Why does Streamlined rely on heredocs which are actually just strings? Why doesn't Streamlined use a special Ruby DSL similar to other tools like Phlex, Papercraft, or Arbre? + +**A:** Because I like writing HTML. ☺️ Beyond that, the value of using template syntaxes which are easily compatible with the vast ecosystem of HTML on the web cannot be overstated. (This also represents an effort to approximate JavaScript's "tagged template literals" in Ruby—an experience already appealing to many frontend developers.) +<% end %>