8000 Implement Rails 8 no-build approach with importmaps and propshaft by Copilot · Pull Request #2 · turgs/sitepress · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Implement Rails 8 no-build approach with importmaps and propshaft #2

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

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
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
248 changes: 248 additions & 0 deletions HOTWIRE_TAILWIND_GUIDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
# Hotwire & Tailwind CSS Integration Guide

This guide shows how to use Hotwire (Turbo + Stimulus) and Tailwind CSS with Sitepress in a no-build setup.

## What's Included

✅ **Hotwire Turbo** - Fast navigation without full page reloads
✅ **Stimulus** - Modest JavaScript framework for adding behavior
✅ **Tailwind CSS** - Utility-first CSS framework
✅ **No build tools** - Everything works without npm/webpack/esbuild

## Quick Start

The dummy application in `sitepress-rails/spec/dummy` demonstrates the complete setup. Visit `/hotwire_demo.html` to see all features working together.

## File Structure

```
app/
├── assets/
│ ├── javascripts/
│ │ ├── application.js # Main entry point
│ │ └── controllers/
│ │ ├── index.js # Stimulus loader
│ │ ├── application.js # Stimulus app setup
│ │ ├── hello_controller.js # Example controller
│ │ └── counter_controller.js # Interactive demo
│ └── stylesheets/
│ └── application.css # CSS (works with Tailwind)
├── views/layouts/
│ └── application.html.erb # Layout with Tailwind CDN
└── content/pages/
└── hotwire_demo.html.erb # Demo page

config/
└── importmap.rb # JavaScript module definitions
```

## Setup Instructions

### 1. Importmap Configuration

Add Hotwire dependencies to `config/importmap.rb`:

```ruby
# config/importmap.rb
pin "application", preload: true

# Hotwire dependencies
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/assets/javascripts/controllers", under: "controllers"
```

### 2. Application JavaScript

Update `app/assets/javascripts/application.js`:

```javascript
// app/assets/javascripts/application.js
import "@hotwired/turbo-rails"
import "controllers"

console.log("Sitepress with Hotwire loaded!")
```

### 3. Stimulus Setup

Create controller files:

```javascript
// app/assets/javascripts/controllers/application.js
import { Application } from "@hotwired/stimulus"

const application = Application.start()
application.debug = false
window.Stimulus = application

export { application }
```

```javascript
// app/assets/javascripts/controllers/index.js
import { application } from "controllers/application"
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"

eagerLoadControllersFrom("controllers", application)
```

### 4. Example Stimulus Controller

```javascript
// app/assets/javascripts/controllers/hello_controller.js
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
static targets = ["output"]

greet() {
this.outputTarget.textContent = "Hello from Stimulus!"
}
}
```

### 5. Layout with Tailwind

Update your layout file:

```erb
<!-- app/views/layouts/application.html.erb -->
<!DOCTYPE html>
<html>
<head>
<title>Your Site</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>

<!-- Tailwind CSS CDN -->
<script src="https://cdn.tailwindcss.com"></script>

<!-- Propshaft CSS -->
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

<!-- Importmap JavaScript -->
<%= javascript_importmap_tags %>
</head>
<body class="bg-gray-50">
<%= yield %>
</body>
</html>
```

### 6. Using in Pages

```erb
<!-- pages/example.html.erb -->
---
title: "Example Page"
---

<div class="max-w-4xl mx-auto p-8">
<h1 class="text-3xl font-bold text-blue-600">Hello Sitepress!</h1>

<!-- Stimulus controller -->
<div data-controller="hello" class="mt-8">
<button
data-action="click->hello#greet"
class="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
>
Click me!
</button>
<div data-hello-target="output" class="mt-4"></div>
</div>

<!-- Turbo navigation -->
<a href="/other-page.html" class="text-blue-500 hover:underline">
Navigate with Turbo
</a>
</div>
```

## Tailwind CSS Options

### Option 1: CDN (Development)
Perfect for getting started quickly:

```html
<script src="https://cdn.tailwindcss.com"></script>
```

### Option 2: Standalone CLI (Production)
For production apps, use the Tailwind CLI:

```bash
# Download Tailwind CLI
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
chmod +x tailwindcss-linux-x64

# Generate CSS
./tailwindcss-linux-x64 -i app/assets/stylesheets/application.css -o public/stylesheets/application.css --watch
```

### Option 3: tailwindcss-rails Gem
Add to your Gemfile for Rails integration:

```ruby
gem 'tailwindcss-rails'
```

## Deployment with Kamal 2

