• Stars
    star
    149
  • Rank 240,305 (Top 5 %)
  • Language
    Ruby
  • License
    MIT License
  • Created about 4 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Build & serve static sites (e.g. Jekyll, Hugo) from your Rails app

static-rails

CircleCI

Build and serve your static sites from your Rails app

tl;dr in development, static-rails runs your static site generators & proxies requests; in production, it compiles and serves the final assets

Static site generators are hot right now. Maybe you're hip with "the Jamstack", or maybe your API documentation is generated by Hugo, or maybe your marketing folks use Jekyll for the company blog.

Up until now, compiling static assets with any degree of sophistication beyond dumping them in your app's public/ directory represented a significant deviation from the "Rails Way". But the alternative—keeping your static sites wholly separate from your Rails app—raises myriad operational challenges, from tracking multiple git repositories, to managing multiple server configurations, to figuring out a way to share common JavaScript and CSS assets between them all.

No longer! static-rails lets you use your static asset generators of choice without forcing you to abandon your monolithic Rails architecture.

Here's what it does:

  • In development, static-rails launches your sites' local servers and then proxies any requests to wherever you've mounted them in your Rails app so you can start a single server and transition work between your static sites and Rails app seamlessly

  • When deploying, static-rails will compile all your static assets when rake assets:precompile is run, meaning your assets will be built automatically when pushed to a platform like Heroku

  • In production, static-rails will serve your sites' compiled assets from disk with a similar features and performance to what you're familiar with if you've ever hosted files out of your public/ directory

Install

Add this to your Gemfile:

gem "static-rails"

Then run this generator to create a configuration file config/initializers/static.rb:

$ rails g static_rails:initializer

You can check out the configuration options in the generated file's comments.

Want an example of setting things up? You're in luck, there's an example app right in this repo!

Configuring the gem

