├── .dockerignore ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .ruby-version ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── Procfile ├── README.md ├── backend ├── Rakefile ├── bin │ └── rails ├── config │ ├── application.rb │ ├── boot.rb │ ├── initializers │ │ ├── cors.rb │ │ └── the_time.rb │ └── routes.rb ├── nano_controller.rb ├── products_controller.rb └── shared │ ├── example.rb │ └── page_renderer.rb ├── bridgetown.config.yml ├── config.ru ├── fly.toml ├── frontend ├── javascript │ └── index.js └── styles │ └── index.scss ├── package.json ├── plugins ├── builders │ └── .keep ├── rails_support.rb └── site_builder.rb ├── run_docker.sh ├── src ├── 404.html ├── _components │ ├── footer.liquid │ ├── head.liquid │ └── navbar.liquid ├── _data │ └── site_metadata.yml ├── _layouts │ ├── default.liquid │ ├── home.liquid │ ├── page.liquid │ ├── post.liquid │ └── product.erb ├── _partials │ └── _product.erb ├── _posts │ └── 2020-10-16-welcome-to-bridgetown.md ├── _products │ └── toothpaste.md ├── about.md ├── favicon.ico ├── index.md ├── posts.md └── products.md ├── start.js ├── sync.js ├── webpack.config.js └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | # Bridgetown 2 | output 3 | .bridgetown-cache 4 | .bridgetown-metadata 5 | .bridgetown-webpack 6 | 7 | # Dependency folders 8 | node_modules 9 | bower_components 10 | vendor 11 | 12 | # Caches 13 | .sass-cache 14 | .npm 15 | .node_repl_history 16 | 17 | # Ignore bundler config. 18 | /.bundle 19 | 20 | # Ignore Byebug command history file. 21 | .byebug_history 22 | 23 | # dotenv environment variables file 24 | .env 25 | 26 | # Mac files 27 | .DS_Store 28 | 29 | # Yarn 30 | yarn-error.log 31 | yarn-debug.log* 32 | .pnp/ 33 | .pnp.js 34 | # Yarn Integrity file 35 | .yarn-integrity 36 | 37 | .git 38 | 39 | tmp 40 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Fly Deploy 2 | on: [push] 3 | env: 4 | FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }} 5 | jobs: 6 | deploy: 7 | name: Deploy app 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: superfly/flyctl-actions@1.1 12 | with: 13 | args: "deploy" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Bridgetown 2 | output 3 | .bridgetown-cache 4 | .bridgetown-metadata 5 | .bridgetown-webpack 6 | 7 | # Dependency folders 8 | node_modules 9 | bower_components 10 | vendor 11 | 12 | # Caches 13 | .sass-cache 14 | .npm 15 | .node_repl_history 16 | 17 | # Ignore bundler config. 18 | /.bundle 19 | 20 | # Ignore Byebug command history file. 21 | .byebug_history 22 | 23 | # dotenv environment variables file 24 | .env 25 | 26 | # Mac files 27 | .DS_Store 28 | 29 | # Yarn 30 | yarn-error.log 31 | yarn-debug.log* 32 | .pnp/ 33 | .pnp.js 34 | # Yarn Integrity file 35 | .yarn-integrity 36 | 37 | tmp -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.7.0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.7.0-alpine3.11 as builder 2 | 3 | RUN apk add --no-cache --virtual \ 4 | # 5 | # required 6 | nodejs-dev yarn bash \ 7 | tzdata build-base libffi-dev \ 8 | # 9 | # nice to haves 10 | curl git \ 11 | # 12 | # Fixes watch file isses with things like HMR 13 | libnotify-dev 14 | 15 | FROM builder as rails-nano-app 16 | 17 | # This is to fix an issue on Linux with permissions issues 18 | ARG USER_ID=${USER_ID:-1000} 19 | ARG GROUP_ID=${GROUP_ID:-1000} 20 | ARG DOCKER_USER=${DOCKER_USER:-user} 21 | ARG APP_DIR=${APP_DIR:-/home/user/nano-rails-app} 22 | 23 | # Change with --build-arg RAILS_ENV=production 24 | ARG RAILS_ENV=production 25 | ENV RAILS_ENV=$RAILS_ENV 26 | 27 | # Create a non-root user 28 | RUN addgroup -g $GROUP_ID -S $GROUP_ID 29 | RUN adduser --disabled-password -G $GROUP_ID --uid $USER_ID -S $DOCKER_USER 30 | 31 | # Create and then own the directory to fix permissions issues 32 | RUN mkdir -p $APP_DIR 33 | RUN chown -R $USER_ID:$GROUP_ID $APP_DIR 34 | 35 | # Define the user running the container 36 | USER $USER_ID:$GROUP_ID 37 | 38 | # . now == $APP_DIR 39 | WORKDIR $APP_DIR 40 | 41 | # COPY is run as a root user, not as the USER defined above, so we must chown it 42 | COPY --chown=$USER_ID:$GROUP_ID Gemfile* ./ 43 | RUN gem install bundler 44 | RUN bundle install 45 | 46 | # For webpacker / node_modules 47 | COPY --chown=$USER_ID:$GROUP_ID package.json . 48 | COPY --chown=$USER_ID:$GROUP_ID yarn.lock . 49 | RUN yarn install 50 | 51 | COPY --chown=$USER_ID:$GROUP_ID . . 52 | RUN yarn deploy 53 | 54 | EXPOSE 8080 55 | 56 | CMD ["bundle", "exec", "puma", "-p", "8080"] 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby "2.7.0" 5 | 6 | gem "puma" 7 | gem "rails" 8 | gem "rack-cors" 9 | gem "phaedra" 10 | gem "bridgetown", "~> 0.18" 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (6.0.3.4) 5 | actionpack (= 6.0.3.4) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailbox (6.0.3.4) 9 | actionpack (= 6.0.3.4) 10 | activejob (= 6.0.3.4) 11 | activerecord (= 6.0.3.4) 12 | activestorage (= 6.0.3.4) 13 | activesupport (= 6.0.3.4) 14 | mail (>= 2.7.1) 15 | actionmailer (6.0.3.4) 16 | actionpack (= 6.0.3.4) 17 | actionview (= 6.0.3.4) 18 | activejob (= 6.0.3.4) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (6.0.3.4) 22 | actionview (= 6.0.3.4) 23 | activesupport (= 6.0.3.4) 24 | rack (~> 2.0, >= 2.0.8) 25 | rack-test (>= 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 28 | actiontext (6.0.3.4) 29 | actionpack (= 6.0.3.4) 30 | activerecord (= 6.0.3.4) 31 | activestorage (= 6.0.3.4) 32 | activesupport (= 6.0.3.4) 33 | nokogiri (>= 1.8.5) 34 | actionview (6.0.3.4) 35 | activesupport (= 6.0.3.4) 36 | builder (~> 3.1) 37 | erubi (~> 1.4) 38 | rails-dom-testing (~> 2.0) 39 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 40 | activejob (6.0.3.4) 41 | activesupport (= 6.0.3.4) 42 | globalid (>= 0.3.6) 43 | activemodel (6.0.3.4) 44 | activesupport (= 6.0.3.4) 45 | activerecord (6.0.3.4) 46 | activemodel (= 6.0.3.4) 47 | activesupport (= 6.0.3.4) 48 | activestorage (6.0.3.4) 49 | actionpack (= 6.0.3.4) 50 | activejob (= 6.0.3.4) 51 | activerecord (= 6.0.3.4) 52 | marcel (~> 0.3.1) 53 | activesupport (6.0.3.4) 54 | concurrent-ruby (~> 1.0, >= 1.0.2) 55 | i18n (>= 0.7, < 2) 56 | minitest (~> 5.1) 57 | tzinfo (~> 1.1) 58 | zeitwerk (~> 2.2, >= 2.2.2) 59 | addressable (2.7.0) 60 | public_suffix (>= 2.0.2, < 5.0) 61 | amazing_print (1.2.2) 62 | bridgetown (0.18.6) 63 | bridgetown-builder (= 0.18.6) 64 | bridgetown-core (= 0.18.6) 65 | bridgetown-paginate (= 0.18.6) 66 | bridgetown-builder (0.18.6) 67 | bridgetown-core (= 0.18.6) 68 | bridgetown-core (0.18.6) 69 | activesupport (~> 6.0) 70 | addressable (~> 2.4) 71 | amazing_print (~> 1.2) 72 | colorator (~> 1.0) 73 | erubi (~> 1.9) 74 | faraday (~> 1.0) 75 | faraday_middleware (~> 1.0) 76 | hash_with_dot_access (~> 1.0) 77 | i18n (~> 1.0) 78 | kramdown (~> 2.1) 79 | kramdown-parser-gfm (~> 1.0) 80 | liquid (~> 4.0) 81 | liquid-component (>= 0.1) 82 | liquid-render-tag (~> 0.2) 83 | listen (~> 3.0) 84 | pathutil (~> 0.9) 85 | rouge (~> 3.0) 86 | safe_yaml (~> 1.0) 87 | terminal-table (~> 1.8) 88 | thor (~> 1.0) 89 | tilt (~> 2.0) 90 | bridgetown-paginate (0.18.6) 91 | bridgetown-core (= 0.18.6) 92 | builder (3.2.4) 93 | colorator (1.1.0) 94 | concurrent-ruby (1.1.7) 95 | crass (1.0.6) 96 | erubi (1.10.0) 97 | faraday (1.1.0) 98 | multipart-post (>= 1.2, < 3) 99 | ruby2_keywords 100 | faraday_middleware (1.0.0) 101 | faraday (~> 1.0) 102 | ffi (1.13.1) 103 | forwardable-extended (2.6.0) 104 | globalid (0.4.2) 105 | activesupport (>= 4.2.0) 106 | hash_with_dot_access (1.1.0) 107 | activesupport (>= 5.0.0, < 7.0) 108 | i18n (1.8.5) 109 | concurrent-ruby (~> 1.0) 110 | kramdown (2.3.0) 111 | rexml 112 | kramdown-parser-gfm (1.1.0) 113 | kramdown (~> 2.0) 114 | liquid (4.0.3) 115 | liquid-component (0.1.0) 116 | activesupport (>= 5.0) 117 | safe_yaml (~> 1.0) 118 | liquid-render-tag (0.2.0) 119 | listen (3.3.1) 120 | rb-fsevent (~> 0.10, >= 0.10.3) 121 | rb-inotify (~> 0.9, >= 0.9.10) 122 | loofah (2.8.0) 123 | crass (~> 1.0.2) 124 | nokogiri (>= 1.5.9) 125 | mail (2.7.1) 126 | mini_mime (>= 0.1.1) 127 | marcel (0.3.3) 128 | mimemagic (~> 0.3.2) 129 | method_source (1.0.0) 130 | mimemagic (0.3.5) 131 | mini_mime (1.0.2) 132 | mini_portile2 (2.4.0) 133 | minitest (5.14.2) 134 | multipart-post (2.1.1) 135 | nio4r (2.5.4) 136 | nokogiri (1.10.10) 137 | mini_portile2 (~> 2.4.0) 138 | pathutil (0.16.2) 139 | forwardable-extended (~> 2.6) 140 | phaedra (0.5.3) 141 | activesupport (~> 6.0) 142 | rack (~> 2.0) 143 | public_suffix (4.0.6) 144 | puma (5.0.4) 145 | nio4r (~> 2.0) 146 | rack (2.2.3) 147 | rack-cors (1.1.1) 148 | rack (>= 2.0.0) 149 | rack-test (1.1.0) 150 | rack (>= 1.0, < 3) 151 | rails (6.0.3.4) 152 | actioncable (= 6.0.3.4) 153 | actionmailbox (= 6.0.3.4) 154 | actionmailer (= 6.0.3.4) 155 | actionpack (= 6.0.3.4) 156 | actiontext (= 6.0.3.4) 157 | actionview (= 6.0.3.4) 158 | activejob (= 6.0.3.4) 159 | activemodel (= 6.0.3.4) 160 | activerecord (= 6.0.3.4) 161 | activestorage (= 6.0.3.4) 162 | activesupport (= 6.0.3.4) 163 | bundler (>= 1.3.0) 164 | railties (= 6.0.3.4) 165 | sprockets-rails (>= 2.0.0) 166 | rails-dom-testing (2.0.3) 167 | activesupport (>= 4.2.0) 168 | nokogiri (>= 1.6) 169 | rails-html-sanitizer (1.3.0) 170 | loofah (~> 2.3) 171 | railties (6.0.3.4) 172 | actionpack (= 6.0.3.4) 173 | activesupport (= 6.0.3.4) 174 | method_source 175 | rake (>= 0.8.7) 176 | thor (>= 0.20.3, < 2.0) 177 | rake (13.0.1) 178 | rb-fsevent (0.10.4) 179 | rb-inotify (0.10.1) 180 | ffi (~> 1.0) 181 | rexml (3.2.4) 182 | rouge (3.25.0) 183 | ruby2_keywords (0.0.2) 184 | safe_yaml (1.0.5) 185 | sprockets (4.0.2) 186 | concurrent-ruby (~> 1.0) 187 | rack (> 1, < 3) 188 | sprockets-rails (3.2.2) 189 | actionpack (>= 4.0) 190 | activesupport (>= 4.0) 191 | sprockets (>= 3.0.0) 192 | terminal-table (1.8.0) 193 | unicode-display_width (~> 1.1, >= 1.1.1) 194 | thor (1.0.1) 195 | thread_safe (0.3.6) 196 | tilt (2.0.10) 197 | tzinfo (1.2.8) 198 | thread_safe (~> 0.1) 199 | unicode-display_width (1.7.0) 200 | websocket-driver (0.7.3) 201 | websocket-extensions (>= 0.1.0) 202 | websocket-extensions (0.1.5) 203 | zeitwerk (2.4.1) 204 | 205 | PLATFORMS 206 | ruby 207 | 208 | DEPENDENCIES 209 | bridgetown (~> 0.18) 210 | phaedra 211 | puma 212 | rack-cors 213 | rails 214 | 215 | RUBY VERSION 216 | ruby 2.7.0p0 217 | 218 | BUNDLED WITH 219 | 2.1.4 220 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -p ${PORT:-3000} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # May 2021 Update: 2 | 3 | I've decided to suspend work on a Rails-specific integration for Bridgetown until further notice. Bridgetown does currently have an [outstanding PR for switching to Puma/Rack/Roda](https://github.com/bridgetownrb/bridgetown/pull/281) for essential routes, and you absolutely can mount a Rails API right onto that however you wish. But [in light](https://solnic.codes/2021/05/01/whoops-thoughts-on-rails-forking-and-leadership/) of [recent events](https://www.theverge.com/2021/4/30/22412714/basecamp-employees-memo-policy-hansson-fried-controversy), I will not personally be working on a Rails integration solution. 4 | 5 | ---- 6 | 7 | # Example of a Bridgetown website with a "Rails Nano" backend API 8 | 9 | More documentation and an official Bridgetown plugin coming soon! 10 | 11 | Basically, run `bundle install` and `yarn install` and then `yarn start` and boom, you have a static website powered by Bridgetown 12 | communicating with a Rails API all in one repo. Also cool: it's a single Rack app stack served by Puma, so you can deploy to a 13 | Ruby-friendly host or use a Docker mesh like Fly.io and get the whole enchilada up and running in no time flat! 14 | 15 | **LIVE DEMOS**: 16 | * [https://rails-nano-api-test.fly.dev](https://rails-nano-api-test.fly.dev) ([Fly.io](https://fly.io)) 17 | * [https://rails-nano-demo.onrender.com](https://rails-nano-demo.onrender.com) ([Render](https://render.com)) 18 | * [https://rails-nano.vercel.app](https://rails-nano.vercel.app) ([Vercel](https://vercel.com) + [Heroku](https://www.heroku.com/)) (_Note: there may be a warmup delay at first due to the free Heroku dyno starting up_) 19 | -------------------------------------------------------------------------------- /backend/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative 'config/application' 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /backend/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | APP_PATH = File.expand_path('../config/application', __dir__) 4 | require_relative '../config/boot' 5 | require 'rails/commands' 6 | -------------------------------------------------------------------------------- /backend/config/application.rb: -------------------------------------------------------------------------------- 1 | require "action_controller/railtie" 2 | 3 | class NanoAPI < Rails::Application 4 | config.root = File.dirname(__dir__) # set the root to `backend` 5 | config.autoloader = :zeitwerk 6 | if Rails.env.production? 7 | config.eager_load = true 8 | config.cache_classes = true 9 | else 10 | config.eager_load = false 11 | end 12 | config.autoload_paths << File.dirname(__dir__) # autoload right from `backend` 13 | config.api_only = true # removes middleware we dont need 14 | config.logger = Logger.new($stdout) 15 | Rails.logger = config.logger 16 | config.secret_key_base = ENV["SECRET_KEY_BASE"] # Rails won't boot w/o a secret token for session, cookies, etc. 17 | end 18 | 19 | NanoAPI.initialize! 20 | -------------------------------------------------------------------------------- /backend/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /backend/config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | require "rack/cors" 2 | 3 | Rails.application.config.middleware.insert_before 0, Rack::Cors, logger: (-> { Rails.logger }) do 4 | allow do 5 | origins '*' 6 | 7 | resource '*', 8 | :headers => :any, 9 | :methods => [:get, :post, :delete, :put, :patch, :options, :head], 10 | :max_age => 0 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /backend/config/initializers/the_time.rb: -------------------------------------------------------------------------------- 1 | require "shared/example" 2 | 3 | Shared::Example.boot_time((Random.rand * 10000).to_i) 4 | -------------------------------------------------------------------------------- /backend/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | scope "/backend" do 3 | resources :nano 4 | 5 | get "/products/*id" => "products#show" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /backend/nano_controller.rb: -------------------------------------------------------------------------------- 1 | class NanoController < ActionController::Base 2 | def index 3 | render json: { 4 | status: "index route", 5 | hello: "Howdy! I'm Rails, your friendly neighborhood Ruby backend. :)", 6 | boot_time: Shared::Example.boot_time, 7 | current_time: Time.now 8 | } 9 | end 10 | 11 | def show 12 | render json: {status: "show route", id: params[:id]} 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /backend/products_controller.rb: -------------------------------------------------------------------------------- 1 | class ProductsController < ActionController::Base 2 | attr_reader :price 3 | 4 | def show 5 | sleep params[:sleep_time].to_i if params[:sleep_time].to_i <= 10 6 | 7 | renderer = Shared::PageRenderer.new(self, collection: :products, id: params[:id]) 8 | 9 | if renderer.page 10 | @price = "$#{SecureRandom.rand(12) + 3}.95" 11 | render html: renderer.content 12 | else 13 | render plain: "Product not found", status: 404 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /backend/shared/example.rb: -------------------------------------------------------------------------------- 1 | module Shared 2 | class Example 3 | def self.boot_time(seed = nil) 4 | @boot_time ||= "#{Time.now} (seed: #{seed})" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /backend/shared/page_renderer.rb: -------------------------------------------------------------------------------- 1 | require "bridgetown" 2 | 3 | module Shared 4 | class PageRenderer 5 | attr_reader :site, :collection, :page 6 | 7 | def initialize(controller, collection:, id:) 8 | path = "/#{collection}/#{id}" 9 | @site = Bridgetown::Site.new(Bridgetown.configuration) 10 | 11 | if Rails.env.development? 12 | site.plugin_manager.reload_plugin_files # plugins hang between Rails request, so we need to load them explicitly 13 | end 14 | 15 | site.controller = controller 16 | site.defaults_reader.read 17 | site.layouts = Bridgetown::LayoutReader.new(site).read 18 | site.data = Bridgetown::DataReader.new(site).read(site.config["data_dir"]) 19 | 20 | Bridgetown::Hooks.trigger :site, :pre_read, site 21 | if collection == :posts 22 | reader = Bridgetown::Reader.new(site) 23 | reader.retrieve_posts(site.source) 24 | @collection = site.posts 25 | else 26 | @collection = site.collections[collection] 27 | @collection.read 28 | end 29 | 30 | site.generators.find { |generator| generator.is_a?(Bridgetown::Builders::DocumentsGenerator) }&.generate(site) 31 | 32 | @page = @collection.docs.find { |doc| doc.id == path } 33 | end 34 | 35 | def render_page 36 | return if page.nil? 37 | 38 | site.layouts[page.data.layout].data.layout = "none" 39 | 40 | Bridgetown::Renderer.new(site, page).run 41 | if Rails.env.development? 42 | Bridgetown::Hooks.trigger :site, :pre_reload, site # clear out generators for next time 43 | end 44 | end 45 | 46 | def content 47 | return @content if @content 48 | 49 | render_page 50 | 51 | @content = page&.output&.html_safe 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /bridgetown.config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Bridgetown! 2 | # 3 | # This config file is for settings that affect your whole site, values 4 | # which you are expected to set up once and rarely edit after that. 5 | # 6 | # For technical reasons, this file is *NOT* reloaded automatically when you use 7 | # 'bundle exec bridgetown serve'. If you change this file, please restart the 8 | # server process. 9 | # 10 | # For reloadable site metadata like title, SEO description, social media 11 | # handles, etc., take a look at src/_data/site_metadata.yml 12 | # 13 | # If you need help with YAML syntax, here are some quick references for you: 14 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml 15 | # https://learnxinyminutes.com/docs/yaml/ 16 | # 17 | 18 | baseurl: "" # OPTIONAL: the subpath of your site, e.g. /blog 19 | url: "" # the base hostname & protocol for your site, e.g. https://example.com 20 | 21 | permalink: pretty 22 | 23 | # timezone: America/Los_Angeles 24 | 25 | collections: 26 | products: 27 | output: true 28 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require "phaedra/rack_middleware" 2 | require_relative "./backend/config/application" 3 | 4 | # Uncomment the following if you wish to deploy a production Rails backend 5 | # separately from the static Bridgetown website. 6 | # unless ENV["RAILS_ENV"] == "production" 7 | 8 | unless ENV["ON_HEROKU"] 9 | use Phaedra::Middleware::Static, root: "output", urls: %w[/] 10 | use Phaedra::Middleware::NotFound, "output/404.html" 11 | end 12 | 13 | run NanoAPI 14 | -------------------------------------------------------------------------------- /fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml file generated for rails-nano-api-test on 2020-10-15T18:26:56-07:00 2 | 3 | app = "rails-nano-api-test" 4 | 5 | 6 | [[services]] 7 | internal_port = 8080 8 | protocol = "tcp" 9 | 10 | [services.concurrency] 11 | hard_limit = 25 12 | soft_limit = 20 13 | 14 | [[services.ports]] 15 | handlers = ["http"] 16 | port = "80" 17 | 18 | [[services.ports]] 19 | handlers = ["tls", "http"] 20 | port = "443" 21 | 22 | [[services.tcp_checks]] 23 | interval = 10000 24 | timeout = 2000 25 | -------------------------------------------------------------------------------- /frontend/javascript/index.js: -------------------------------------------------------------------------------- 1 | import "../styles/index.scss" 2 | import axios from "axios" 3 | 4 | async function refreshProduct(sleepTime) { 5 | const pageId = location.pathname 6 | const response = await fetch(RAILS_API + "/backend" + pageId + "?sleep_time=" + Math.min(sleepTime, 10)); 7 | const data = await response.text(); 8 | const template = document.createElement("template") 9 | template.innerHTML = data 10 | 11 | const contentNode = document.querySelector("main-content") 12 | while (contentNode.firstChild && !contentNode.firstChild.remove()); 13 | 14 | contentNode.replaceWith(template.content.querySelector("main-content")) 15 | document.querySelector("main").style.visibility = "visible" 16 | } 17 | 18 | window.refreshProduct = refreshProduct 19 | 20 | document.addEventListener("DOMContentLoaded", () => { 21 | if (document.querySelector("#hello-rails")) { 22 | document.querySelector("#hello-rails").addEventListener("click", () => { 23 | axios.get(RAILS_API + "/backend/nano").then((response) => { 24 | document.querySelector("rails-result").innerHTML = ` 25 |

