staticky 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +9 -0
  4. data/README.md +19 -6
  5. data/lib/staticky/builder.rb +4 -2
  6. data/lib/staticky/cli/commands/generate.rb +1 -1
  7. data/lib/staticky/phlex/view_helpers.rb +6 -0
  8. data/lib/staticky/resources/plugins/prelude.rb +8 -6
  9. data/lib/staticky/server.rb +5 -16
  10. data/lib/staticky/server_plugin.rb +37 -0
  11. data/lib/staticky/server_plugins/live_reloading.rb +58 -0
  12. data/lib/staticky/utils.rb +63 -0
  13. data/lib/staticky/version.rb +1 -1
  14. data/lib/staticky.rb +3 -1
  15. data/site_template/Gemfile +1 -2
  16. data/site_template/Rakefile +3 -1
  17. data/site_template/{lib/component.rb → app/views/application_component.rb} +1 -1
  18. data/site_template/app/views/application_layout.rb +4 -0
  19. data/site_template/app/views/application_page.rb +5 -0
  20. data/site_template/app/views/errors/not_found.rb +1 -1
  21. data/site_template/app/views/errors/service_error.rb +1 -1
  22. data/site_template/app/views/layouts/error.rb +4 -6
  23. data/site_template/app/views/layouts/head.rb +11 -3
  24. data/site_template/app/views/layouts/site.rb +10 -23
  25. data/site_template/app/views/pages/home.rb +1 -1
  26. data/site_template/app/views/ui/footer.rb +1 -1
  27. data/site_template/app/views/ui/navbar.rb +1 -1
  28. data/site_template/config/boot.rb +1 -2
  29. data/site_template/config/staticky.rb +1 -0
  30. data/site_template/config/vite.json +6 -1
  31. data/site_template/frontend/entrypoints/application.js +1 -4
  32. data/site_template/frontend/tailwindcss/variable_font_plugin.js +1 -1
  33. data/site_template/lib/icon.rb +1 -1
  34. data/site_template/lib/vite_helpers.rb +8 -0
  35. data/site_template/nginx.conf +8 -1
  36. data/site_template/package.json +10 -15
  37. data/site_template/tailwind.config.js +12 -8
  38. data/site_template/vite.config.ts +0 -6
  39. metadata +9 -7
  40. data/site_template/frontend/turbo_transitions.js +0 -54
  41. data/site_template/lib/layout.rb +0 -4
  42. data/site_template/lib/page.rb +0 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 015d7c615ec3f67937c4c6a7c26d4818f394386f8a3bc6b2b10bb834f1759fb8
4
- data.tar.gz: 6e2a25655085984a6e37ace773f55a8ccfd8ffc702de127f1ec8748df391db13
3
+ metadata.gz: 47010ab3ec9422b0d978c1e5c1ac789e2fb756e03ae5324e41687299692b6a1a
4
+ data.tar.gz: efb0ecc55c89126bbeb694bc22ae6b3d44e7938a4e117985639d91e5ed678ac1
5
5
  SHA512:
6
- metadata.gz: ed18ccdf8f86c13249e5fbc17971ac0b5155a3d15c3760e9308c941892c9f7f488363e5d8a858ad26c67345eb7477ca7c4f2955b35d2b3fc227b5f96f1e16c15
7
- data.tar.gz: '09147233cded8c538c9b2e2f86c917c94b1c8c9afc5b028cebc482c364c8586fd4efeee2157bb29cccdb7cfdcca7dda8beb6ef027aea7507b94bdf069ee1ab9b'
6
+ metadata.gz: dec68edcbda555bb736b836f7a6de1b2a87010a3ccd1157d2ab0d6bd6168c3824232851099db40338018c96005652dcb9c4d11ae8e8704fc68571107581813a0
7
+ data.tar.gz: f8995712d64ff925e530b757c67d8a326fa8f705373a8a2d07ca04212f462711bba5318a38efa25730026aaed7f6d232bc62c9fb6a9dd0db544bc5dd126f62b6
data/.rubocop.yml CHANGED
@@ -9,3 +9,6 @@ inherit_gem:
9
9
 
10
10
  AllCops:
11
11
  TargetRubyVersion: 3.3