For deploying with Kamal 2, your `Dockerfile` can be simple:

```dockerfile
FROM ruby:3.2

WORKDIR /app
COPY Gemfile* ./
RUN bundle install --without development test

COPY . .

# For Tailwind standalone CLI (optional)
RUN curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 && \
chmod +x tailwindcss-linux-x64 && \
./tailwindcss-linux-x64 -i app/assets/stylesheets/application.css -o public/stylesheets/application.css --minify

EXPOSE 8080
CMD ["sitepress", "server", "--bind-address", "0.0.0.0"]
```

`config/deploy.yml`:

```yaml
service: my-sitepress-site
image: my-registry/sitepress-site

servers:
- your-server-ip

env:
secret:
- RAILS_MASTER_KEY

proxy:
host: example.com
```

## Benefits

- ✅ **Zero build complexity** - No webpack, esbuild, or npm required
- ✅ **Fast development** - Instant reloads without compilation
- ✅ **Modern UX** - Turbo navigation + Stimulus interactivity
- ✅ **Beautiful styling** - Tailwind utility classes
- ✅ **Rails conventions** - Familiar importmap + propshaft approach
- ✅ **Production ready** - Can compile Tailwind for optimization

## Examples in Action

Visit the demo page at `/hotwire_demo.html` in the dummy app to see:

- Turbo navigation between pages
- Stimulus controllers adding interactivity
- Tailwind CSS styling everything beautifully
- All working together without any build step

This setup gives you the full modern web development experience while maintaining Rails 8's philosophy of simplicity and convention over configuration.
11 changes: 4 additions & 7 deletions sitepress-rails/lib/sitepress/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,12 @@ class Engine < ::Rails::Engine
app.paths["app/views"].push site.root_path.expand_path
app.paths["app/views"].push site.pages_path.expand_path
app.paths["app/models"].push site.models_path.expand_path

# Set for view_components to load at ./components
app.config.autoload_paths << File.expand_path("./components")
end

# Configure sprockets paths for the site.
initializer :set_manifest_file_path, before: :append_assets_path do |app|
manifest_file = sitepress_configuration.manifest_file_path.expand_path
app.config.assets.precompile << manifest_file.to_s if manifest_file.exist?
# Set for view_components to load at ./components
initializer :add_components_autoload_path, before: :set_autoload_paths do |app|
components_path = File.expand_path("./components")
app.config.autoload_paths.push(components_path) unless app.config.autoload_paths.include?(components_path)
end

# Configure Sitepress with Rails settings.
Expand Down
5 changes: 4 additions & 1 deletion sitepress-rails/sitepress-rails.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ Gem::Specification.new do |spec|
# We don't need every single rals rependency, so grab the subset here.
spec.add_dependency "railties", rails_version
spec.add_dependency "actionpack", rails_version
spec.add_dependency "sprockets-rails", ">= 2.0.0"
spec.add_dependency "propshaft", ">= 0.1.0"
spec.add_dependency "importmap-rails", ">= 1.0.0"
spec.add_dependency "stimulus-rails", ">= 1.0.0"
spec.add_dependency "turbo-rails", ">= 1.0.0"
end
5 changes: 0 additions & 5 deletions sitepress-rails/spec/dummy/app/assets/config/manifest.js

This file was deleted.

20 changes: 7 additions & 13 deletions sitepress-rails/spec/dummy/app/assets/javascripts/application.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require_tree .
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails

// Hotwire Turbo & Stimulus setup
import "@hotwired/turbo-rails"
import "controllers"

console.log("Sitepress Rails 8 no-build application loaded with Hotwire")
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Application } from "@hotwired/stimulus"

const application = Application.start()

// Configure Stimulus development experience
application.debug = false
window.Stimulus = application

export { application }
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="counter"
export default class extends Controller {
static targets = ["count"]

connect() {
this.count = 0
console.log("Counter controller connected!")
}

increment() {
this.count++
this.countTarget.textContent = this.count
}

decrement() {
this.count--
this.countTarget.textContent = this.count
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Controller } from "@hotwired/stimulus"

// Connects to data-controller="hello"
export default class extends Controller {
static targets = ["output"]

connect() {
console.log("Hello controller connected!")
}

greet() {
this.outputTarget.textContent = "Hello from Stimulus!"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Import and register all your controllers from the importmap under controllers/*

import { application } from "controllers/application"

// Eager load all controllers defined in the import map under controllers/**/*_controller
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
eagerLoadControllersFrom("controllers", application)
Loading
0