(Want to dive right in? The generated initializer enumerates every option and the example app's config sets up 4 sites.)

Top-level configuration

So, what should you stick in your initializer's StaticRails.config do |config| block? These options are set right off the config object and control the overall behavior of the gem itself, across all your static sites:

  • config.proxy_requests (Default: !Rails.env.production?) when true, the gem's middleware requests that match where you've mounted your static site and proxy them to the development server

  • config.serve_compiled_assets (Default: Rails.env.production?) when true, the gem's middleware will find your static assets on disk and serve them using the same code that Rails uses to serve files out of public/

  • config.ping_server_timeout (Default: 5) the number of seconds that (when proxy_requests is true, that the gem will wait for a response from a static site's server on any given request before timing out and raising an error

  • config.set_csrf_token_cookie (Default: false) when true, the gem's middleware will set a cookie named _csrf_token with each request of your static site. You can use this to set the 'x-csrf-token' header on any requests from your site back to routes hosted by the Rails app that are protected from CSRF forgery (if you're not using Rails' cookie store for sessions or you're okay with API calls bypassing Rails CSRF, leave this off)

Configuring your static sites themselves

To tell the gem about your static sites, assign an array of hashes as sites (e.g. config.sites = [{…}]). Each of those hashes have the following options:

  • name (Required) A unique name for the site (primarily used for logging)

  • source_dir (Required) The file path (relative to the Rails app's root) to the static site's project directory

  • url_subdomain (Default: nil) Constrains the static site's assets to only be served for requests to a given subdomain (e.g. for a Rails app hosting example.com, a Hugo site at blog.example.com would set this to "blog")

  • url_root_path (Default: /) The base URL path at which to mount the static site (e.g. if you want your Jekyll site hosted at example.com/docs, you'd set this to /docs). For most static site generators, you'll want to configure it to serve assets from the same path so links and other references are correct (see below for examples)

  • url_skip_paths_starting_with (Default: []) If you want to mount your static site to / but allow the Rails app to serve APIs from /api, you can set the path prefix ["/api"] here to tell the gem's middleware not to try to proxy or serve the request from your static site, but rather let Rails handle it

  • start_server (Default `!Rails.env.production?) When true, the gem will start the site's server (and if it ever exits, restart it) as your Rails app is booting up. All output from the server will be forwarded to STDOUT/STDERR

  • server_command (Required if start_server is true) the command to run to start the site's server, from the working directory of source_dir (e.g. hugo server)

  • ping_server (Default: true) if this and start_server are both true, then wait to proxy any requests until the server is accepting TCP connections

  • env (Default: {}) any environment variables you need to pass to either the server or compile commands (e.g. env: {"BUNDLE_PATH" => "vendor/bundle"}). Note that this configuration file is Ruby, so if you need to provide different env vars based on Rails environment, you have the power to do that!

  • server_host (Default: localhost) the host your static site's server will run on

  • server_port (Required if proxy_requests is true) the port your static site's server will accept requests on

  • server_path (Default: "/") the root URL path to which requests should be proxied

  • compile_comand (Required) the command to be run by both the static:compile and assets:precompile Rake commands (e.g. npm run build), with working directory set to the site's source_dir

  • compile_dir (Required when serve_compiled_assets is true) the root file path to which production assets are compiled, relative to the site's source_dir

  • compile_404_file_path (Optional) When serve_compiled_assets is true, this file (relative to the compile_dir) will be served whenever the request's path does not match a file on disk

Configuring your static site generators

Assuming you won't be mounting your static site to your app's root / path, you'll probably need to configure its base URL path somehow. Here are some tips (and if your tool of choice isn't listed, we'd love a pull request!):

Using Jekyll

If you have a Jekyll app that you plan on serving from a non-root path (say, /docs), then you'll need to set the baseurl in _config.yml:

baseurl: "/docs"

(Note that this means running the Jekyll application directly using bundle exec jekyll serve will also start serving at http://127.0.0.1:4000/docs/)

Using Hugo

If you are mounting your Hugo app to anything but the root path, you'll need to specify that path in the baseURL of your root config.toml file, like so:

baseURL = "http://blog.example.com/docs"

Additionally, getting Hugo to play nicely when being proxied by your Rails server in development and test can be a little tricky, because most themes will render fully-qualified URLs into markup when running the hugo server command. That means if you're forwarding http://blog.localhost:3000/docs to a Hugo server running on http://localhost:1313, it's very likely the static files (e.g. all the links on the page) will have references to http://localhost:1313, which may result in accidentally navigating away from your Rails development server unexpectedly.

To mitigate this, there are a few things you can do:

  • Favor .RelPermalink in your templates over .Permalink where possible.
  • In place of referring to {{.Site.BaseURL}} in your templates, generate a base path with {{ "/" | relURL }} (given the above baseURL, this will render "/marketing/")

Also, because Hugo will serve /livereload.js from the root, live-reloading probably won't work in development when running through the static-rails proxy. You might consider disabling it with --disableLiveReload unless you're serving Hugo from a root path ("/").

A static-rails config for a Hugo configuration in sites might look like:

  {
    name: "docs",
    url_root_path: "/docs",
    source_dir: "static/docs",
    server_command: "hugo server",
    server_port: 8080,
    compile_command: "hugo",
    compile_dir: "static/docs/public"
  }

Using Eleventy

If you are mounting your Eleventy app to anything but the root path, you'll want to configure a path prefix in .eleventy.js

module.exports = {
  pathPrefix: "/docs/"
}

Alternatively, you can specify this from the command line with --pathprefix /docs.

A static-rails config for an Eleventy configuration in sites might look like:

  {
    name: "docs",
    url_root_path: "/docs",
    source_dir: "static/docs",
    server_command: "npx @11ty/eleventy --serve --pathprefix /docs",
    server_port: 8080,
    compile_command: "npx @11ty/eleventy --pathprefix /docs",
    compile_dir: "static/docs/_site"
  }

Using Gatsby

⚠️ Gatsby is unlikely to work in development mode, due to this issue, wherein all the assets are actually served over a socket.io WebSocket channel and not able to be proxied effectively. ⚠️

If you're mounting a Gatsby site to a non-root path (e.g. in static-rails, you've configured its url_root_path to, say, careers), then you'll want to configure the same root path in Gatsby as well, so that its development servers and built assets line up correctly.

To do this, first add the pathPrefix property to gatsby-config.js:

module.exports = {
  pathPrefix: `/careers`,
  // …
}

Next, add the flag --prefix-paths to both the Gatsby site's server_command and compile_command, or else the setting will be ignored.

A static-rails config for a Gatsby configuration in sites might look like:

  {
    name: "gatsby",
    url_root_path: "/docs",
    source_dir: "static/docs",
    server_command: "npx gatsby develop --prefix-paths",
    server_port: 8000,
    compile_command: "npx gatsby build --prefix-paths",
    compile_dir: "static/docs/public"
  },

Code of Conduct

This project follows Test Double's code of conduct for all community interactions, including (but not limited to) one-on-one communications, public posts/comments, code reviews, pull requests, and GitHub issues. If violations occur, Test Double will take any action they deem appropriate for the infraction, up to and including blocking a user from the organization's repositories.

More Repositories

1

standard

🌟 Ruby Style Guide, with linter & automatic code fixer
Ruby
2,104
star
2

testdouble.js

A minimal test double library for TDD with JavaScript
JavaScript
1,407
star
3

suture

🏥 A Ruby gem that helps you refactor your legacy code
Ruby
1,401
star
4

contributing-tests

1,104
star
5

scripty

Because no one should be shell-scripting inside a JSON file.
JavaScript
957
star
6

test-smells

A workbook repository of example test smells and what to do about them.
JavaScript
421
star
7

jasmine-rails

A Jasmine runner for rails projects that's got you covered in both the terminal and the browser
JavaScript
378
star
8

referral

🕵️‍♀️ Find, filter, and sort your Ruby code's definitions & references
Ruby
343
star
9

cypress-rails

Helps you write Cypress tests of your Rails app
Ruby
312
star
10

good-migrations

Prevent Rails from auto-loading app/ code when running database migrations
Ruby
294
star
11

mocktail

🥃 Take your Ruby, and make it a double!
Ruby
273
star
12

maybe_later

Run code after the current Rack response or Rails action completes
Ruby
132
star
13

time_up

⏱ Create and manage multiple timers to tell where your Ruby code's time is going
Ruby
117
star
14

teenytest

A very simple, zero-config test runner for Node.js
JavaScript
97
star
15

test_data

A fast & reliable system for managing your Rails application's test data
Ruby
95
star
16

put

Ruby
92
star
17

theredoc

Makes your multi-line JavaScript strings look good
JavaScript
79
star
18

quibble

Makes it easy to replace require'd dependencies.
JavaScript
78
star
19

react-decoupler

JavaScript
55
star
20

noncommittal

A gem that ensures test isolation by preventing your Rails tests from committing to the database
Ruby
46
star
21

real-world-testing-video

testdouble/real-world-testing + screencasts
JavaScript
40
star
22

clojurescript.csv

A ClojureScript library for reading and writing CSV
Clojure
37
star
23

testdouble-jest

A testdouble.js extension to add support for Jest module mocking
JavaScript
37
star
24

grunt-markdown-blog

Grunt task for building a blog with markdown posts & underscore templates
CoffeeScript
36
star
25

ought

A dumb assertion library with smart diffs for JavaScript
JavaScript
34
star
26

cypress-capybara

Capybara finders re-implemented as custom Cypress commands
JavaScript
33
star
27

minitest-suite

Re-order your Minitest suite into logical sub-suites/groups
Ruby
32
star
28

rust-ffi-example

An example project that shows how to use FFI between Rust and Unity.
Rust
31
star
29

gem_dating

How old is that anyway?
Ruby
29
star
30

rspec-graphql_response

Verify ruby-graphql responses with a :graphql spec type
Ruby
25
star
31

ecto_resource

A simple module to clear up the boilerplate of CRUD resources in Phoenix context files.
Elixir
22
star
32

java-testing-example

An example project that's configured for JUnit and Mocha
Java
20
star
33

real-world-testing

Workshop for Testing JavaScripts
JavaScript
17
star
34

unusual-spending

A code kata for outside-in TDD in Node.js
JavaScript
16
star
35

webpacker-assets-demo

A demo repo to show how to reference images and styles when using Webpacker instead of Sprockets
Ruby
13
star
36

javascript-testing-tactics

The Missing Manual for writing great JavaScript Testing
13
star
37

magic_email_demo

An example Rails app that implements passwordless authentication by emailing a magic link
Ruby
12
star
38

scheduled-merge

Merge PRs on a specified date using Labels
JavaScript
12
star
39

rust-ffi-complex-example

Follow-up project to shows how to use complex data structures between Unity and Rust.
Rust
12
star
40

todos

JavaScript
11
star
41

grunt-asset-fingerprint

CoffeeScript
9
star
42

covet

Instruct a remote Express app to stub APIs via HTTP requests
CoffeeScript
9
star
43

bored

Gives you ideas of stuff to do when you're bored
Ruby
8
star
44

rails-twitter-oauth-example

An example Rails app that implements log in to Twitter via OAuth
Ruby
8
star
45

javascript-tdd-examples

Yet another little toy repo of javascript tdd examples
JavaScript
8
star
46

baizen

BAI file format parser
Clojure
8
star
47

tiny_type

Fast, easy, and simple runtime type checking for Ruby
Ruby
8
star
48

halfpipe

A Pipedrive client for Ruby that doesn't do half of what you want it to 🛹
Ruby
7
star
49

forewarn

Configure method invocation warnings for deprecated or dangerous methods (e.g. mutable methods in default-frozen String literals in Ruby 3)
Ruby
7
star
50

dependable

A minimalist dependency injection framework for node.js
JavaScript
7
star
51

grunt-jasmine-bundle

A "spec" grunt task for Jasmine that includes a standard pack of helpers (jasmine-given, jasmine-stealth, jasmine-only). Uses minijasminenode.
CoffeeScript
6
star
52

servme

gimme for integration tests
Ruby
6
star
53

intro-to-node

Introduction to Node.js Workshop
JavaScript
6
star
54

standardrb

You're probably in the wrong place. This is an alias for the gem standard, whose binary is standardrb
Ruby
6
star
55

bootboot-example

An example of using boot-boot.
Ruby
5
star
56

testdrivennode

Test Driven Node.js Precompiler for Codemash 2014
JavaScript
5
star
57

docunit

Makes sure the code examples in your docs actually work
CoffeeScript
5
star
58

railsconf-test-drive-javascript

JavaScript
5
star
59

json-to-svg-to-pdf

Converts JSON/CSON input through SVG templates and renders them to PDF using librsvg
JavaScript
5
star
60

imagemagick-macos-font-setup

Sets up user fonts for imagemagick on macOS
Shell
5
star
61

jasmine-before-all

Adds a done-friendly beforeAll global function to Jasmine
JavaScript
5
star
62

good-day

An example ember + active_model_serializers + rails + lineman app
JavaScript
5
star
63

sockem

A wrapper around the ActionCable JS client to ensure eventual delivery for requests
Ruby
5
star
64

satisfaction

Satisfaction tracker for your work!
Ruby
5
star
65

headerify

Browserify plugin to add a comment containing lib name, version, description, and homepage to the top of the bundle
JavaScript
4
star
66

SublimeLinter-contrib-standardrb

SublimeLinter 3 plugin for Ruby, using Standard, a wrapper for Rubocop.
Python
4
star
67

rails-upsert-all-demo

An example app that demos use of Rails 6 `upsert_all` method
Ruby
4
star
68

cobbler

A tool to generate résumés for Test Double agents.
JavaScript
3
star
69

react-d3-blog-example

Example for Blog Post
JavaScript
3
star
70

supertitle

Converts between subtitles and transcript formats
Ruby
3
star
71

time_traveler_demo

A Rails app that demoes time traveling both Ruby and Postgres in lock-step with one another
Ruby
3
star
72

devops-standards

Standard Auditing Tools for DevSecOps best practices
Python
3
star
73

least

A pager that can dynamically filter log lines
Ruby
3
star
74

defuse

An API to define and use JavaScript in a module-y way. And nothing else.
JavaScript
3
star
75

lockify

Ensure an async function does not run concurrently.
JavaScript
3
star
76

jasmine-matcher-wrapper

A utility to wrap Jasmine 1.x argument matchers for use under Jasmine 2.x
CoffeeScript
3
star
77

testdouble-nock

JavaScript
3
star
78

teenytest-promise

Promise support for asynchronous teenytest tests
JavaScript
3
star
79

npm-tree

Generates a tree of all the node.js modules depended on by a module
CoffeeScript
3
star
80

function-names-at-line

Name the functions found at a particular line number in some JavaScript source
JavaScript
2
star
81

tradecraft

CSS
2
star
82

course-cypress-intro-demo-app

Demo application to supplement Test Double's End-to-end Testing with Cypress intro video course
Ruby
2
star
83

standard-ruby-action

Ruby
2
star
84

rails-training-201

A demo app for Rails 201 students to build on!
Ruby
2
star
85

testdrivennode-frontend

JavaScript
2
star
86

yslow-grader

A little Node.js wrapper for YSlow for PhantomJS
CoffeeScript
2
star
87

ios-learnins

Objective-C
2
star
88

fetcher

Fetches things based on a JSON recipe hosted in a repository
CoffeeScript
2
star
89

backbone-fixins

Boilerplate that strengthens your backbone
JavaScript
2
star
90

ruby_rails_training_github

Ruby
1
star
91

prioritize-api

Elixir
1
star
92

baruco2014-angular

Ruby
1
star
93

oredev2014-angular

JavaScript
1
star
94

double-up

Slack scheduler to set up rotating brunch pairings
Ruby
1
star
95

elm-testdouble

A minimal test double library for TDD with Elm
Elm
1
star
96

doubot

test double's hubot
CoffeeScript
1
star
97

jasmine-example

JavaScript
1
star
98

arg-that

arg-that makes it easier to assert equality on complex objects
Ruby
1
star
99

react-routing-example

Example for screencast on client-side routing in React
CSS
1
star
100

cucumber-peel

Provides a CLI to search a project's step implementations for a given step
Ruby
1
star