Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Models and Resources can now be rendered by Roda automatically #845

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions bridgetown-core/lib/bridgetown-core/concerns/site/ssr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@ def enable_ssr

def ssr_setup(&block)
config.serving = true

Bridgetown::Hooks.trigger :site, :pre_read, self
ssr_first_read
Bridgetown::Hooks.trigger :site, :post_read, self

block&.call(self) # provide additional setup hook

return if Bridgetown.env.production?

Bridgetown::Watcher.watch(self, config, &block)
end

def ssr_first_read
Bridgetown::Hooks.trigger :site, :pre_read, self
defaults_reader.tap do |d|
d.path_defaults.clear
Expand All @@ -43,12 +56,13 @@ def ssr_setup(&block)
coll.read
self.data = coll.merge_data_resources
end
Bridgetown::Hooks.trigger :site, :post_read, self

block&.call(self) # provide additional setup hook
return if Bridgetown.env.production?

Bridgetown::Watcher.watch(self, config, &block)
# This part is handled via a hook so it is supported by the SSR "soft reset"
Bridgetown::Hooks.register_one :site, :post_read, reloadable: false, priority: :high do
reader.read_directories
reader.sort_files!
reader.read_collections
end
end

def disable_ssr
Expand Down
6 changes: 4 additions & 2 deletions bridgetown-core/lib/bridgetown-core/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def read_collections
site.collections.each_value do |collection|
next if collection.data?

collection.read
collection.read unless site.ssr? && collection.metadata.skip_for_ssr
end
end

Expand Down Expand Up @@ -78,7 +78,7 @@ def read_directories(dir = "")

retrieve_dirs(dir, entries_dirs)
retrieve_pages(dir, entries_pages)
retrieve_static_files(dir, entries_static_files)
retrieve_static_files(dir, entries_static_files) unless site.ssr?
end

# Recursively traverse directories with the read_directories function.
Expand All @@ -101,6 +101,8 @@ def retrieve_dirs(dir, entries_dirs)
# @param entries_pages [Array<String>] page paths in the directory
# @return [void]
def retrieve_pages(dir, entries_pages)
return if site.ssr? && site.collections.pages.metadata.skip_for_ssr

entries_pages.each do |page_path|
site.collections.pages.read_resource(site.in_source_dir(dir, page_path))
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def setup_loaders(autoload_paths = []) # rubocop:todo Metrics/AbcSize, Metrics/C
loader.enable_reloading if reloading_enabled?(load_path)
loader.ignore(File.join(load_path, "**", "*.js.rb"))
loader.ignore(
File.join(File.expand_path(config[:islands_dir], config[:source]), "routes")
File.join(File.expand_path(config[:islands_dir], config[:source]), "**", "routes")
)
config.autoloader_collapsed_paths.each do |collapsed_path|
next unless collapsed_path.starts_with?(load_path)
Expand Down
11 changes: 11 additions & 0 deletions bridgetown-core/lib/roda/plugins/bridgetown_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ def self.load_dependencies(app) # rubocop:disable Metrics
"500 Internal Server Error"
end

# This lets us return models or resources directly in Roda response blocks
app.plugin :custom_block_results

app.handle_block_result Bridgetown::Model::Base do |result|
result.render_as_resource.output
end

app.handle_block_result Bridgetown::Resource::Base do |result|
result.transform!.output
end

ExceptionPage.class_eval do # rubocop:disable Metrics/BlockLength
def self.css
<<~CSS
Expand Down
17 changes: 17 additions & 0 deletions bridgetown-core/test/ssr/config/initializers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,21 @@
init :local_ssr_init, require_gem: false
init :local_ssr_init, require_gem: false
init :local_ssr_init, require_gem: false

collections do
posts do
skip_for_ssr true
end
end

# poor man's Inspector plugin
hook :resources, :post_render do |resource|
next unless resource.site.root_dir.end_with?("test/ssr") # ugly hack or else test suite errors

document = Nokogiri.HTML5(resource.output)
document.css("p.test").each do |paragraph|
paragraph.inner_html = paragraph.inner_html.upcase
end
resource.output = document.to_html
end
end
2 changes: 1 addition & 1 deletion bridgetown-core/test/ssr/config/local_ssr_init.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Bridgetown.initializer :local_ssr_init do |config|
config.init :ssr do
setup ->(site) do
setup -> site do # rubocop:disable Layout/SpaceInLambdaLiteral, Style/StabbyLambdaParentheses
site.data.iterations ||= 0
site.data.iterations += 1
end
Expand Down
16 changes: 16 additions & 0 deletions bridgetown-core/test/ssr/server/routes/render_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

class Routes::RenderResource < Bridgetown::Rack::Routes
route do |r|
# route: GET /render_resource
r.get "render_resource" do
# Roda should know how to autorender the resource
bridgetown_site.collections.pages.resources.find { _1.id == "repo://pages.collection/index.md" }
end