12
+
13
+ Lint/NumberConversion:
14
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.1] - 202-12-09
4
+
5
+ - Fixes flashes of unstyled content by adding `vite_stylesheet_tag`
6
+
7
+ ## [0.3.0] - 2024-12-09
8
+
9
+ - Adds live reloading of pages during development using server side events
10
+ - Integrates live reloading with vite in site template
11
+
3
12
  ## [0.2.0] - 2024-09-24
4
13
 
5
14
  - Adds plugin system akin to Roda and Sequel for `Resource` and `Router`
data/README.md CHANGED
@@ -8,7 +8,7 @@ first-class support for Phlex components.
8
8
  I wanted to extend the developer experience of something like Rails but focused
9
9
  on static sites.
10
10
 
11
- I am using this at https://taintedcoders.com. (soon)
11
+ I am currently using this to create https://taintedcoders.com
12
12
 
13
13
  - Hot reloading in development with Roda serving static files
14
14
  - Docker deployment with NGINX
@@ -61,7 +61,7 @@ Options:
61
61
  --url=VALUE, -u VALUE # Site URL, default: "https://example.com"
62
62
  --title=VALUE, -t VALUE # Site title, default: "Example"
63
63
  --description=VALUE, -d VALUE # Site description, default: "Example site"
64
- --twitter=VALUE, -t VALUE # Twitter handle, default: ""
64
+ --twitter=VALUE, -x VALUE # Twitter handle, default: ""
65
65
  --help, -h # Print this help
66
66
  ```
67
67
 
@@ -274,9 +274,9 @@ The `prelude` plugin provides the following methods:
274
274
 
275
275
  |Method|Description|
276
276
  |------|-----------|
277
- |`filepath`|`Pathname` of where the component's output will be written to|
277
+ |`build_path`|`Pathname` of where the component's output will be written to|
278
278
  |`read`|Read the output of the resource from the file system|
279
- |`basename`|The file basename (e.g. `index.html`) for the resource|
279
+ |`filepath`|The file path (e.g. `about/index.html`) for the resource|
280
280
  |`root?`|Whether or not the resource is the root path|
281
281
 
282
282
  While the `phlex` plugin provides:
@@ -325,7 +325,7 @@ use plain old Phlex components if you like.
325
325
 
326
326
  ```ruby
327
327
  module Posts
328
- class Show < Component
328
+ class Show < ApplicationComponent
329
329
  param :post, reader: false
330
330
 
331
331
  def around_template(&)
@@ -379,7 +379,7 @@ link_to("Some link", "/")
379
379
  ### Building your site
380
380
 
381
381
  When you are developing your site you run `bin/dev` to start your development
