Skip to content

Commit 001d3d1

Browse files
committed
Server Side Rendering of Tailwind
- 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 -
1 parent 5937b92 commit 001d3d1

15 files changed

+73
-14
lines changed

Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ gem 'redcarpet', '~> 3.5'
4747
gem 'simple_form'
4848
gem 'tinymce-rails'
4949
gem 'image_processing', '~> 1.2'
50+
gem 'puppeteer-ruby'
5051
gem 'react-rails'
5152
gem 'webpacker'
5253

Gemfile.lock

+5
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,10 @@ GEM
345345
nio4r (~> 2.0)
346346
pundit (2.1.1)
347347
activesupport (>= 3.0.0)
348+
puppeteer-ruby (0.40.7)
349+
concurrent-ruby (~> 1.1.0)
350+
mime-types (>= 3.0)
351+
websocket-driver (>= 0.6.0)
348352
racc (1.6.0)
349353
rack (2.2.3.1)
350354
rack-mini-profiler (2.3.3)
@@ -556,6 +560,7 @@ DEPENDENCIES
556560
pry-rescue
557561
puma
558562
pundit
563+
puppeteer-ruby
559564
rack-mini-profiler
560565
rack-timeout (~> 0.5)
561566
rails (~> 6.1.4)

app/controllers/staff/pages_controller.rb

+24
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def index
1111

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

@@ -39,6 +40,8 @@ def update
3940
def preview; end
4041

4142
def publish
43+
save_tailwind_page_content
44+
4245
@page.update(published_body: @page.unpublished_body,
4346
body_published_at: Time.current)
4447
flash[:success] = "#{@page.name} Page was successfully published."
@@ -67,6 +70,27 @@ def set_page
6770
end
6871
end
6972

73+
def save_tailwind_page_content
74+
Puppeteer.launch(
75+
headless: true,
76+
args: ['--no-sandbox', '--disable-setuid-sandbox']
77+
) do |browser|
78+
page = browser.pages.first || browser.new_page
79+
page.goto(root_url)
80+
cookies.each do |name, value|
81+
page.set_cookie(name: name, value: value)
82+
end
83+
page.goto(event_staff_page_url(current_event, @page), wait_until: 'domcontentloaded')
84+
css = page.query_selector_all('style').map do |style|
85+
style.evaluate('(el) => el.textContent')
86+
end.detect { |text| text.match("tailwindcss") }
87+
html = "<style>#{css}</style>"
88+
89+
content = @page.contents.find_or_initialize_by(name: Page::TAILWIND)
90+
content.update!(placement: Website::Content::HEAD, html: html)
91+
end
92+
end
93+
7094
def build_page
7195
if template = params[:page] && page_params[:template].presence
7296
Page.from_template(

app/helpers/page_helper.rb

+6
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,10 @@ def background_style
3636
def logo_image(args)
3737
resize_image_tag(current_website.logo, **args)
3838
end
39+
40+
def tailwind_content
41+
return false if @include_tailwind
42+
43+
@page&.tailwind_css&.html_safe
44+
end
3945
end

app/javascript/packs/default_theme.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
import "controllers"

app/models/page.rb

+8
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ class Page < ApplicationRecord
44
'home' => { },
55
}
66

7+
TAILWIND = 'tailwind'.freeze
8+
79
belongs_to :website
810
after_save_commit :purge_website_cache
911

12+
has_many :contents, class_name: 'Website::Content', as: :contentable, dependent: :destroy
13+
1014
scope :published, -> { where.not(published_body: nil).where(hide_page: false) }
1115
scope :in_footer, -> { published.where.not(footer_category: [nil, ""]) }
1216

@@ -39,6 +43,10 @@ def unpublished_changes?
3943
published_body != unpublished_body
4044
end
4145

46+
def tailwind_css
47+
contents.where(name: TAILWIND).pluck(:html).first
48+
end
49+
4250
private
4351

4452
def purge_website_cache

app/models/website.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ class Website < ApplicationRecord
44
belongs_to :event
55
has_many :pages, dependent: :destroy
66
has_many :fonts, class_name: 'Website::Font', dependent: :destroy
7-
has_many :contents, class_name: 'Website::Content', dependent: :destroy
7+
has_many :contents, class_name: 'Website::Content', as: :contentable, dependent: :destroy
88
has_one :meta_data, class_name: 'Website::MetaData', dependent: :destroy
99

