Skip to content

Commit

Permalink
Server Side Rendering of Tailwind
Browse files Browse the repository at this point in the history
- uses Puppeteer to launch a headless browser  to render the published
  page and extract the JIT tailwind css to be used directly on public
published page
- converts Website::Content to be a polymorphic association so that it
  can be used generally on Website or specific per Page
- adds a separate default_theme webpack so that unneeded application js
  is not included on website pages
-
  • Loading branch information
jonsgreen committed Jun 29, 2022
1 parent 5937b92 commit e5f0f9f
Show file tree
Hide file tree
Showing 15 changed files with 70 additions and 14 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ gem 'redcarpet', '~> 3.5'
gem 'simple_form'
gem 'tinymce-rails'
gem 'image_processing', '~> 1.2'
gem 'puppeteer-ruby'
gem 'react-rails'
gem 'webpacker'

Expand Down
5 changes: 5 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,10 @@ GEM
nio4r (~> 2.0)
pundit (2.1.1)
activesupport (>= 3.0.0)
puppeteer-ruby (0.40.7)
concurrent-ruby (~> 1.1.0)
mime-types (>= 3.0)
websocket-driver (>= 0.6.0)
racc (1.6.0)
rack (2.2.3.1)
rack-mini-profiler (2.3.3)
Expand Down Expand Up @@ -556,6 +560,7 @@ DEPENDENCIES
pry-rescue
puma
pundit
puppeteer-ruby
rack-mini-profiler
rack-timeout (~> 0.5)
rails (~> 6.1.4)
Expand Down
21 changes: 21 additions & 0 deletions app/controllers/staff/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def index

def show
@body = params[:preview] || @page.unpublished_body || ""
@include_tailwind = true
render template: 'pages/show', layout: "themes/#{current_website.theme}"
end

Expand Down Expand Up @@ -39,6 +40,8 @@ def update
def preview; end

def publish
save_tailwind_page_content

@page.update(published_body: @page.unpublished_body,
body_published_at: Time.current)
flash[:success] = "#{@page.name} Page was successfully published."
Expand Down Expand Up @@ -67,6 +70,24 @@ def set_page
end
end

def save_tailwind_page_content
Puppeteer.launch do |browser|
page = browser.pages.first || browser.new_page
page.goto(root_url)
cookies.each do |name, value|
page.set_cookie(name: name, value: value)
end
page.goto(event_staff_page_url(current_event, @page), wait_until: 'domcontentloaded')
css = page.query_selector_all('style').map do |style|
style.evaluate('(el) => el.textContent')
end.detect { |text| text.match("tailwindcss") }
html = "<style>#{css}</style>"

content = @page.contents.find_or_initialize_by(name: Page::TAILWIND)
content.update!(placement: Website::Content::HEAD, html: html)
end
end

def build_page
if template = params[:page] && page_params[:template].presence
Page.from_template(
Expand Down
6 changes: 6 additions & 0 deletions app/helpers/page_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,10 @@ def background_style
def logo_image(args)
resize_image_tag(current_website.logo, **args)
end

def tailwind_content
return false if @include_tailwind

@page&.tailwind_css&.html_safe
end
end
1 change: 1 addition & 0 deletions app/javascript/packs/default_theme.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "controllers"
8 changes: 8 additions & 0 deletions app/models/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ class Page < ApplicationRecord
'home' => { },
}

TAILWIND = 'tailwind'.freeze

belongs_to :website
after_save_commit :purge_website_cache

has_many :contents, class_name: 'Website::Content', as: :contentable, dependent: :destroy

scope :published, -> { where.not(published_body: nil).where(hide_page: false) }
scope :in_footer, -> { published.where.not(footer_category: [nil, ""]) }

Expand Down Expand Up @@ -39,6 +43,10 @@ def unpublished_changes?
published_body != unpublished_body
end

def tailwind_css
contents.where(name: TAILWIND).pluck(:html).first
end

private

def purge_website_cache
Expand Down
2 changes: 1 addition & 1 deletion app/models/website.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Website < ApplicationRecord
belongs_to :event
has_many :pages, dependent: :destroy
has_many :fonts, class_name: 'Website::Font', dependent: :destroy
has_many :contents, class_name: 'Website::Content', dependent: :destroy
has_many :contents, class_name: 'Website::Content', as: :contentable, dependent: :destroy
has_one :meta_data, class_name: 'Website::MetaData', dependent: :destroy