382
- server on [http://localhost:9292](http://localhost:9292).
382
+ server on [http://localhost:3000](http://localhost:3000).
383
383
  This will automatically reload after a short period when you make changes.
384
384
 
385
385
  Assets are handled by Vite by default, but you can have whatever build process
@@ -417,6 +417,18 @@ These are available in your Phlex components under `helpers` (if you are using
417
417
  the site template). This matches what you might expect when using Phlex in
418
418
  Rails with `phlex-rails`.
419
419
 
420
+ ## Live reloading
421
+
422
+ The development server has been hooked up with some live reloading using
423
+ server-side events.
424
+
425
+ A javascript script is inserted into the `<head>` tag during development which
426
+ will poll the `_staticky/live_reloading` endpoint. If files have changed then
427
+ a reload is triggered with `Turbo` if available, and just plain
428
+ `window.location.reload()` if not.
429
+
430
+ You can toggle this off by setting `live_reloading` to false inside the config.
431
+
420
432
  ## Configuration
421
433
 
422
434
  We can override the configuration according to the settings defined on the main
@@ -429,6 +441,7 @@ Staticky.configure do |config|
429
441
  config.root_path = Pathname(__dir__)
430
442
  config.logger = Logger.new($stdout)
431
443
  config.server_logger = Logger.new($stdout)
444
+ config.live_reloading = false
432
445
  end
433
446
  ```
434
447
 
@@ -2,7 +2,9 @@
2
2
 
3
3
  module Staticky
4
4
  class Builder
5
- include Dry::Events::Publisher[:builder]
5
+ # Publisher needs to have a different name vs the application monitor
6
+ # https://github.com/dry-rb/dry-events/issues/6
7
+ include Dry::Events::Publisher[:building]
6
8
  include Deps[:files, :router]
7
9
 
8
10
  register_event("started")
@@ -28,7 +30,7 @@ module Staticky
28
30
  .resources
29
31
  .each do |resource|
30
32
  publish("before_resource", resource:)
31
- compile resource.filepath, resource.build
33
+ compile resource.build_path, resource.build
32
34
  publish("after_resource", resource:)
33
35
  end
34
36
  end
@@ -25,7 +25,7 @@ module Staticky
25
25
  option :twitter,
26
26
  default: "",
27
27
  desc: "Twitter handle",
28
- aliases: ["-t"]
28
+ aliases: ["-x"]
29
29
 
30
30
  def call(path:, **)
31
31
  path = Pathname.new(path).expand_path
@@ -7,6 +7,12 @@ module Staticky
7
7
  @_view_context
8
8
  end
9
9
 
10
+ def staticky_live_reload_js(base_path = "/")
11
+ script(type: :module) do
12
+ unsafe_raw Staticky::Utils.live_reload_js(base_path)
13
+ end
14
+ end
15
+
10
16
  def link_to(text = nil, href, **, &block) # rubocop:disable Style/OptionalArguments
11
17
  block ||= proc { text }
12
18
  href = Staticky.router.resolve(href)
@@ -15,16 +15,18 @@ module Staticky
15
15
  end
16
16
 
17
17
  module InstanceMethods
18
- def filepath
19
- destination.join(basename)
18
+ def build_path
19
+ destination.join(filepath)
20
20
  end
21
21
 
22
- def read
23
- filepath.read
22
+ def filepath
23
+ return Pathname.new("index.html") if root?
24
+
25
+ Pathname.new("#{uri.path.gsub(%r{^/}, "")}.html")
24
26
  end
25
27
 
26
- def basename
27
- root? ? "index.html" : "#{url}.html"
28
+ def read
29
+ Staticky.files.read(build_path)
28
30
  end
29
31
 
30
32
  def root?
@@ -3,6 +3,7 @@
3
3
  require "roda"
4
4
 
5
5
  require_relative "../staticky"
6
+ require_relative "server_plugin"
6
7
 
7
8
  module Staticky
8
9
  class Server < Roda
@@ -12,25 +13,13 @@ module Staticky
12
13
 
13
14
  NotFound = Class.new(Staticky::Error)
14
15
 
15
- plugin :common_logger, Staticky.server_logger, method: :debug
16
- plugin :render, engine: "html"
17
- plugin :public
18
-
19
- plugin :not_found do
20
- raise NotFound if Staticky.env.test?
21
-
22
- Staticky.build_path.join("404.html").read
23
- end
24
-
25
- plugin :error_handler do |e|
26
- raise e if Staticky.env.test?
27
-
28
- Staticky.build_path.join("500.html").read
29
- end
16
+ plugin :staticky_server
30
17
 
31
18
  route do |r|
19
+ r.staticky
20
+
32
21
  Staticky.resources.each do |resource|
33
- case resource.filepath.basename.to_s
22
+ case resource.filepath.to_s
34
23
  when "index.html"
35
24
  r.root do
36
25
  render(inline: resource.read)
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staticky
4
+ module ServerPlugin
5
+ def self.load_dependencies(app)
6
+ app.plugin :common_logger, Staticky.server_logger, method: :debug
7
+ app.plugin :render, engine: "html"
8
+ app.plugin :public
9
+ app.plugin :exception_page
10
+
11
+ app.plugin :not_found do
12
+ raise Staticky::Server::NotFound if Staticky.env.test?
13
+
14
+ Staticky.build_path.join("404.html").read
15
+ end
16
+
17
+ app.plugin :error_handler do |error|
18
+ raise error if Staticky.env.test?
19
+ next exception_page(error) if Staticky.env.development?
20
+
21
+ Staticky.build_path.join("500.html").read
22
+ end
23
+ end
24
+
25
+ module RequestMethods
26
+ def staticky
27
+ unless Staticky.env.development? && Staticky.config.live_reloading
28
+ return
29
+ end
30
+
31
+ ServerPlugins::LiveReloading.setup_live_reload(scope)
32
+ end
33
+ end
34
+
35
+ Roda::RodaPlugins.register_plugin(:staticky_server, self)
36
+ end
37
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staticky
4
+ module ServerPlugins
5
+ module LiveReloading
6
+ def self.setup_live_reload(app) # rubocop:disable Metrics
7
+ sleep_interval = 0.5
8
+ file_to_check = Staticky.build_path.join("index.html")
9
+ errors_file = Staticky.build_path.join("errors.json")
10
+
11
+ app.request.get "_staticky/live_reload" do # rubocop:disable Metrics/BlockLength
12
+ @_mod = if Staticky.files.exist?(file_to_check)
13
+ file_to_check.mtime.to_i
14
+ else
15
+ 0
16
+ end
17
+
18
+ event_stream = proc do |stream|
19
+ Thread.new do
20
+ loop do
21
+ new_mod = if Staticky.files.exist?(file_to_check)
22
+ file_to_check.mtime.to_i
23
+ else
24
+ 0
25
+ end
26
+
27
+ if @_mod < new_mod
28
+ stream.write "data: reloaded!\n\n"
29
+ break
30
+ elsif Staticky.files.exist?(errors_file)
31
+ stream.write "event: builderror\n" \
32
+ "data: #{errors_file.read.to_json}\n\n"
33
+ else
34
+ stream.write "data: #{new_mod}\n\n"
35
+ end
36
+
37
+ sleep sleep_interval
38
+ rescue Errno::EPIPE # User refreshed the page
39
+ break
40
+ end
41
+ ensure
42
+ stream.close
43
+ end
44
+ end
45
+
46
+ app.request.halt [
47
+ 200,
48
+ {
49
+ "Content-Type" => "text/event-stream",
50
+ "cache-control" => "no-cache"
51
+ },
52
+ event_stream
53
+ ]
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Staticky
4
+ module Utils
5
+ module_function
6
+
7
+ def live_reload_js(base_path) # rubocop:disable Metrics/MethodLength
8
+ return "" unless Staticky.env.development?
9
+
10
+ path = File.join(base_path, "/_staticky/live_reload")
11
+
12
+ <<~JAVASCRIPT
13
+ let lastmod = 0
14
+ let reconnectAttempts = 0
15
+
16
+ function statickyReload() {
17
+ if (window.Turbo) {
18
+ Turbo.visit(window.location)
19
+ } else {
20
+ location.reload()
21
+ }
22
+ }
23
+
24
+ function startLiveReload() {
25
+ const connection = new EventSource("#{path}")
26
+
27
+ connection.addEventListener("message", event => {
28
+ reconnectAttempts = 0
29
+
30
+ if (event.data == "reloaded!") {
31
+ statickyReload()
32
+ } else {
33
+ const newmod = Number(event.data)
34
+
35
+ if (lastmod < newmod) {
36
+ statickyReload()
37
+ lastmod = newmod
38
+ }
39
+ }
40
+ })
41
+
42
+ connection.addEventListener("error", () => {
43
+ if (connection.readyState === 2) {
44
+ // reconnect with new object
45
+ connection.close()
46
+ reconnectAttempts++
47
+ if (reconnectAttempts < 25) {
48
+ console.warn("Live reload: attempting to reconnect in 3 seconds...")
49
+ setTimeout(() => startLiveReload(), 3000)
50
+ } else {
51
+ console.error(
52
+ "Too many live reload connections failed. Refresh the page to try again."
53
+ )
54
+ }
55
+ }
56
+ })
57
+ }
58
+
59
+ startLiveReload()
60
+ JAVASCRIPT
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Staticky
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/staticky.rb CHANGED
@@ -35,9 +35,10 @@ module Staticky
35
35
  extend Dry::Configurable
36
36
 
37
37
  setting :env, default: ENV.fetch("RACK_ENV", "development").to_sym
38
- setting :build_path, default: Pathname.new("build")
38
+ setting :build_path, default: Pathname(__dir__).join("..", "build")
39
39
  setting :root_path, default: Pathname(__dir__)
40
40
  setting :logger, default: Dry.Logger(:staticky, template: :details)
41
+ setting :live_reloading, default: true
41
42
  setting :server_logger, default: Dry.Logger(
42
43
  :staticky_server,
43
44
  template: :details,
@@ -53,6 +54,7 @@ module Staticky
53
54
  def router = application[:router]
54
55
  def builder = application[:builder]
55
56
  def generator = application[:generator]
57
+ def files = application[:files]
56
58
  def application = Application
57
59
 
58
60
  def env
@@ -3,8 +3,7 @@
3
3
  source "https://rubygems.org"
4
4
  git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5
5
 
6
- ruby_version = Pathname.new(__dir__).join(".ruby-version")
7
- ruby ruby_version.read.strip
6
+ ruby file: ".ruby-version"
8
7
 
9
8
  gem "protos"
10
9
  gem "protos-icon"
@@ -24,7 +24,9 @@ namespace :site do
24
24
  task watch: :environment do
25
25
  require "filewatcher"
26
26
 
27
- Rake::Task["site:build"].execute unless Staticky.build_path.exist?
27
+ unless Staticky.build_path.join("500.html").exist?
28
+ Rake::Task["site:build"].execute
29
+ end
28
30
 
29
31
  Staticky.logger.info "Watching site in #{Staticky.env.name} environment..."
30
32
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "staticky/phlex/view_helpers"
4
4
 
5
- class Component < Protos::Component
5
+ class ApplicationComponent < Protos::Component
6
6
  include ViteHelpers
7
7
 
8
8
  class NullViewContext
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationLayout < ApplicationComponent
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationPage < ApplicationComponent
4
+ def around_template(&block) = render Layouts::Page.new(&block)
5
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Errors
4
- class NotFound < Page
4
+ class NotFound < ApplicationPage
5
5
  include Protos::Typography
6
6
 
7
7
  def self.layout = Layouts::Error
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Errors
4
- class ServiceError < Page
4
+ class ServiceError < ApplicationPage
5
5
  include Protos::Typography
6
6
 
7
7
  def self.layout = Layouts::Error
@@ -1,12 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Layouts
4
- class Error < Layout
5
- def view_template(&block)
6
- render Layouts::Site.new do
7
- div(class: "grid place-items-center h-[--main-scene]") do
8
- section(class: "flex flex-col place-items-center gap-sm", &block)
9
- end
4
+ class Error < Site
5
+ def view_template(&)
6
+ div(class: "grid place-items-center h-[--main-scene]") do
7
+ section(class: "flex flex-col place-items-center gap-sm", &)
10
8
  end
11
9
  end
12
10
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Layouts
4
- class Head < Component
4
+ class Head < ApplicationComponent
5
5
  option :title, default: -> { ::Site.title }, reader: false
6
6
  option :description, default: -> { ::Site.description }, reader: false
7
7
 
@@ -44,10 +44,14 @@ module Layouts
44
44
  meta name: "twitter:site", content: ::Site.twitter
45
45
  meta name: "twitter:creator", content: ::Site.twitter
46
46
 
47
- vite_client_tag unless ENV["RACK_ENV"] == "production"
47
+ stylesheet_tag "stylesheets/syntax", media: "screen"
48
+ stylesheet_tag "stylesheets/application", media: "screen"
48
49
  javascript_tag "application"
49
50
 
50
- yield if block_given?
51
+ if Staticky.env.development?
52
+ vite_client_tag
53
+ staticky_live_reload_js if Staticky.config.live_reloading
54
+ end
51
55
  end
52
56
  end
53
57
 
@@ -56,5 +60,9 @@ module Layouts
56
60
  def javascript_tag(...)
57
61
  vite_javascript_tag(...)
58
62
  end
63
+
64
+ def stylesheet_tag(...)
65
+ vite_stylesheet_tag(...)
66
+ end
59
67
  end
60
68
  end
@@ -1,38 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Layouts
4
- class Site < Layout
5
- include Phlex::DeferredRender
6
-
7
- def view_template
4
+ class Site < ApplicationLayout
5
+ def around_template(&)
8
6
  doctype
9
7
  html lang: "en", data: { theme: "onedark" } do
10
- render Layouts::Head.new(&head)
11
-
12
- body do
13
- render UI::Navbar.new(class: css[:navbar])
14
- main(class: css[:main], &content)
15
- render UI::Footer.new(class: css[:footer])
16
- end
8
+ head
9
+ body(&)
17
10
  end
18
11
  end
19
12
 
20
- def with_head(&block)
21
- @head = block
22
- end
23
-
24
- def with_content(&block)
25
- @content = block
13
+ def view_template(&block)
14
+ render UI::Navbar.new(class: css[:navbar])
15
+ main(**attrs, &block)
16
+ render UI::Footer.new(class: css[:footer])
26
17
  end
27
18
 
28
19
  private
29
20
 
30
- def content
31
- @content || proc { }
32
- end
33
-
34
- def head
35
- @head || proc { }
21
+ def head(&block)
22
+ render Head.new(&block)
36
23
  end
37
24
  end
38
25
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pages
4
- class Home < Page
4
+ class Home < ApplicationPage
5
5
  include Protos::Typography
6
6
 
7
7
  def view_template
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UI
4
- class Footer < Component
4
+ class Footer < ApplicationComponent
5
5
  def view_template
6
6
  footer(**attrs) do
7
7
  p(class: "opacity-80 text-sm") do
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module UI
4
- class Navbar < Component
4
+ class Navbar < ApplicationComponent
5
5
  def view_template
6
6
  header(**attrs) do
7
7
  a(href: "/", class: "btn btn-ghost") { Site.title }
@@ -5,13 +5,12 @@ ENV["RACK_ENV"] ||= "development"
5
5
  require "bundler"
6
6
  Bundler.require(:default, ENV.fetch("RACK_ENV", nil))
7
7
 
8
- require_relative "staticky"
9
-
10
8
  loader = Zeitwerk::Loader.new
11
9
  loader.inflector.inflect("ui" => "UI")
12
10
  loader.push_dir("lib")
13
11
  loader.push_dir("app/views")
14
12
  loader.setup
15
13
 
14
+ require_relative "staticky"
16
15
  require_relative "site"
17
16
  require_relative "routes"
@@ -3,4 +3,5 @@
3
3
  Staticky.configure do |config|
4
4
  config.build_path = Pathname.new("build")
5
5
  config.root_path = Pathname(__dir__).join("..")
6
+ config.live_reloading = true
6
7
  end
@@ -2,7 +2,12 @@
2
2
  "all": {
3
3
  "publicDir": "build",
4
4
  "sourceCodeDir": "frontend",
5
- "watchAdditionalPaths": []
5
+ "additionalEntrypoints": [
6
+ "~/{images,stylesheets}/**/*"
7
+ ],
8
+ "watchAdditionalPaths": [
9
+ "app/**/*.rb"
10
+ ]
6
11
  },
7
12
  "development": {
8
13
  "autoBuild": true,
@@ -1,8 +1,5 @@
1
1
  import "@fontsource-variable/inter"
2
+ import * as Turbo from "@hotwired/turbo"
2
3
  import "protos-stimulus"
3
4
 
4
- import "~/stylesheets/application.css"
5
- import "~/stylesheets/syntax.css"
6
-
7
5
  import "~/controllers"
8
- import "~/turbo_transitions.js"
@@ -1,7 +1,7 @@
1
1
  // This is a custom plugin for Tailwind CSS to enable font-variation-settings
2
2
  import plugin from "tailwindcss/plugin";
3
3
 
4
- const fontVariationSettings = plugin(function ({ addUtilities }) {
4
+ const fontVariationSettings = plugin(function({ addUtilities }) {
5
5
  addUtilities({
6
6
  ".font-thin": {
7
7
  fontWeight: 100,
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class Icon < Component
3
+ class Icon < ApplicationComponent
4
4
  param :name, reader: false
5
5
  option :variant, reader: false, default: -> { }
6
6
  option :size, default: -> { :md }, reader: false
@@ -6,6 +6,14 @@ module ViteHelpers
6
6
  script(src:, type: :module)
7
7
  end
8
8
 
9
+ def vite_stylesheet_tag(*names, **options)
10
+ style_paths = names.map { |name| vite_asset_path(name, type: :stylesheet) }
11
+
12
+ style_paths.each do |href|
13
+ link(href:, rel: :stylesheet, **options)
14
+ end
15
+ end
16
+
9
17
  def vite_javascript_tag(
10
18
  *names,
11
19
  type: :module,
@@ -28,9 +28,16 @@ http {
28
28
  port_in_redirect off;
29
29
  add_header X-Content-Type-Options "nosniff";
30
30
 
31
+ location = / {
32
+ try_files /index.html =404;
33
+ }
34
+
31
35
  location / {
32
- try_files $uri $uri/ =404;
36
+ rewrite ^/(.*)/$ /$1 permanent;
37
+
38
+ try_files $uri $uri.html =404;
33
39
  expires 1h;
40
+ error_page 404 /404.html;
34
41
  }
35
42
 
36
43
  location ~* \.(?:ico|gif|jpe?g|png|svg|webp)$ {
@@ -11,21 +11,16 @@
11
11
  "author": "",
12
12
  "license": "ISC",
13
13
  "dependencies": {
14
- "@fontsource-variable/inter": "^5.0.20",
14
+ "@fontsource-variable/inter": "^5.1.0",
15
15
  "@hotwired/stimulus": "^3.2.2",
16
- "@hotwired/turbo": "^8.0.5",
17
- "@tailwindcss/typography": "^0.5.14",
16
+ "@hotwired/turbo": "^8.0.12",
17
+ "@tailwindcss/typography": "^0.5.15",
18
18
  "autoprefixer": "^10.4.20",
19
- "daisyui": "^4.12.10",
20
- "protos-stimulus": "^0.0.3",
21
- "postcss": "^8.4.41",
22
- "tailwindcss": "^3.4.10",
23
- "vite": "^5.4.0",
24
- "vite-plugin-ruby": "^5.0.0",
25
- "vite-plugin-full-reload": "^1.2.0"
26
- },
27
- "optionalDependencies": {
28
- "@rollup/rollup-linux-arm64-musl": "4.20.0"
29
- },
30
- "packageManager": "yarn@4.5.0"
19
+ "daisyui": "^4.12.20",
20
+ "postcss": "^8.4.49",
21
+ "protos-stimulus": "^0.0.4",
22
+ "tailwindcss": "^3.4.16",
23
+ "vite": "^6.0.3",
24
+ "vite-plugin-ruby": "^5.1.1"
25
+ }
31
26
  }
@@ -1,12 +1,15 @@
1
1
  import defaultTheme from "tailwindcss/defaultTheme"
2
+ import daisyui from "daisyui"
3
+ import typography from "@tailwindcss/typography"
4
+ import variableFonts from "./frontend/tailwindcss/variable_font_plugin"
2
5
 
3
6
  // For importing tailwind styles from protos gem
4
- const execSync = require('child_process').execSync;
5
- const output = execSync('bundle show protos', { encoding: 'utf-8' });
6
- const protos_path = output.trim() + '/**/*.rb';
7
+ import { execSync } from "child_process"
8
+ const output = execSync("bundle show protos", { encoding: "utf-8" });
9
+ const protos_path = output.trim() + "/**/*.rb";
7
10
 
8
- /** @type {import('tailwindcss').Config} */
9
- module.exports = {
11
+ /** @type {import("tailwindcss").Config} */
12
+ const config = {
10
13
  content: [
11
14
  "./app/**/*.rb",
12
15
  "./lib/**/*.rb",
@@ -75,9 +78,10 @@ module.exports = {
75
78
  ]
76
79
  },
77
80
  plugins: [
78
- require("daisyui"),
79
- require('@tailwindcss/typography'),
80
- require("./frontend/tailwindcss/variable_font_plugin"),
81
+ daisyui,
82
+ typography,
83
+ variableFonts
81
84
  ],
82
85
  }
83
86
 
87
+ export default config
@@ -1,14 +1,8 @@
1
1
  import { defineConfig } from "vite"
2
2
  import RubyPlugin from "vite-plugin-ruby"
3
- import FullReload from "vite-plugin-full-reload"
4
3
 
5
4
  export default defineConfig({
6
- server: { hmr: false },
7
5
  plugins: [
8
6
  RubyPlugin(),
9
- FullReload(
10
- "build/index.html",
11
- { delay: 500 }
12
- ),
13
7
  ],
14
8
  })
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: staticky
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nolan J Tait
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-24 00:00:00.000000000 Z
11
+ date: 2024-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-cli
@@ -216,6 +216,9 @@ files:
216
216
  - lib/staticky/routing/plugins.rb
217
217
  - lib/staticky/routing/plugins/prelude.rb
218
218
  - lib/staticky/server.rb
219
+ - lib/staticky/server_plugin.rb
220
+ - lib/staticky/server_plugins/live_reloading.rb
221
+ - lib/staticky/utils.rb
219
222
  - lib/staticky/version.rb
220
223
  - site_template/.dockerignore
221
224
  - site_template/.gitignore
@@ -228,6 +231,9 @@ files:
228
231
  - site_template/Procfile.dev
229
232
  - site_template/README.md
230
233
  - site_template/Rakefile
234
+ - site_template/app/views/application_component.rb
235
+ - site_template/app/views/application_layout.rb
236
+ - site_template/app/views/application_page.rb
231
237
  - site_template/app/views/errors/not_found.rb
232
238
  - site_template/app/views/errors/service_error.rb
233
239
  - site_template/app/views/layouts/error.rb
@@ -258,11 +264,7 @@ files:
258
264
  - site_template/frontend/stylesheets/application.css
259
265
  - site_template/frontend/stylesheets/syntax.css
260
266
  - site_template/frontend/tailwindcss/variable_font_plugin.js
261
- - site_template/frontend/turbo_transitions.js
262
- - site_template/lib/component.rb
263
267
  - site_template/lib/icon.rb
264
- - site_template/lib/layout.rb
265
- - site_template/lib/page.rb
266
268
  - site_template/lib/vite_helpers.rb
267
269
  - site_template/logs/.keep
268
270
  - site_template/nginx.conf
@@ -304,7 +306,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
304
306
  - !ruby/object:Gem::Version
305
307
  version: '0'
306
308
  requirements: []
307
- rubygems_version: 3.5.16
309
+ rubygems_version: 3.5.22
308
310
  signing_key:
309
311
  specification_version: 4
310
312
  summary: Static site
@@ -1,54 +0,0 @@
1
- document.addEventListener("turbo:visit", () => {
2
- let main = document.querySelector("main");
3
- if (main.dataset.turboTransition == "false") return;
4
-
5
- let [movement, scale] = ["15px", "0.99"];
6
-
7
- if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
8
- [movement, scale] = ["7px", "1"];
9
- }
10
-
11
- main.style.transformOrigin = "50% 0%";
12
- main.dataset.animatingOut = true;
13
-
14
- main.animate(
15
- [
16
- { opacity: 1, transform: "translateY(0px) scale(1)" },
17
- { opacity: 0, transform: `translateY(${movement}) scale(${scale})` },
18
- ],
19
- {
20
- duration: 300,
21
- easing: "cubic-bezier(0.45, 0, 0.55, 1)",
22
- fill: "forwards",
23
- },
24
- );
25
-
26
- Promise.all(main.getAnimations().map((animation) => animation.finished)).then(
27
- () => {
28
- if (main.dataset.animatingOut) main.style.visibility = "hidden";
29
- },
30
- );
31
- });
32
-
33
- document.addEventListener("turbo:load", () => {
34
- let main = document.querySelector("main");
35
- if (main.dataset.turboTransition == "false") return;
36
-
37
- let [movement, scale] = ["-10px", "0.99"];
38
-
39
- if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
40
- [movement, scale] = ["-5px", "1"];
41
- }
42
-
43
- main.style.visibility = "visible";
44
- main.style.transformOrigin = "50% 0%";
45
- delete main.dataset.animatingOut;
46
-
47
- main.animate(
48
- [
49
- { opacity: 0, transform: `translateY(${movement}) scale(${scale})` },
50
- { opacity: 1, transform: "translateY(0px) scale(1)" },
51
- ],
52
- { duration: 150, easing: "cubic-bezier(0.45, 0, 0.55, 1)" },
53
- );
54
- });
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Layout < Component
4
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Page < Component
4
- # Example of a nested layout
5
-
6
- def around_template(&block)
7
- render Layouts::Site.new do |layout|
8
- layout.with_content(&block)
9
- end
10
- end
11
- end