1010
has_many :session_formats, through: :event

app/models/website/content.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ class Website::Content < ApplicationRecord
44
FOOTER = "footer",
55
].freeze
66

7-
belongs_to :website
7+
belongs_to :contentable, polymorphic: true
88

99
scope :for, -> (placement) { where(placement: placement) }
10+
scope :for_page, -> (slug) { where(name: slug) }
1011
end
1112

1213
# == Schema Information

app/views/layouts/themes/default.html.haml

+3-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
%meta{property: "twitter:image", content: current_website.meta_image_url}
2222

2323
= stylesheet_link_tag current_website.theme, media: 'all'
24-
= javascript_pack_tag "application"
24+
= javascript_pack_tag "default_theme"
2525
:css
2626
#{current_website.font_faces_css}
2727
#{current_website.font_root_css}
28-
= javascript_pack_tag "tailwind"
28+
= tailwind_content || javascript_pack_tag("tailwind")
2929
= current_website.head_content
3030

3131
%body
@@ -58,4 +58,4 @@
5858
})
5959
= current_website.footer_content
6060

61-
= yield :javascript
61+
= yield :javascript
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class ConvertWebsiteContentsToPolymorphic < ActiveRecord::Migration[6.1]
2+
def change
3+
change_table :website_contents, bulk: true do |t|
4+
t.rename :website_id, :contentable_id
5+
t.string :contentable_type, default: 'Website', null: false
6+
t.index [:contentable_id, :contentable_type]
7+
t.remove_index :contentable_id
8+
end
9+
end
10+
end

db/schema.rb

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
#
1111
# It's strongly recommended that you check this file into your version control system.
1212

13-
ActiveRecord::Schema.define(version: 2022_05_23_185412) do
13+
ActiveRecord::Schema.define(version: 2022_06_09_140626) do
1414

1515
# These are extensions that must be enabled in order to support this database
1616
enable_extension "plpgsql"
@@ -344,10 +344,11 @@
344344
t.text "html"
345345
t.string "placement", default: "head", null: false
346346
t.string "name"
347-
t.bigint "website_id"
347+
t.bigint "contentable_id"
348348
t.datetime "created_at", precision: 6, null: false
349349
t.datetime "updated_at", precision: 6, null: false
350-
t.index ["website_id"], name: "index_website_contents_on_website_id"
350+
t.string "contentable_type", default: "Website", null: false
351+
t.index ["contentable_id", "contentable_type"], name: "index_website_contents_on_contentable_id_and_contentable_type"
351352
end
352353

353354
create_table "website_fonts", force: :cascade do |t|

spec/features/website/configuration_spec.rb

+2-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
website = create(:website, event: event)
7272
home_page = create(:page, website: website)
7373

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

114-
login_as(organizer)
114+
signin_as(organizer)
115115
visit edit_event_staff_website_path(event)
116116

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

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

147146
RSpec::Mocks.space.proxy_for(fastly_service).reset
148-
sleep 1
149147
visit event_staff_pages_path(event)
150148
click_on("Publish")
151149
expect(fastly_service).not_to have_received(:purge_by_key).with(event.slug)

spec/features/website/page_management_spec.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060

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

6565
visit page_path(slug: event.slug, page: home_page.slug)
6666
expect(page).to have_content("Page Not Found")
@@ -91,7 +91,7 @@
9191
end
9292

9393
scenario "Organizer creates and publishes a splash page from a template", :js do
94-
login_as(organizer)
94+
signin_as(organizer)
9595
visit new_event_staff_page_path(event)
9696
select("splash", from: "template")
9797

spec/rails_helper.rb

+1
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,4 @@ def save_timestamped_screenshot(page)
9393

9494
page.save_screenshot(screenshot_path)
9595
end
96+
Capybara.default_max_wait_time = 3.seconds

spec/support/helpers/session_helpers.rb

+3
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,8 @@ def forgot_password(email)
2222
click_button 'Send me reset password instructions'
2323
end
2424

25+
def signin_as(user)
26+
signin(user.email, user.password)
27+
end
2528
end
2629
end

0 commit comments

Comments
 (0)