${response.data.hello}

26 |

Booted at: ${response.data.boot_time}

27 |

Current server time: ${response.data.current_time}

28 | ` 29 | }) 30 | }) 31 | } 32 | 33 | if (document.querySelector("product-description")) { 34 | document.querySelector("main").style.visibility = "hidden" 35 | setTimeout(() => { 36 | document.querySelector("main").style.visibility = "visible" 37 | }, 1000) // in case the request takes too long... 38 | 39 | refreshProduct(0) 40 | } 41 | }) -------------------------------------------------------------------------------- /frontend/styles/index.scss: -------------------------------------------------------------------------------- 1 | $body-background: #fafafa; 2 | $body-color: #444; 3 | 4 | body { 5 | background: $body-background; 6 | color: $body-color; 7 | font-family: sans-serif; 8 | } 9 | 10 | a { 11 | color: darkgreen; 12 | } 13 | 14 | h1, nav, footer { 15 | text-align: center; 16 | } 17 | 18 | main { 19 | margin: 4rem auto 8rem; 20 | max-width: 60rem; 21 | } 22 | 23 | footer { 24 | color: #777; 25 | font-style: italic; 26 | } 27 | 28 | button { 29 | font-size: inherit; 30 | padding:10px; 31 | background:green; 32 | color:white; 33 | border:none; 34 | border-radius: 10px; 35 | box-shadow: 0px 3px 6px rgba(0,0,0,0.2); 36 | } 37 | 38 | .box { 39 | display: block; 40 | border: 1px dashed #ccc; 41 | background: white; 42 | padding: 20px; 43 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "new-bridgetown-site", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "build": "bundle exec bridgetown build", 7 | "watch": "bundle exec bridgetown build -w", 8 | "serve": "bundle exec bridgetown serve", 9 | "webpack-build": "webpack --mode production", 10 | "webpack-dev": "webpack --mode development -w", 11 | "deploy": "yarn webpack-build && yarn build", 12 | "sync": "node sync.js", 13 | "start": "node start.js", 14 | "puma": "bundle exec puma" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.9.0", 18 | "@babel/plugin-proposal-class-properties": "^7.8.3", 19 | "@babel/plugin-proposal-decorators": "^7.10.1", 20 | "@babel/plugin-transform-runtime": "^7.9.0", 21 | "@babel/preset-env": "^7.9.0", 22 | "babel-loader": "^8.1.0", 23 | "browser-sync": "^2.26.7", 24 | "concurrently": "^5.2.0", 25 | "css-loader": "^3.4.2", 26 | "file-loader": "^6.0.0", 27 | "mini-css-extract-plugin": "^0.9.0", 28 | "node-sass": "^4.13.1", 29 | "sass-loader": "^8.0.2", 30 | "style-loader": "^1.1.3", 31 | "webpack": "^4.42.1", 32 | "webpack-cli": "^3.3.11", 33 | "webpack-manifest-plugin": "^2.2.0" 34 | }, 35 | "dependencies": { 36 | "axios": "^0.20.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /plugins/builders/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcwhite/rails-nano/61c52eda6e56ea830a559ce87cbb62c7979112b5/plugins/builders/.keep -------------------------------------------------------------------------------- /plugins/rails_support.rb: -------------------------------------------------------------------------------- 1 | Bridgetown::Site.attr_accessor :controller -------------------------------------------------------------------------------- /plugins/site_builder.rb: -------------------------------------------------------------------------------- 1 | class SiteBuilder < Bridgetown::Builder 2 | # write builders which subclass SiteBuilder in plugins/builder 3 | end 4 | 5 | -------------------------------------------------------------------------------- /run_docker.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | docker build -t nano-rails-test . 4 | docker run -d --env-file=.env -p 8080:8080 -t nano-rails-test -------------------------------------------------------------------------------- /src/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 |

404

7 | 8 |

Page not found :(

9 |

The requested page could not be found.

10 | -------------------------------------------------------------------------------- /src/_components/footer.liquid: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/_components/head.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% capture page_title %}{{ title | strip_html | strip_newlines }}{% endcapture %} 4 | {% if page_title != "" %}{{ page_title | escape }} | {{ metadata.title | escape }}{% else %}{{ metadata.title | escape }}: {{ metadata.tagline | escape }}{% endif %} 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/_components/navbar.liquid: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/_data/site_metadata.yml: -------------------------------------------------------------------------------- 1 | # Site settings 2 | # These are used to personalize your new site. If you look in the HTML files, 3 | # you will see them accessed via {{ site.metadata.title }}, {{ site.metadata.email }}, and so on. 4 | # You can create any custom variable you would like, and they will be accessible 5 | # in the templates via {{ site.metadata.myvariable }}. 6 | 7 | title: Your awesome title 8 | tagline: This site is totally awesome 9 | email: your-email@example.com 10 | description: >- # this means to ignore newlines until "baseurl:" 11 | Write an awesome description for your new site here. It will appear in your document head meta (for Google search results) and in your feed.xml site description. 12 | -------------------------------------------------------------------------------- /src/_layouts/default.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% render "head", metadata: site.metadata, title: page.title %} 5 | 6 | 7 | {% render "navbar", metadata: site.metadata, page: page %} 8 | 9 |
10 | {{ content }} 11 |
12 | 13 | {% render "footer", metadata: site.metadata %} 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/_layouts/home.liquid: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |

Home Page

6 | 7 | {{ content }} 8 | -------------------------------------------------------------------------------- /src/_layouts/page.liquid: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |

{{ page.title }}

6 | 7 | {{ content }} 8 | -------------------------------------------------------------------------------- /src/_layouts/post.liquid: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | 5 |

{{ page.title }}

6 | 7 | {{ content }} 8 | -------------------------------------------------------------------------------- /src/_layouts/product.erb: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 | <% product = page %> 5 | 6 |

<%= link_to product.data.title, product.url %>

7 | 8 | 9 | <%= render "product", controller: site.controller, product: product %> 10 | 11 | 12 |
13 |

14 |


(Simulated Network Slowdown)

15 | 16 |
17 | 18 |

In case this isn't immediately clear, you're currently viewing a statically-rendered Bridgetown page which is dynamically-updated with fresh data via a backend Rails API which is re-rendering the content of the page via the Bridgetown gem loaded in the Rails controller. There is no Rails view layer at all. ActionView isn't even loaded. It's all the same Bridgetown templates, whether statically-generated or dynamically-rendered by Rails. 👀

19 | -------------------------------------------------------------------------------- /src/_partials/_product.erb: -------------------------------------------------------------------------------- 1 |

Sku: <%= product.data.sku %>

2 | 3 |

Price: 4 | 5 | <% if controller %> 6 | <%= controller.price %> 7 | <% else %> 8 | Contact Us for Price Details 9 | <% end %> 10 | 11 |

12 | 13 | 14 | <%== product.content %> 15 | 16 | -------------------------------------------------------------------------------- /src/_posts/2020-10-16-welcome-to-bridgetown.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: post 3 | title: "Your First Post on Bridgetown" 4 | date: 2020-10-16 21:22:58 -0700 5 | categories: updates 6 | --- 7 | 8 | You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `bridgetown serve`, which launches a web server and auto-regenerates your site when a file is updated. 9 | 10 | Bridgetown requires blog post files to be named according to the following format: 11 | 12 | `YEAR-MONTH-DAY-title.EXT` 13 | 14 | Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `EXT` is the file extension representing the format used in the file (for example, `md` for Markdown). After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works. 15 | 16 | Bridgetown also offers powerful support for code snippets: 17 | 18 | ```ruby 19 | def print_hi(name) 20 | puts "Hi, #{name}" 21 | end 22 | print_hi('Tom') 23 | #=> prints 'Hi, Tom' to STDOUT. 24 | ```` 25 | 26 | Check out the [Bridgetown docs](https://bridgetownrb.com/docs/) for more info on how to get the most out of Bridgetown. File all bugs/feature requests at [Bridgetown’s GitHub repo](https://github.com/bridgetownrb/bridgetown). If you have questions, you can ask them on [Bridgetown Community Forum](https://community.bridgetownrb.com). 27 | -------------------------------------------------------------------------------- /src/_products/toothpaste.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: product 3 | sku: tooth9183 4 | title: Totally Awesome Toothpaste 5 | price: $5.95 6 | --- 7 | 8 | This is the _best toothpaste_ you'll ever lay eyes (or teeth?) on. 9 | -------------------------------------------------------------------------------- /src/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: About “Rails Nano” 4 | permalink: /about/ 5 | --- 6 | 7 | This is a demo of a static website powered by Bridgetown communicating with a Rails API all in one repo. It's a single Rack app stack served by Puma, so it's somethign you can easily deploy to a Ruby-friendly host or use a Docker mesh like Fly.io and get the whole enchilada up and running in no time flat! 8 | 9 | [Check out the source code on GitHub](https://github.com/jaredcwhite/rails-nano) -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaredcwhite/rails-nano/61c52eda6e56ea830a559ce87cbb62c7979112b5/src/favicon.ico -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | --- 4 | 5 | Welcome to [Bridgetown + Rails Nano](https://github.com/jaredcwhite/rails-nano)! 6 | 7 | 8 | 9 | 10 |   11 | 12 | 13 |
14 | 15 |