has_many :session_formats, through: :event
Expand Down
3 changes: 2 additions & 1 deletion app/models/website/content.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ class Website::Content < ApplicationRecord
FOOTER = "footer",
].freeze

belongs_to :website
belongs_to :contentable, polymorphic: true

scope :for, -> (placement) { where(placement: placement) }
scope :for_page, -> (slug) { where(name: slug) }
end

# == Schema Information
Expand Down
6 changes: 3 additions & 3 deletions app/views/layouts/themes/default.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
%meta{property: "twitter:image", content: current_website.meta_image_url}

= stylesheet_link_tag current_website.theme, media: 'all'
= javascript_pack_tag "application"
= javascript_pack_tag "default_theme"
:css
#{current_website.font_faces_css}
#{current_website.font_root_css}
= javascript_pack_tag "tailwind"
= tailwind_content || javascript_pack_tag("tailwind")
= current_website.head_content

%body
Expand Down Expand Up @@ -58,4 +58,4 @@
})
= current_website.footer_content

= yield :javascript
= yield :javascript
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class ConvertWebsiteContentsToPolymorphic < ActiveRecord::Migration[6.1]
def change
change_table :website_contents, bulk: true do |t|
t.rename :website_id, :contentable_id
t.string :contentable_type, default: 'Website', null: false
t.index [:contentable_id, :contentable_type]
t.remove_index :contentable_id
end
end
end
7 changes: 4 additions & 3 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2022_05_23_185412) do
ActiveRecord::Schema.define(version: 2022_06_09_140626) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -344,10 +344,11 @@
t.text "html"
t.string "placement", default: "head", null: false
t.string "name"
t.bigint "website_id"
t.bigint "contentable_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["website_id"], name: "index_website_contents_on_website_id"
t.string "contentable_type", default: "Website", null: false
t.index ["contentable_id", "contentable_type"], name: "index_website_contents_on_contentable_id_and_contentable_type"
end

create_table "website_fonts", force: :cascade do |t|
Expand Down
6 changes: 2 additions & 4 deletions spec/features/website/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
website = create(:website, event: event)
home_page = create(:page, website: website)

login_as(organizer)
signin_as(organizer)
visit edit_event_staff_website_path(event)
click_on("Add Content")
fill_in_codemirror(<<~HTML
Expand Down Expand Up @@ -111,7 +111,7 @@
visit page_path(event, home_page)
expect(response_headers["Cache-Control"]).to eq("max-age=0, private, s-maxage=0")

login_as(organizer)
signin_as(organizer)
visit edit_event_staff_website_path(event)

select("automatic", from: "Caching")
Expand All @@ -126,7 +126,6 @@
expect(response_headers["Last-Modified"]).to eq(last_modified)

RSpec::Mocks.space.proxy_for(fastly_service).reset
sleep 1
visit event_staff_pages_path(event)
click_on("Publish")
expect(fastly_service).to have_received(:purge_by_key).with(event.slug)
Expand All @@ -145,7 +144,6 @@
last_modified = response_headers["Last-Modified"]

RSpec::Mocks.space.proxy_for(fastly_service).reset
sleep 1
visit event_staff_pages_path(event)
click_on("Publish")
expect(fastly_service).not_to have_received(:purge_by_key).with(event.slug)
Expand Down
4 changes: 2 additions & 2 deletions spec/features/website/page_management_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@

scenario "Organizer publishes a website page", :js do
home_page = create(:page, unpublished_body: 'Home Content', published_body: nil)
login_as(organizer)
signin_as(organizer)

visit page_path(slug: event.slug, page: home_page.slug)
expect(page).to have_content("Page Not Found")
Expand Down Expand Up @@ -91,7 +91,7 @@
end

scenario "Organizer creates and publishes a splash page from a template", :js do
login_as(organizer)
signin_as(organizer)
visit new_event_staff_page_path(event)
select("splash", from: "template")

Expand Down
1 change: 1 addition & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,4 @@ def save_timestamped_screenshot(page)

page.save_screenshot(screenshot_path)
end
Capybara.default_max_wait_time = 3.seconds
3 changes: 3 additions & 0 deletions spec/support/helpers/session_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ def forgot_password(email)
click_button 'Send me reset password instructions'
end

def signin_as(user)
signin(user.email, user.password)
end
end
end

0 comments on commit e5f0f9f

Please sign in to comment.