r.get "render_model" do
# Roda should know how to autorender the model as a resource
Bridgetown::Model::Base.find("repo://pages.collection/test_doc.md")
end
end
end
3 changes: 3 additions & 0 deletions bridgetown-core/test/ssr/src/index.md
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
---
---

Hello **world**!
6 changes: 6 additions & 0 deletions bridgetown-core/test/ssr/src/test_doc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
title: Test Document
---

This is a _test_.
{: .test}
14 changes: 14 additions & 0 deletions bridgetown-core/test/test_ssr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,19 @@ def site
assert last_response.ok?
assert_equal("Redirected!", last_response.body)
end

should "return rendered resource" do
get "/render_resource"

assert last_response.ok?
assert_includes last_response.body, "<p>Hello <strong>world</strong>!</p>"
end

should "return model as rendered resource" do
get "/render_model"

assert last_response.ok?
assert_includes last_response.body, "<p class=\"test\">THIS IS A <em>TEST</em>.</p>"
end
end
end
1 change: 1 addition & 0 deletions bridgetown-routes/.rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ inherit_from: ../.rubocop.yml
AllCops:
Exclude:
- "*.gemspec"
- "test/ssr/src/_islands/**/*.rb"
11 changes: 9 additions & 2 deletions bridgetown-routes/lib/bridgetown-routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,20 @@ module Routes
end

# @param config [Bridgetown::Configuration::ConfigurationDSL]
Bridgetown.initializer :"bridgetown-routes" do |config|
Bridgetown.initializer :"bridgetown-routes" do |
config,
additional_source_paths: [],
additional_extensions: []
|
config.init :ssr # ensure we already have touchdown!

config.routes ||= {}
config.routes.source_paths ||= ["_routes", "#{config.islands_dir}/routes"]
config.routes.source_paths ||= ["_routes"]
config.routes.extensions ||= %w(rb md serb erb liquid)

config.routes.source_paths += Array(additional_source_paths)
config.routes.extensions += Array(additional_extensions)

config.only :server do
require_relative "bridgetown-routes/manifest_router"
end
Expand Down
25 changes: 19 additions & 6 deletions bridgetown-routes/lib/bridgetown-routes/manifest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def generate_manifest(site) # rubocop:todo Metrics/AbcSize, Metrics/CyclomaticCo
new_manifest = []
routable_extensions = site.config.routes.extensions.join(",")

site.config.routes.source_paths.each do |routes_dir|
expand_source_paths_with_islands(site.config).each do |routes_dir|
routes_dir = File.expand_path(routes_dir, site.config.source)

# @type [Array]
Expand Down Expand Up @@ -66,13 +66,26 @@ def locale_for(slug, site)

private

def file_slug_and_segments(site, routes_dir, file)
# Add any `routes` folders that may be living within islands
def expand_source_paths_with_islands(config)
@islands_dir ||= File.expand_path(config.islands_dir, config.source)

# clear out any past islands folders
config.routes.source_paths.reject! { _1.start_with?(@islands_dir) }

Dir.glob("#{@islands_dir}/**/routes").each do |route_folder|
config.routes.source_paths << route_folder
end

config.routes.source_paths
end

def file_slug_and_segments(_site, routes_dir, file)
# @type [String]
file_slug = file.delete_prefix("#{routes_dir}/").then do |f|
if routes_dir.start_with?(
File.expand_path(site.config[:islands_dir], site.config.source)
)
f = "#{site.config[:islands_dir].delete_prefix("_")}/#{f}"
if routes_dir.start_with?(@islands_dir)
# convert _islands/foldername/routes/someroute.rb to foldername/someroute.rb
f = routes_dir.delete_prefix("#{@islands_dir}/").sub(%r!/routes$!, "/") + f
end
[File.dirname(f), File.basename(f, ".*")].join("/").delete_prefix("./")
end.delete_suffix("/index")
Expand Down
3 changes: 2 additions & 1 deletion bridgetown-routes/lib/roda/plugins/bridgetown_routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,15 @@ def render_with(data: {}) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength

Bridgetown::Model::Base.new(data).to_resource.tap do |resource|
resource.roda_app = self
end.read!.transform!.output
end.read!
end

def render(...)
view.render(...)
end

def view(view_class: Bridgetown::ERBView)
# TODO: support user choosing templates by extension rather than class
response._fake_resource_view(
view_class:, roda_app: self, bridgetown_site:
)
Expand Down
5 changes: 2 additions & 3 deletions bridgetown-routes/test/ssr/config/initializers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

Bridgetown.configure do |config|
require "bridgetown-routes"
init :"bridgetown-routes", require_gem: false