Check Out Our Nifty Products!

16 | 17 | {% for product in site.products %} 18 |

{{ product.title }}

19 | {% endfor %} 20 | -------------------------------------------------------------------------------- /src/posts.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Posts 4 | permalink: /posts/ 5 | --- 6 | 7 | 14 | 15 | If you have a lot of posts, you may want to consider adding [pagination](https://www.bridgetownrb.com/docs/content/pagination)! 16 | -------------------------------------------------------------------------------- /src/products.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Products 4 | --- 5 | 6 |

Check Out Our Nifty Products!

7 | 8 | {% for product in site.products %} 9 |

{{ product.title }}

10 | {% endfor %} 11 | -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | const concurrently = require('concurrently'); 2 | 3 | // By default, configure Bridgetown to use port 4001 so Browsersync can use 4000 4 | // See also Browsersync settings in sync.js 5 | const port = 4001 6 | 7 | ///////////////// 8 | // Concurrently 9 | ///////////////// 10 | concurrently([ 11 | { command: "yarn webpack-dev", name: "Webpack", prefixColor: "yellow"}, 12 | { command: "sleep 4; yarn watch", name: "Bridgetown", prefixColor: "green"}, 13 | { command: "sleep 8; yarn puma --port " + port, name: "Puma", prefixColor: "red"}, 14 | { command: "sleep 10; yarn sync", name: "Live", prefixColor: "blue"} 15 | ], { 16 | restartTries: 3, 17 | killOthers: ['failure', 'success'], 18 | }).then(() => { console.log("Done.");console.log('\033[0G'); }, () => {}); 19 | -------------------------------------------------------------------------------- /sync.js: -------------------------------------------------------------------------------- 1 | const browserSync = require("browser-sync").create(); 2 | 3 | // You can change these configuration values: 4 | const proxy = "http://localhost:4001" 5 | const port = 4000 6 | const uiPort = 4002 7 | 8 | //////////////// 9 | // Browsersync 10 | //////////////// 11 | browserSync.init({ 12 | open: false, 13 | notify: false, 14 | proxy: proxy, 15 | port: port, 16 | files: "output/index.html", 17 | ghostMode: { 18 | clicks: false, 19 | forms: false, 20 | scroll: false, 21 | }, 22 | reloadDelay: 0, 23 | injectChanges: false, 24 | ui: { 25 | port: uiPort 26 | }, 27 | snippetOptions: { 28 | rule: { 29 | match: /<\/head>/i, 30 | fn: function (snippet, match) { 31 | return snippet + match; 32 | }, 33 | }, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack") 3 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 4 | const ManifestPlugin = require("webpack-manifest-plugin"); 5 | 6 | module.exports = { 7 | entry: "./frontend/javascript/index.js", 8 | devtool: "source-map", 9 | // Set some or all of these to true if you want more verbose logging: 10 | stats: { 11 | modules: false, 12 | builtAt: false, 13 | timings: false, 14 | children: false, 15 | }, 16 | output: { 17 | path: path.resolve(__dirname, "output", "_bridgetown", "static", "js"), 18 | filename: "all.[contenthash].js", 19 | }, 20 | resolve: { 21 | extensions: [".js", ".jsx"], 22 | }, 23 | plugins: [ 24 | new webpack.DefinePlugin({ 25 | RAILS_API: JSON.stringify(process.env.RAILS_API || "") 26 | }), 27 | new MiniCssExtractPlugin({ 28 | filename: "../css/all.[contenthash].css", 29 | }), 30 | new ManifestPlugin({ 31 | fileName: path.resolve(__dirname, ".bridgetown-webpack", "manifest.json"), 32 | }), 33 | ], 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.(js|jsx)/, 38 | use: { 39 | loader: "babel-loader", 40 | options: { 41 | presets: ["@babel/preset-env"], 42 | plugins: [ 43 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 44 | ["@babel/plugin-proposal-class-properties", { "loose" : true }], 45 | [ 46 | "@babel/plugin-transform-runtime", 47 | { 48 | helpers: false, 49 | }, 50 | ], 51 | ], 52 | }, 53 | }, 54 | }, 55 | { 56 | test: /\.(s[ac]|c)ss$/, 57 | use: [ 58 | MiniCssExtractPlugin.loader, 59 | "css-loader", 60 | { 61 | loader: "sass-loader", 62 | options: { 63 | sassOptions: { 64 | includePaths: [ 65 | path.resolve(__dirname, "src/_components") 66 | ], 67 | }, 68 | }, 69 | }, 70 | ], 71 | }, 72 | { 73 | test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/, 74 | loader: "file-loader", 75 | options: { 76 | outputPath: "../fonts", 77 | publicPath: "../fonts", 78 | }, 79 | }, 80 | ], 81 | }, 82 | }; 83 | --------------------------------------------------------------------------------