From 6b987041149223ca9ecb8dc2beb772853ec90951 Mon Sep 17 00:00:00 2001 From: Jacob Carlborg Date: Fri, 6 Sep 2024 14:41:28 +0200 Subject: [PATCH] Add Puma plugin This manages the Dartsass watch process from `rails server` --- README.md | 28 ++++++++++++++- lib/dartsass/runner.rb | 4 +++ lib/puma/plugin/dartsass.rb | 69 +++++++++++++++++++++++++++++++++++++ lib/tasks/build.rake | 2 +- 4 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 lib/puma/plugin/dartsass.rb diff --git a/README.md b/README.md index 0ac9db9..0e2a732 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,34 @@ The installer will create your default Sass input file in `app/assets/stylesheet If you need to configure the build process – beyond configuring the build files – you can run `bundle exec dartsass` to access the platform-specific executable, and give it your own build options. -When you're developing your application, you want to run Dart Sass in watch mode, so changes are automatically reflected in the generated CSS output. You can do this either by running `rails dartsass:watch` as a separate process, or by running `./bin/dev` which uses [foreman](https://github.com/ddollar/foreman) to start both the Dart Sass watch process and the rails server in development mode. +## Live rebuild +While you're developing your application, you want to run Dartsass in "watch" +mode, so changes are automatically reflected in the generated CSS output. You +can do this in a few different ways: + +- use this gem's [Puma](https://puma.io/) plugin to integrate "watch" with `rails server`, +- or run `rails dartsass:watch` as a separate process, +- or run `bin/dev` which uses [Foreman](https://github.com/ddollar/foreman) + +### Puma plugin + +This gem ships with a Puma plugin. To use it, add this line to your `puma.rb` configuration: + +```ruby +plugin :dartsass if ENV.fetch("RAILS_ENV", "development") == "development" +``` + +and then running `rails server` will run the Dartsass watch process in the background + +### Run `rails dartsass:watch` + +Running `./bin/rails dartsass:watch` starts the Dartsass process in watch mode. + +### Foreman + +Running `bin/dev` invokes Foreman to start both the Dartsass watch process and +the rails server in development mode based on your `Procfile.dev` file. ## Installation diff --git a/lib/dartsass/runner.rb b/lib/dartsass/runner.rb index de35e81..40c3a54 100644 --- a/lib/dartsass/runner.rb +++ b/lib/dartsass/runner.rb @@ -23,5 +23,9 @@ def dartsass_load_paths def dartsass_compile_command [ RbConfig.ruby, EXEC_PATH ].concat(dartsass_build_options).concat(dartsass_load_paths).concat(dartsass_build_mapping) end + + def dartsass_watch_command + dartsass_compile_command << "--watch" + end end end diff --git a/lib/puma/plugin/dartsass.rb b/lib/puma/plugin/dartsass.rb new file mode 100644 index 0000000..6702357 --- /dev/null +++ b/lib/puma/plugin/dartsass.rb @@ -0,0 +1,69 @@ +require "dartsass/runner" +require "puma/plugin" + +Puma::Plugin.create do + attr_reader :puma_pid, :dartsass_pid, :log_writer + + def start(launcher) + @log_writer = launcher.log_writer + @puma_pid = $$ + @dartsass_pid = fork do + Thread.new { monitor_puma } + # Using IO.popen(command, 'r+') will avoid watch_command read from $stdin. + # If we use system(*command) instead, IRB and Debug can't read from $stdin + # correctly bacause some keystrokes will be taken by watch_command. + IO.popen(Dartsass::Runner.dartsass_watch_command, 'r+') do |io| + IO.copy_stream(io, $stdout) + end + end + + launcher.events.on_stopped { stop_dartsass } + + in_background do + monitor_dartsass + end + end + + private + def stop_dartsass + Process.waitpid(dartsass_pid, Process::WNOHANG) + log "Stopping dartsass..." + Process.kill(:INT, dartsass_pid) if dartsass_pid + Process.wait(dartsass_pid) + rescue Errno::ECHILD, Errno::ESRCH + end + + def monitor_puma + monitor(:puma_dead?, "Detected Puma has gone away, stopping dartsass...") + end + + def monitor_dartsass + monitor(:dartsass_dead?, "Detected dartsass has gone away, stopping Puma...") + end + + def monitor(process_dead, message) + loop do + if send(process_dead) + log message + Process.kill(:INT, $$) + break + end + sleep 2 + end + end + + def dartsass_dead? + Process.waitpid(dartsass_pid, Process::WNOHANG) + false + rescue Errno::ECHILD, Errno::ESRCH + true + end + + def puma_dead? + Process.ppid != puma_pid + end + + def log(...) + log_writer.log(...) + end +end diff --git a/lib/tasks/build.rake b/lib/tasks/build.rake index c5dc609..69dbf64 100644 --- a/lib/tasks/build.rake +++ b/lib/tasks/build.rake @@ -8,7 +8,7 @@ namespace :dartsass do desc "Watch and build your Dart Sass CSS on file changes" task watch: :environment do - system(*Dartsass::Runner.dartsass_compile_command, "--watch", exception: true) + system(*Dartsass::Runner.dartsass_watch_command, exception: true) end end