routes.source_paths << File.expand_path("alt_routes", "#{root_dir}/../")
init :"bridgetown-routes", require_gem: false, additional_source_paths:
File.expand_path("alt_routes", "#{root_dir}/..")

config.available_locales = [:en, :it]
config.default_locale = :en
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
###ruby
render_with data: {
title: "Ah, island life…"
}
###

data.title + " =)"
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
###ruby
render_with data: {
title: "Living in paradise"
}
###

data.title + " =)"
10 changes: 10 additions & 0 deletions bridgetown-routes/test/test_routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,15 @@ def site
get "/nested/123-abc"
assert_equal "<h1>Nested Page with Slug: 123-abc</h1>\n", last_response.body
end

should "return the proper route within an island" do
get "/paradise" do
assert_equal "Living in paradise =)", last_response.body
end

get "/paradise/dreamy" do
assert_equal "Ah, island life… =)", last_response.body
end
end
end
end
50 changes: 33 additions & 17 deletions bridgetown-website/src/_docs/configuration/initializers.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,39 @@ While it's not strictly required that you place a Roda block inside of an `only
As mentioned above, you can still add and configure plugins directly in your Roda class file (`server/roda_app.rb`) just like any standard Roda application, but using a Roda configuration block alongside your other initialization steps is a handy way to keep everything consolidated. Bear in mind that the Roda blocks are all executed prior to anything defined within the class-level code of `server/roda_app.rb`, so if you write any code in a Roda block that relies on state having already been defined in the app class directly, it will fail. Best to keep Roda block code self-contained, or reliant only on other settings in the Bridgetown initializers file.
{% end %}

### SSR & Dynamic Routes

The SSR features of Bridgetown, along with its companion file-based routing features, are now configurable via initializers.

```rb
init :ssr

# optional:
init :"bridgetown-routes"

# …or you can just init the routes, which will init :ssr automatically:

init :"bridgetown-routes"
```

If you want to run some specific site setup code on first boot, or any time there's a file refresh in development, provide a `setup` block inside of the SSR initializer.

```rb
init :ssr do
setup -> site do
# access the site object, add data with `site.data`, whatever
end
end
```

For the file-based routing plugin, you can provide additional configuration options to add new source paths (relative to the `src` folder, unless you specify an absolute file path) or add other routable extensions (for example to support a custom template engine):

```rb
init :"bridgetown-routes", additional_source_paths: ["some_more_routes"], additional_extensions: ["tmpl"]
```

For more on how SSR works in Bridgetown, check out our [Routes documentation here](/docs/routes).

## Low-level Boot Customization

If you need to run Ruby code at the earliest possible moment, essentially right when the `bridgetown` executable has finished its startup process, you can add a `config/boot.rb` file to your repo. This is particularly useful if you wish to extend `bridgetown` with new commands.
Expand All @@ -241,23 +274,6 @@ require_relative "../ruby_code_file.rb"

Bridgetown ships with several initializers you can add to your configuration. In future versions of Bridgetown, we expect to make our overall architecture a little more modular so you can use the initializer system to specify just those key features you need (and by omission which ones you don't!).

### SSR & Dynamic Routes

The SSR features of Bridgetown, along with its companion file-based routing features, are now configurable via initializers.

```rb
init :ssr

# optional:
init :"bridgetown-routes"

# …or you can just init the routes, which will init :ssr automatically:

init :"bridgetown-routes"
```

Check out our [Routes documentation here](/docs/routes).

### Dotenv

The Dotenv gem provides a simple way to manage environment variables with your Bridgetown project. Simply add the gem to your Gemfile (`bundle add dotenv`), and then add the initializer to your configuration:
Expand Down
2 changes: 1 addition & 1 deletion bridgetown-website/src/_docs/content/dsd.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ What's great about this approach is:
In addition to the benefits above, you also have the ability to leverage [CSS Shadow Parts](https://developer.mozilla.org/en-US/docs/Web/CSS/::part) (which only work when you have, er, shadow DOM—hence the name!). What's a shadow part? It's when you use the `part=` attribute on an element inside your DSD template, and by doing so it makes it styleable from the "outside". Defining parts and labeling them appropriately is a fantastic way to build up a true "style API" for each layout or component.

{%@ Note do %}
Declarative Shadow DOM is a fairly new specification. As of the time of this writing, Firefox (and some older versions of Safari) do not offer built-in DSD support. The `<is-land>` web component automatically polyfills DSD, which is an added benefit of using it. Otherwise, the Turbo bundled configuration also includes a site-wide polyfill for DSD.
Declarative Shadow DOM is a fairly new specification. As of the time of this writing, some older versions of Safari and Firefox do not offer built-in DSD support. The `<is-land>` web component automatically polyfills DSD, which is an added benefit of using it. Otherwise, the Turbo bundled configuration also includes a site-wide polyfill for DSD.
{% end %}

## Components with Sidecar CSS
Expand Down
Loading