svelte-on-rails 0.0.20 → 0.0.22

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 06d39c6909bc770f4cef89091d1ac17566715996aa7425afe29e847072691ee0
4
- data.tar.gz: ca26182656cd9e27bd3a4f9f6d6e58e5e283b625d06a6133b9fd196c369c4306
3
+ metadata.gz: b9171b9d426450c01ff922c667fff82f135416693c935b2cd131d5ee2326e854
4
+ data.tar.gz: 96daa72a3460ace56b5e5e6382f678ba354b1693b95a87113639ed4cb3c2afe3
5
5
  SHA512:
6
- metadata.gz: 3ac18b72caf1f4b64c960423690d4d62dbb9a456edca7337ceb49044b5efa920d5ea728886721dd10818fc2d0be7019af3bc9005ff8e9609017655f21a9e004c
7
- data.tar.gz: 7e355580850438267e99bc8eef9077968860813e55f3de90f2b1d451c59ddb68fd1ca99381dec3e44b84232ac8d488f5628a01ab6248ac158e02a33b3936de1b
6
+ metadata.gz: 52a206e75f0fe55f8121a05ae103701fefdd02e9ab6a4c5874179aa5b0ea2429fdb9c7d88d191c5c444fe0259990571ca1f5315ee017387d02d1e14271741d2c
7
+ data.tar.gz: d9370b582b86edd05470e54d6f6c78a993412f67ec2dd454e11cb282e96ebc97c379ba0e94f644157edb13c946e2d4bf238667895402f92c2644b48822eb43c1
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Seamless and robust Svelte in Rails integration.
4
4
 
5
- By default, and when installed together with hotwired/turbo, it renders
5
+ By default, and when installed together with `@hotwired/turbo-rails`, it renders
6
6
  svelte components on first request server side («SSR») and for subsequent
7
7
  requests it provides a empty tag that will be mounted on the frontend by the associated npm package [@csedl/svelte-on-rails](https://www.npmjs.com/package/@csedl/svelte-on-rails) on the frontend.
8
8
 
@@ -34,17 +34,33 @@ If you have issues, please open one and contributors are welcome!
34
34
  - svelte v5 (see: [how to install svelte on rails/vite](https://dev.to/chmich/setup-inertia-and-svelte-on-rails-7-3glk))
35
35
  - turbo (recommended / [how to install turbo on rails](https://github.com/hotwired/turbo-rails?tab=readme-ov-file#installation))
36
36
 
37
- ## Setup from cero
37
+ ## Installation from cero
38
+
39
+ together with haml, vite, svelte and turbo
38
40
 
39
41
  If you want to start from a new rails app, follow theese tutorials
40
42
 
41
- - [Create a new rails app with vite_rails](https://dev.to/chmich/setup-vite-on-rails-7-f1i)
42
- - create a controller and a view and you should see `Vite ⚡️ Rails` on the browser console.
43
- - make sure vite is on its latest version, sometimes you have to `npm i vite@latest`
44
- - [Setup Svelte](https://dev.to/chmich/setup-inertia-and-svelte-on-rails-7-3glk)
45
- - and restart the app
43
+ ```bash
44
+ rails new my-test-app --skip-javascript
45
+ ```
46
+
47
+ within the app path do
48
+
49
+ ```bash
50
+ bundle add svelte-on-rails
51
+ ```
52
+
53
+ and run the fat installer
46
54
 
47
- ## Installation
55
+ ```bash
56
+ rails svelte_on_rails:install_haml_vite
57
+ ```
58
+
59
+ this installer guides you through and if you answer all with yes,
60
+ at the end, you just have to (re-) start your server
61
+ and you will see a Svelte component!
62
+
63
+ ## Minimal Installation
48
64
 
49
65
  within the app folder
50
66
 
@@ -52,14 +68,11 @@ within the app folder
52
68
  bundle add svelte-on-rails
53
69
  ```
54
70
  ```bash
55
- rails svelte_on_rails:install_for_vite_and_turbo
71
+ rails svelte_on_rails:install
56
72
  ```
57
73
 
58
-
59
74
  This will create a little config file, installs the npm package,
60
75
  creates a initializer file and adds the import statement on `appplication.js` entrypoint file
61
- and it adds a HelloWorld.svelte component.
62
- If not installed, it installs `@hotwired/turbo-rails` too.
63
76
 
64
77
  #### Minimal installer
65
78
 
@@ -69,7 +82,13 @@ There is also a minimal installer:
69
82
  rails svelte_on_rails:install
70
83
  ```
71
84
 
72
- that does nothing else than creating a config file.
85
+ that does:
86
+
87
+ - creating a config file.
88
+ - installs the npm package
89
+
90
+ please follow the instructions on [@csedl/svelte-on-rails](https://www.npmjs.com/package/@csedl/svelte-on-rails)
91
+ for setup the workflow
73
92
 
74
93
  Within the config file, there are mainly two important tags:
75
94
 
@@ -161,6 +180,26 @@ or you want to see the state before hydration, for development purposes, you can
161
180
  the `hydrate: false` option to the view helper,
162
181
  and no hydration will happen for this component.
163
182
 
183
+ ## More rake tasks
184
+
185
+ This tasks are more for testing/playground purposes
186
+
187
+ ```bash
188
+ rails svelte_on_rails:add_hello_world
189
+ ```
190
+
191
+ ```bash
192
+ rails svelte_on_rails:remove_hello_world
193
+ ```
194
+
195
+ ```bash
196
+ rails svelte_on_rails:reset_and_compile_all
197
+ ```
198
+
199
+ This does the same step that ist triggered together with the
200
+ `rails assets:precompile` step together with the deployment pipeline:
201
+ it removes all contents of the svelte-on-rails compiled
202
+ assets and compiles them all new.
164
203
 
165
204
  ## Performance
166
205
 
@@ -2,7 +2,16 @@ require "svelte_on_rails/configuration"
2
2
  require "svelte_on_rails/view_helpers"
3
3
  require "svelte_on_rails/render_server_side"
4
4
  require "svelte_on_rails/railtie" if defined?(Rails)
5
- require 'svelte_on_rails/installer_utils'
5
+
6
+ # installer
7
+ require 'svelte_on_rails/installer/utils'
8
+ require 'svelte_on_rails/installer/haml'
9
+ require 'svelte_on_rails/installer/gem_utils'
10
+ require 'svelte_on_rails/installer/vite'
11
+ require 'svelte_on_rails/installer/svelte'
12
+ require 'svelte_on_rails/installer/npm'
13
+ require 'svelte_on_rails/installer/javascript'
14
+ require 'svelte_on_rails/installer/hello_world'
6
15
 
7
16
  module SvelteOnRails
8
17
  class << self
@@ -0,0 +1,36 @@
1
+ module SvelteOnRails
2
+
3
+ module GemUtils
4
+
5
+ def self.check_gem_version(gem_name)
6
+ gl = File.read(Rails.root.join('Gemfile.lock'))
7
+ regex = /(?<=#{Regexp.escape(gem_name)})[<>=\(~0-9\. \),]+/
8
+ match = gl.match(regex)
9
+
10
+ if match
11
+ match.to_s.gsub('(','').gsub(')','').strip
12
+ end
13
+
14
+ end
15
+
16
+ def self.install_gem(gem_name, group: nil)
17
+ v = check_gem_version(gem_name)
18
+ if v
19
+ puts "Gem #{gem_name} already installed, version: #{v} "
20
+ else
21
+ cmd = [
22
+ "bundle add #{gem_name}",
23
+ (group ? "--group=#{group}" : nil)
24
+ ].compact.join(' ')
25
+ stdout, stderr, status = Open3.capture3(cmd)
26
+ if stderr.present?
27
+ raise stderr
28
+ else
29
+ puts "Installed #{gem_name} version #{check_gem_version(gem_name)}"
30
+ end
31
+ end
32
+ end
33
+
34
+
35
+ end
36
+ end
@@ -0,0 +1,109 @@
1
+ module SvelteOnRails
2
+ module Installer
3
+ module Haml
4
+ def self.install_haml_and_convert
5
+
6
+ # install haml-rails
7
+
8
+ puts '-' * 80
9
+
10
+ gu = SvelteOnRails::GemUtils
11
+ gu.install_gem('haml-rails')
12
+
13
+ # check existing files
14
+
15
+ existing_haml_files = Dir.glob('app/views/**/*.haml').select { |f| File.file? f }
16
+
17
+ overwrite_files = []
18
+ files = Dir.glob('app/views/**/*.html.erb').each_with_object([]) do |f, ary|
19
+ new_file = f.gsub(/\.erb\z/, '.haml')
20
+ if File.file? new_file
21
+ overwrite_files += [new_file]
22
+ existing_haml_files.delete(new_file)
23
+ end
24
+ if File.file? f
25
+ ary << [
26
+ f,
27
+ new_file
28
+ ]
29
+ end
30
+ end
31
+
32
+ if files.empty?
33
+ puts "No .erb files found, no conversion necessary."
34
+ return
35
+ end
36
+
37
+ # ask if haml already exist
38
+
39
+ if existing_haml_files.any?
40
+ puts '-' * 80
41
+ begin
42
+ puts "Theare are already .haml files:"
43
+ puts existing_haml_files
44
+ puts "Would you like to continue? (y/n)"
45
+ continue = STDIN.gets.chomp.downcase[0]
46
+ end until ['y', 'n'].include?(continue)
47
+ if continue == 'n'
48
+ puts 'skipping convert to haml'
49
+ return
50
+ end
51
+ end
52
+
53
+ # ask if overwrite
54
+
55
+ if overwrite_files.any?
56
+ puts '-' * 80
57
+ begin
58
+ puts "The following files already exist and would be overwritten:"
59
+ puts overwrite_files
60
+ puts "Would you like to overwrite these .haml files? (y/n)"
61
+ should_overwrite = STDIN.gets.chomp.downcase[0]
62
+ end until ['y', 'n'].include?(should_overwrite)
63
+ end
64
+
65
+ puts '-' * 80
66
+
67
+ # check for html2haml
68
+
69
+ installed_html2haml = false
70
+ if gu.check_gem_version('html2haml')
71
+ puts "html2haml already installed, now converting..."
72
+ else
73
+ gu.install_gem('html2haml', group: 'development')
74
+ installed_html2haml = true
75
+ end
76
+
77
+ # convert
78
+
79
+ backup_dir = "svelte_on_rails_backup_views_#{Time.now.strftime('%Y%m%d_%H%M%S')}"
80
+ FileUtils.mkdir_p(backup_dir)
81
+
82
+ Dir.chdir(Rails.root) do
83
+ files.each do |f|
84
+ stdout, stderr, status = Open3.capture3("html2haml #{f.join(' ')}")
85
+ if stderr.present?
86
+ raise stderr
87
+ else
88
+ view_dir = File.dirname(f.first).to_s.match(/(?<=app\/views\/)(.*)/).to_s
89
+ backup_view_dir = backup_dir + '/' + view_dir
90
+ FileUtils.mkdir_p(backup_view_dir)
91
+ FileUtils.mv(f.first, backup_view_dir)
92
+ puts "Converted #{f.join(' => ')}"
93
+ end
94
+ end
95
+ end
96
+
97
+ if installed_html2haml
98
+ system("bundle remove html2haml")
99
+ if $?.success?
100
+ puts "cleanup: removed html2haml from Gemfile"
101
+ else
102
+ puts "ERROR: failed to remove html2haml (please remove manually as it was only installed for this task)"
103
+ end
104
+ end
105
+
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,57 @@
1
+ module SvelteOnRails
2
+ module Installer
3
+ module HelloWorld
4
+
5
+ def self.install_hello_world
6
+ utils_i = SvelteOnRails::Installer::Utils
7
+
8
+ # write templates
9
+
10
+ templates = %w[
11
+ app/controllers/svelte_on_rails_hello_world_controller.rb
12
+ app/views/svelte_on_rails_hello_world/index.haml
13
+ app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
14
+ ]
15
+ utils_i.write_templates(templates)
16
+
17
+ # route
18
+
19
+ route = 'svelte_on_rails_hello_world#index'
20
+ rr = utils_i.which_root_route
21
+ root_url = "/"
22
+ url = root_url + route.sub('#', '/')
23
+
24
+ if rr && rr == route
25
+ puts "Root route «#{route}» already exists, skipping."
26
+ root_url
27
+ elsif rr && utils_i.route_exists?(route.sub('#', '/'))
28
+ puts "route «#{route}» already exists, skipping."
29
+ url
30
+ elsif rr
31
+ utils_i.add_route(" get \"#{route.sub('#', '/')}\"")
32
+ url
33
+ else
34
+ utils_i.add_route(' root "svelte_on_rails_hello_world#index"')
35
+ root_url
36
+ end
37
+
38
+ end
39
+
40
+ def self.remove_hello_world
41
+
42
+ utils = SvelteOnRails::Installer::Utils
43
+ if utils.ask_yn('Remove the Hello World component?')
44
+ files = %w[
45
+ app/views/svelte_on_rails_hello_world/index.haml
46
+ app/views/svelte_on_rails_hello_world
47
+ app/controllers/svelte_on_rails_hello_world_controller.rb
48
+ app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
49
+ ]
50
+ utils.remove_files(files)
51
+ utils.remove_line_from_file('config/routes.rb', 'svelte_on_rails_hello_world')
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,55 @@
1
+ module SvelteOnRails
2
+ module Installer
3
+ module Javascript
4
+
5
+ # check if import statement already exists and, if not, append it after the last import statement of that file
6
+ #
7
+
8
+ def self.append_import_statement(file_path, package_name_for_test_existence, import_statement)
9
+
10
+ # Read the file content
11
+ content = File.read(file_path)
12
+
13
+ # Split content into lines
14
+ lines = content.lines
15
+
16
+ already_exist = []
17
+ lines.each do |line|
18
+ if line.match(/^\s*import\s+.*['"]#{package_name_for_test_existence}['";].*$/)
19
+ already_exist.push(line)
20
+ end
21
+ end
22
+
23
+ if already_exist.present?
24
+ puts "skipping: #{import_statement} already exists in #{File.basename(file_path)}, found: «#{already_exist.join(' // ').strip}»."
25
+ else
26
+
27
+ # Find the index of the last import statement
28
+ last_import_index = -1
29
+ lines.each_with_index do |line, index|
30
+ if line.match?(/^\s*import\s+.*$/)
31
+ last_import_index = index
32
+ end
33
+ end
34
+
35
+ # Insert the import statement after the last import
36
+ if last_import_index >= 0
37
+ lines.insert(last_import_index + 1, import_statement)
38
+ else
39
+ # If no import statements, add at the beginning
40
+ lines.unshift(import_statement)
41
+ end
42
+
43
+ # Write the modified content back to the file
44
+ begin
45
+ File.write(file_path, lines.map{|l|l.gsub(/\n/, '')}.join("\n"))
46
+ puts "Successfully inserted «#{import_statement}» into '#{file_path}'"
47
+ rescue => e
48
+ raise "Error writing to #{file_path} => «#{e.message}»"
49
+ end
50
+ end
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,90 @@
1
+ module SvelteOnRails
2
+ module Installer
3
+ module Npm
4
+
5
+ def self.install_or_update_package(package_name, minimal_version: nil, update_to_latest: true)
6
+ pkg = inspect_package(package_name)
7
+ to_do = !check_version((pkg ? pkg[:version] : nil), minimal_version)
8
+ if to_do
9
+
10
+ cmd = if update_to_latest
11
+ "npm install #{package_name}@latest"
12
+ else
13
+ raise "ERROR: not implemented"
14
+ end
15
+
16
+ stdout, stderr, status = Open3.capture3(cmd)
17
+ if stderr.present?
18
+ raise "ERROR #{cmd} => #{stderr}"
19
+ end
20
+
21
+ notice = [
22
+ (pkg ? "Updated" : "Installed"),
23
+ package_name,
24
+ (pkg ? "from version #{pkg[:version].join('.')}" : nil),
25
+ (pkg ? "to" : nil),
26
+ "@latest"
27
+ ].compact.join(' ')
28
+
29
+ puts notice
30
+
31
+ else
32
+ min_str = minimal_version.present? ? ", required: >= #{minimal_version.join('.')}" : ''
33
+ puts "#{package_name} already installed (#{pkg[:version].join('.')}#{min_str}), skipping."
34
+ end
35
+ end
36
+
37
+ def self.inspect_package(package_name)
38
+ pkg = nil
39
+ version = nil
40
+
41
+ if File.exist?('package-lock.json')
42
+ pl = JSON.parse(File.read('package-lock.json'))
43
+ unless pl['packages']
44
+ raise "ERROR: package-lock.json found, but no packages found, seems to be corrupted."
45
+ end
46
+ pkg = pl['packages'].keys.grep(/\/#{package_name}$/).first
47
+ version = pl['packages'][pkg]['version'] if pkg
48
+ end
49
+
50
+ if pkg
51
+ {
52
+ type: pkg.match(/^.*?(?=\/[^\/]*$)/).to_s,
53
+ version: version.split('.').map(&:to_i)
54
+ }
55
+ end
56
+
57
+ end
58
+
59
+ def self.check_version(current_version, minimal_version)
60
+ if !current_version
61
+ return false
62
+ elsif !minimal_version.present?
63
+ return true
64
+ else
65
+ compare_version_arrays(current_version, minimal_version)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def self.compare_version_arrays(current_version, minimal_version)
72
+ raise "ERROR: current_version must be an array" unless current_version.is_a?(Array)
73
+ raise "ERROR: minimal_version must be an array" unless current_version.is_a?(Array)
74
+
75
+ current_version.each_with_index do |v, i|
76
+ raise "ERROR: current_version entries must be an integer" unless v.is_a?(Integer)
77
+
78
+ if minimal_version[i]
79
+ raise "ERROR: minimal_version entries must be an integer" unless minimal_version[i].is_a?(Integer)
80
+ if v < minimal_version[i]
81
+ return false
82
+ end
83
+ end
84
+ end
85
+ true
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,6 @@
1
+ class SvelteOnRailsHelloWorldController < ApplicationController
2
+
3
+ def index
4
+ end
5
+
6
+ end
@@ -0,0 +1,18 @@
1
+
2
+ import { initializeSvelteComponents, cleanupSvelteComponents } from '@csedl/svelte-on-rails';
3
+
4
+ const components = import.meta.glob('/javascript/components/**/*.svelte', { eager: true });
5
+ const componentsRoot = '/javascript/components';
6
+
7
+ // Initialize Svelte components
8
+ initializeSvelteComponents(componentsRoot, components, true);
9
+
10
+ // Turbo event listener for page load
11
+ document.addEventListener('turbo:load', () => {
12
+ initializeSvelteComponents(componentsRoot, components, true);
13
+ });
14
+
15
+ // Turbo event listener for cleanup before page cache
16
+ document.addEventListener('turbo:before-cache', () => {
17
+ cleanupSvelteComponents(false);
18
+ });
@@ -0,0 +1,3 @@
1
+ %h1 Hello
2
+
3
+ = svelte_component "SvelteOnRailsHelloWorld"
@@ -0,0 +1,23 @@
1
+ frontend_folder: "app/frontend"
2
+ # the entrypoint that is your web root, example for vite: where @ points to
3
+ # relative to Rails.root
4
+
5
+ components_folder: "javascript/components"
6
+ # relative to frontend_folder
7
+ # where your svelte components are located
8
+
9
+ ssr: :auto
10
+ # options: true, false, :auto
11
+ # :auto: render server side if request is initial request (works only with turbo, because it checks for the X-Turbo-Request-ID header)
12
+ # when not server-side rendered the components must be built as custom elements, see node-package @csedl/svelte-on-rails
13
+
14
+ development:
15
+ watch_changes: true
16
+ # Check on every request if any file within the svelte components folder have changed, for recompiling
17
+ # Case sensitive path checking, even the file system is case insensitive
18
+ # Make sure this ist set to tue for development and test, but not for production
19
+
20
+ test:
21
+ watch_changes: true
22
+
23
+ production:
@@ -0,0 +1,22 @@
1
+ module SvelteOnRails
2
+ module Installer
3
+ module Svelte
4
+
5
+ def self.install_svelte
6
+ puts '-' * 80
7
+
8
+ # check npm package version
9
+
10
+ npm_i = SvelteOnRails::Installer::Npm
11
+ npm_i.install_or_update_package('svelte', minimal_version: [5])
12
+
13
+ # configure vite
14
+
15
+ vite_i = SvelteOnRails::Installer::Vite
16
+ vite_i.configure_for_svelte
17
+
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,328 @@
1
+ module SvelteOnRails
2
+
3
+ module Installer
4
+
5
+ module Utils
6
+ def self.install_npm_package
7
+ package_name = "@csedl/svelte-on-rails@latest"
8
+ puts "Installing #{package_name} via npm..."
9
+
10
+ if system("npm install #{package_name}")
11
+ puts "#{package_name} successfully installed."
12
+ else
13
+ abort "Failed to install #{package_name}. Please ensure npm is installed and try running 'npm install #{package_name}' manually."
14
+ end
15
+ end
16
+
17
+ def self.install_turbo
18
+
19
+ pkg_js = Rails.root.join("package.json")
20
+ package_name = "@hotwired/turbo-rails"
21
+ file_content = File.exist?(pkg_js) ? File.read(pkg_js) : ""
22
+
23
+ if file_content.match?(/#{package_name}/)
24
+ puts "#{package_name} is already present in package.json, assuming that it is set up well and working."
25
+ else
26
+ puts "Installing #{package_name} via npm..."
27
+ if system("npm install #{package_name}")
28
+ puts "#{package_name} successfully installed."
29
+ add_line_to_file(Rails.root.join("app", "frontend", "entrypoints", "application.js"), "import '#{package_name}';")
30
+ else
31
+ abort "Failed to install #{package_name}. Please ensure npm is installed and try running 'npm install #{package_name}' manually."
32
+ end
33
+ end
34
+
35
+ end
36
+
37
+ def self.create_folder(folder)
38
+ if Dir.exist?(folder)
39
+ puts "Folder already exists: #{folder}"
40
+ else
41
+ FileUtils.mkdir_p(folder)
42
+ puts "Created folder: #{folder}"
43
+ end
44
+ end
45
+
46
+ def self.add_line_to_file(file_path, line)
47
+ file_content = File.exist?(file_path) ? File.read(file_path) : ""
48
+
49
+ if file_content.match?(/#{line}/)
50
+ puts "#{line} already present in #{file_path}, nothing changed here."
51
+ return
52
+ end
53
+
54
+ File.open(file_path, 'a') do |file|
55
+ file.puts(line)
56
+ end
57
+ puts "added #{line} to #{file_path}."
58
+ rescue StandardError => e
59
+ puts "Error: #{e.message}"
60
+ end
61
+
62
+ def self.create_file(file_path)
63
+
64
+ unless File.exist?(file_path)
65
+ FileUtils.touch(file_path)
66
+ puts "Created empty file at file://#{file_path}"
67
+ end
68
+ end
69
+
70
+ def self.create_javascript_initializer
71
+ config_path = Rails.root.join("app", "frontend", "initializers", "svelte.js")
72
+ if File.exist?(config_path)
73
+ puts "Initializer already exists: file://#{config_path}"
74
+ else
75
+ File.write(config_path, <<~JAVASCRIPT)
76
+
77
+ import { initializeSvelteComponents, cleanupSvelteComponents } from '@csedl/svelte-on-rails';
78
+
79
+ const components = import.meta.glob('/javascript/components/**/*.svelte', { eager: true });
80
+ const componentsRoot = '/javascript/components';
81
+
82
+ // Initialize Svelte components
83
+ initializeSvelteComponents(componentsRoot, components, true);
84
+
85
+ // Turbo event listener for page load
86
+ document.addEventListener('turbo:load', () => {
87
+ initializeSvelteComponents(componentsRoot, components, true);
88
+ });
89
+
90
+ // Turbo event listener for cleanup before page cache
91
+ document.addEventListener('turbo:before-cache', () => {
92
+ cleanupSvelteComponents(false);
93
+ });
94
+ JAVASCRIPT
95
+ puts "Created initializer file at file://#{config_path}"
96
+ end
97
+ end
98
+
99
+ def self.create_svelte_hello_world
100
+ file_path = Rails.root.join("app", "frontend", "javascript", "components", "HelloWorld.svelte")
101
+ if File.exist?(file_path)
102
+ puts "Hello World file already exists: file://#{file_path}"
103
+ else
104
+ File.write(file_path, <<~HTML)
105
+ <script>
106
+ export let items
107
+ let count = 0;
108
+
109
+ function increment() {
110
+ count += 1;
111
+ }
112
+ </script>
113
+
114
+ <h1>Greetings from svelte</h1>
115
+
116
+ <button on:click={increment}>Increment: {count}</button>
117
+ <ul>
118
+ {#each items as item}
119
+ <li>{item}</li>
120
+ {/each}
121
+ </ul>
122
+
123
+ <style>
124
+ button {
125
+ background-color: darkred;
126
+ color: white;
127
+ padding: 10px;
128
+ border: none;
129
+ }
130
+ </style>
131
+ HTML
132
+ puts "Hello World file at file://#{file_path}"
133
+ end
134
+ end
135
+
136
+ def self.run_command(command)
137
+
138
+ Dir.chdir(Rails.root) do
139
+ stdout, stderr, status = Open3.capture3(command)
140
+ if stderr.present?
141
+ puts "Error running command «#{command}»:"
142
+ raise stderr
143
+ else
144
+ puts "#{command} => Success"
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ def self.check_file_exists(file_path)
151
+ unless File.exist?(file_path)
152
+ raise "ERROR: File not found: #{file_path}"
153
+ end
154
+ end
155
+
156
+ def self.check_folder_exists(folder_path)
157
+ unless File.exist?(folder_path)
158
+ raise "ERROR: Folder not found: #{folder_path}"
159
+ end
160
+ end
161
+
162
+ def self.check_file_not_exists(file_path)
163
+ if File.exist?(file_path)
164
+ raise "ERROR: File already exists: #{file_path}"
165
+ end
166
+ end
167
+
168
+ def self.check_folder_not_exists(folder_path)
169
+ if File.exist?(folder_path)
170
+ raise "ERROR: Folder already exists: #{folder_path}"
171
+ end
172
+ end
173
+
174
+ def self.write_templates(template_paths)
175
+
176
+ existing = template_paths.select { |p| File.exist?(p) }
177
+ verb = 'Created'
178
+
179
+ if existing.present?
180
+ begin
181
+ puts "#{'Template'.pluralize(existing.length)} already exists:\n#{existing.join("\n")}.\nOverwrite? (y/n)"
182
+ continue = STDIN.gets.chomp.downcase[0]
183
+ end until ['y', 'n'].include?(continue)
184
+ if continue == 'n'
185
+ puts "Skipping write #{'template'.pluralize(template_paths.length)}."
186
+ return
187
+ end
188
+ verb = 'Overwrote'
189
+ end
190
+
191
+ template_paths.each do |p|
192
+ source_path = File.expand_path('rails_template', __dir__) + '/' + p
193
+ FileUtils.mkdir_p(File.dirname(p))
194
+ FileUtils.cp(source_path, p)
195
+ end
196
+
197
+ puts "#{verb} #{'file'.pluralize(template_paths.length)}:\n#{template_paths.join("\n")}"
198
+ end
199
+
200
+ def self.ask_yn(question)
201
+ begin
202
+ puts "#{question} (y/n)"
203
+ continue = STDIN.gets.chomp.downcase[0]
204
+ end until ['y', 'n'].include?(continue)
205
+ continue == 'y'
206
+ end
207
+
208
+ def self.which_root_route
209
+
210
+ # Check if the root route is active (uncommented) or commented out
211
+
212
+ routes = File.read(Rails.root.join('config', 'routes.rb'))
213
+ m = routes.match(/^\s*root\s+['"]([^'"]+)['"]/m)
214
+ if m
215
+ m.to_s.match(/^\s*root\s*['"]([^'"]*)['"]/)[1]
216
+ end
217
+ end
218
+
219
+ def self.add_route(route)
220
+
221
+ file_path = 'config/routes.rb'
222
+
223
+ # Read the file content
224
+ content = File.read(file_path)
225
+
226
+ # Split content into lines
227
+ lines = content.lines
228
+
229
+ # Find the index of Rails.application.routes.draw do
230
+ ind = -1
231
+ lines.each_with_index do |line, i|
232
+ if line.match?(/^\s*Rails.application.routes.draw\s*do[\s\S]+$/)
233
+ ind = i
234
+ end
235
+ end
236
+
237
+ # Insert
238
+
239
+ if ind >= 0
240
+ lines.insert(ind + 1, route)
241
+ else
242
+ raise "ERROR: Could not find Rails.application.routes.draw do"
243
+ end
244
+
245
+ # Write the modified content back to the file
246
+ begin
247
+ File.write(file_path, lines.map { |l| l.gsub(/\n/, '') }.join("\n"))
248
+ puts "Successfully inserted «root '#{route}'» into '#{file_path}'"
249
+ rescue => e
250
+ raise "Error writing to #{file_path} => «#{e.message}»"
251
+ end
252
+
253
+ end
254
+
255
+ def self.route_exists?(target_route)
256
+
257
+ # check if exists
258
+ # Ensure the Rails environment is loaded
259
+ # require File.expand_path("../config/environment", __dir__)
260
+
261
+ # Get all routes from the Rails application
262
+ routes = Rails.application.routes.routes
263
+
264
+ # Check if the route exists
265
+ routes.any? do |route|
266
+ # Extract the path spec and remove any optional parts or constraints
267
+ path = route.path.spec.to_s
268
+ # Clean up the path to match the format (remove leading/trailing slashes, etc.)
269
+ cleaned_path = path.gsub(/\(.*\)/, "").gsub(/^\/|\/$/, "")
270
+ # Check if the cleaned path matches the target route
271
+ cleaned_path == target_route
272
+ end
273
+
274
+ end
275
+
276
+ def self.remove_files(file_paths)
277
+ file_paths.each do |f|
278
+ if File.exist?(f)
279
+ if File.directory?(f)
280
+ Dir.delete(f)
281
+ puts " • Removed directory: #{f}"
282
+ else
283
+ File.delete(f)
284
+ puts " • Removed file: #{f}"
285
+ end
286
+ else
287
+ puts " • File/Path not found so not removed: #{f}"
288
+ end
289
+ end
290
+ end
291
+
292
+ def self.remove_line_from_file(file_path, string_to_find)
293
+
294
+ # Read the file content
295
+ content = File.read(file_path)
296
+
297
+ # Split content into lines
298
+ lines = content.lines
299
+
300
+ found_lines = []
301
+ modified_content = []
302
+ lines.each do |line|
303
+ if line.match(/^[\s\S]+#{string_to_find}[\s\S]+$/)
304
+ found_lines.push(line)
305
+ else
306
+ modified_content.push(line)
307
+ end
308
+ end
309
+
310
+ utils = SvelteOnRails::Installer::Utils
311
+ if found_lines.present? && utils.ask_yn("Remove lines\n • #{found_lines.join("\n • ")}\n from #{file_path}?")
312
+ # Write the modified content back to the file
313
+ begin
314
+ File.write(file_path, modified_content.map{|l|l.gsub(/\n/, '')}.join("\n"))
315
+ puts "Successfully removed #{found_lines.length} #{'line'.pluralize(found_lines.length)}."
316
+ rescue => e
317
+ raise "Error writing to #{file_path} => «#{e.message}»"
318
+ end
319
+ else
320
+
321
+
322
+ end
323
+
324
+ end
325
+
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,93 @@
1
+ module SvelteOnRails
2
+
3
+ module Installer
4
+
5
+ module Vite
6
+
7
+ def self.install_vite
8
+ iu = SvelteOnRails::Installer::Utils
9
+
10
+ puts '-' * 80
11
+
12
+ gu = SvelteOnRails::GemUtils
13
+ if gu.check_gem_version('vite_rails')
14
+ puts "vite_rails already installed, skipping this part."
15
+ else
16
+
17
+ # check non-existence
18
+
19
+ iu.check_file_not_exists('config/vite.json')
20
+ iu.check_file_not_exists('vite.config.ts')
21
+ iu.check_folder_not_exists('app/frontend')
22
+
23
+ # install
24
+
25
+ gu.install_gem('vite_rails')
26
+
27
+ Dir.chdir(Rails.root) do
28
+ puts '++ running «bundle exec vite install» ++'
29
+ `bundle exec vite install`
30
+ puts '++ vite installer finished ++'
31
+ end
32
+
33
+ end
34
+
35
+ # check existence
36
+
37
+ iu.check_file_exists('config/vite.json')
38
+ iu.check_file_exists('vite.config.ts')
39
+ iu.check_file_exists('package.json')
40
+ iu.check_file_exists('package-lock.json')
41
+ iu.check_file_exists('app/frontend/entrypoints/application.js')
42
+ iu.check_folder_exists('app/frontend')
43
+
44
+ # check npm package version
45
+
46
+ ni = SvelteOnRails::Installer::Npm
47
+ ni.install_or_update_package('vite', minimal_version: [6, 1])
48
+
49
+ end
50
+
51
+ def self.configure_for_svelte
52
+
53
+ # add import statement
54
+
55
+ js_i = SvelteOnRails::Installer::Javascript
56
+ pkg = '@sveltejs/vite-plugin-svelte'
57
+ js_i.append_import_statement('vite.config.ts', pkg, "import {svelte} from '#{pkg}'")
58
+ npm_i = SvelteOnRails::Installer::Npm
59
+ npm_i.install_or_update_package(pkg)
60
+
61
+ # add plugin
62
+
63
+ file_content = File.read('vite.config.ts')
64
+
65
+ if file_content.match?(/svelte/)
66
+ puts "Svelte seams already configured in vite.config.ts, nothing changed here."
67
+ else
68
+
69
+ # Regex to match the plugins array and locate RubyPlugin()
70
+ plugins_regex = /(plugins:\s*\[\s*)(\s*)RubyPlugin\(\),\s*([^]]*?\])/m
71
+
72
+ # Check if plugins array with RubyPlugin exists
73
+ unless file_content.match?(plugins_regex)
74
+ puts "Error: No plugins array with RubyPlugin() found in the input."
75
+ return file_content
76
+ end
77
+
78
+ # Insert svelte({}), after RubyPlugin() with proper indentation
79
+ modified_content = file_content.gsub(plugins_regex) do |match|
80
+ prefix = $1 # Start of plugins array (e.g., "plugins: [")
81
+ indent = ' ' # Indentation before RubyPlugin()
82
+ rest = $3 # Remaining plugins and closing bracket
83
+ "#{prefix}#{indent}RubyPlugin(),\n#{indent}svelte({}),\n#{indent}#{rest}"
84
+ end
85
+
86
+ File.write('vite.config.ts', modified_content)
87
+ puts "Updated vite.config.ts with svelte() plugin."
88
+ end
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -3,38 +3,108 @@ Rake::Task["assets:precompile"].enhance do
3
3
  puts "Mount Svelte: Reset dist and compile-all executed"
4
4
  end
5
5
 
6
- require 'svelte_on_rails/installer_utils'
6
+ require 'svelte_on_rails/installer/utils'
7
+ require 'svelte_on_rails/installer/haml'
8
+ utils = SvelteOnRails::Installer::Utils
9
+ npm_i = SvelteOnRails::Installer::Npm
10
+ application_js_path = Rails.root.join("app", "frontend", "entrypoints", "application.js")
7
11
 
8
12
  namespace :svelte_on_rails do
9
- desc "Installs SvelteOnRails YAML configuration into your Rails application"
10
- task :install do
11
13
 
12
- create_configuration_file
14
+ desc "Installs SvelteOnRails for usage together with vite_rails, haml and @hotwired/turbo-rails"
15
+ task :install_haml_vite => :environment do
16
+
17
+ puts '-' * 80
18
+ puts 'STARTING SVELTE-ON-RAILS INSTALLATION'
19
+
20
+
21
+ # haml
22
+
23
+ haml_i = SvelteOnRails::Installer::Haml
24
+ haml_i.install_haml_and_convert
25
+
26
+ # vite
27
+
28
+ vite_i = SvelteOnRails::Installer::Vite
29
+ vite_i.install_vite
30
+
31
+ # svelte_on_rails
32
+
33
+ utils.write_templates(['config/svelte_on_rails.yml'])
34
+ npm_i.install_or_update_package('@csedl/svelte-on-rails')
35
+ # insert_initializer
36
+ uts = SvelteOnRails::Installer::Utils
37
+ uts.write_templates(['app/frontend/initializers/svelte.js'])
38
+ # add import statements
39
+ js_i = SvelteOnRails::Installer::Javascript
40
+ init_stat = '../initializers/svelte.js'
41
+ js_i.append_import_statement(application_js_path, init_stat, "import '#{init_stat}';")
42
+
43
+ # turbo
44
+
45
+ tr_pkg = '@hotwired/turbo-rails'
13
46
 
47
+ npm_i.install_or_update_package(tr_pkg)
48
+
49
+ js_i = SvelteOnRails::Installer::Javascript
50
+ js_i.append_import_statement(application_js_path, tr_pkg, "import '#{tr_pkg}';")
51
+
52
+ # svelte
53
+
54
+ svelte_i = SvelteOnRails::Installer::Svelte
55
+ svelte_i.install_svelte
56
+
57
+ # Hello World
58
+
59
+ puts '-' * 80
60
+ hello_world_path = nil
61
+ if utils.ask_yn('Do you want to install the Hello World component?')
62
+ hw_i = SvelteOnRails::Installer::HelloWorld
63
+ hello_world_path = hw_i.install_hello_world
64
+ end
65
+
66
+ # finish
67
+
68
+ puts '-' * 80
69
+ puts 'FINISHED SVELTE INSTALLATION'
70
+ puts '-' * 80
71
+
72
+ puts "SvelteOnRails installed successfully!"
73
+ puts "Restart the server and check if it all works."
74
+ if hello_world_path
75
+ puts "You can now access the Hello World component on: http://localhost:your-port-number#{hello_world_path}."
76
+ end
77
+ puts "Happy coding!"
78
+ #exit
14
79
  end
15
80
 
16
- desc "Installs SvelteOnRails for usage together with vite_rails and @hotwired/turbo-rails"
17
- task :install_for_vite_and_turbo do
81
+ desc "Installs SvelteOnRails YAML configuration into your Rails application"
82
+ task :install do
83
+
84
+ # svelte_on_rails
18
85
 
19
- iu = SvelteOnRails::InstallerUtils
86
+ utils.write_templates(['config/svelte_on_rails.yml'])
87
+ npm_i.install_or_update_package('@csedl/svelte-on-rails')
20
88
 
21
- iu.create_configuration_file
89
+ end
22
90
 
91
+ desc "Removes the Hello World component"
92
+ task :remove_hello_world do
23
93
 
24
- iu.install_npm_package
25
- iu.install_turbo
94
+ hw_i = SvelteOnRails::Installer::HelloWorld
95
+ hw_i.remove_hello_world
26
96
 
27
- iu.create_folder(Rails.root.join("app", "frontend", "initializers"))
28
- iu.create_folder(Rails.root.join("app", "frontend", "javascript", "components"))
29
- iu.add_line_to_file(Rails.root.join("app", "frontend", "entrypoints", "application.js"), "import '../initializers/svelte.js';")
97
+ end
30
98
 
31
- iu.create_javascript_initializer
32
- iu.create_svelte_hello_world
99
+ desc "Add the Hello World component"
100
+ task :add_hello_world do
33
101
 
34
- puts ''
35
- puts 'SvelteOnRails installed successfully for using together with vite_rails and @hotwired/turbo-rails.'
36
- puts 'Restart the server and check if it all works.'
37
- puts 'Happy coding!'
102
+ puts '-' * 80
103
+ if utils.ask_yn('Do you want to install the Hello World component?')
104
+ hw_i = SvelteOnRails::Installer::HelloWorld
105
+ hello_world_path = hw_i.install_hello_world
106
+ puts "You can now see the Hello World component on: #{hello_world_path}."
107
+ end
38
108
 
39
109
  end
40
110
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svelte-on-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.20
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Sedlmair
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-04-29 00:00:00.000000000 Z
11
+ date: 2025-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -34,7 +34,19 @@ files:
34
34
  - lib/svelte-on-rails.rb
35
35
  - lib/svelte_on_rails/compile.js
36
36
  - lib/svelte_on_rails/configuration.rb
37
- - lib/svelte_on_rails/installer_utils.rb
37
+ - lib/svelte_on_rails/installer/gem_utils.rb
38
+ - lib/svelte_on_rails/installer/haml.rb
39
+ - lib/svelte_on_rails/installer/hello_world.rb
40
+ - lib/svelte_on_rails/installer/javascript.rb
41
+ - lib/svelte_on_rails/installer/npm.rb
42
+ - lib/svelte_on_rails/installer/rails_template/app/controllers/svelte_on_rails_hello_world_controller.rb
43
+ - lib/svelte_on_rails/installer/rails_template/app/frontend/initializers/svelte.js
44
+ - lib/svelte_on_rails/installer/rails_template/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
45
+ - lib/svelte_on_rails/installer/rails_template/app/views/svelte_on_rails_hello_world/index.haml
46
+ - lib/svelte_on_rails/installer/rails_template/config/svelte_on_rails.yml
47
+ - lib/svelte_on_rails/installer/svelte.rb
48
+ - lib/svelte_on_rails/installer/utils.rb
49
+ - lib/svelte_on_rails/installer/vite.rb
38
50
  - lib/svelte_on_rails/node_modules/@ampproject/remapping/LICENSE
39
51
  - lib/svelte_on_rails/node_modules/@ampproject/remapping/README.md
40
52
  - lib/svelte_on_rails/node_modules/@ampproject/remapping/dist/remapping.mjs
@@ -1,170 +0,0 @@
1
- module SvelteOnRails
2
-
3
- module InstallerUtils
4
- def self.install_npm_package
5
- package_name = "@csedl/svelte-on-rails@latest"
6
- puts "Installing #{package_name} via npm..."
7
-
8
- if system("npm install #{package_name}")
9
- puts "#{package_name} successfully installed."
10
- else
11
- abort "Failed to install #{package_name}. Please ensure npm is installed and try running 'npm install #{package_name}' manually."
12
- end
13
- end
14
-
15
- def self.install_turbo
16
-
17
- pkg_js = Rails.root.join("package.json")
18
- package_name = "@hotwired/turbo-rails"
19
- file_content = File.exist?(pkg_js) ? File.read(pkg_js) : ""
20
-
21
- if file_content.match?(/#{package_name}/)
22
- puts "#{package_name} is already present in package.json, assuming that it is set up well and working."
23
- else
24
- puts "Installing #{package_name} via npm..."
25
- if system("npm install #{package_name}")
26
- puts "#{package_name} successfully installed."
27
- add_line_to_file(Rails.root.join("app", "frontend", "entrypoints", "application.js"), "import '#{package_name}';")
28
- else
29
- abort "Failed to install #{package_name}. Please ensure npm is installed and try running 'npm install #{package_name}' manually."
30
- end
31
- end
32
-
33
- end
34
-
35
- def self.create_folder(folder)
36
- if Dir.exist?(folder)
37
- puts "Folder already exists: #{folder}"
38
- else
39
- FileUtils.mkdir_p(folder)
40
- puts "Created folder: #{folder}"
41
- end
42
- end
43
-
44
- def self.create_configuration_file
45
- config_path = Rails.root.join("config", "svelte_on_rails.yml")
46
- if File.exist?(config_path)
47
- puts "Configuration file already exists at file://#{config_path}"
48
- else
49
- File.write(config_path, <<~YAML)
50
-
51
- frontend_folder: "app/frontend"
52
- # the entrypoint that is your web root, example for vite: where @ points to
53
- # relative to Rails.root
54
-
55
- components_folder: "javascript/components"
56
- # relative to frontend_folder
57
- # where your svelte components are located
58
-
59
- ssr: :auto
60
- # options: true, false, :auto
61
- # :auto: render server side if request is initial request (works only with turbo, because it checks for the X-Turbo-Request-ID header)
62
- # when not server-side rendered the components must be built as custom elements, see node-package @csedl/svelte-on-rails
63
-
64
- development:
65
- watch_changes: true
66
- # Check on every request if any file within the svelte components folder have changed, for recompiling
67
- # Case sensitive path checking, even the file system is case insensitive
68
- # Make sure this ist set to tue for development and test, but not for production
69
-
70
- test:
71
- watch_changes: true
72
-
73
- production:
74
- YAML
75
- puts "Created configuration file at file://#{config_path}"
76
- end
77
- end
78
-
79
- def self.add_line_to_file(file_path, line)
80
- file_content = File.exist?(file_path) ? File.read(file_path) : ""
81
-
82
- if file_content.match?(/#{line}/)
83
- puts "#{line} already present in #{file_path}, nothing changed here."
84
- return
85
- end
86
-
87
- File.open(file_path, 'a') do |file|
88
- file.puts(line)
89
- end
90
- puts "added #{line} to #{file_path}."
91
- rescue StandardError => e
92
- puts "Error: #{e.message}"
93
- end
94
-
95
- def self.create_file(file_path)
96
-
97
- unless File.exist?(file_path)
98
- FileUtils.touch(file_path)
99
- puts "Created empty file at file://#{file_path}"
100
- end
101
- end
102
-
103
-
104
- def self.create_javascript_initializer
105
- config_path = Rails.root.join("app", "frontend", "initializers", "svelte.js")
106
- if File.exist?(config_path)
107
- puts "Initializer already exists: file://#{config_path}"
108
- else
109
- File.write(config_path, <<~JAVASCRIPT)
110
-
111
- import { initializeSvelteComponents, cleanupSvelteComponents } from '@csedl/svelte-on-rails';
112
-
113
- const components = import.meta.glob('/javascript/components/**/*.svelte', { eager: true });
114
- const componentsRoot = '/javascript/components';
115
-
116
- // Initialize Svelte components
117
- initializeSvelteComponents(componentsRoot, components, true);
118
-
119
- // Turbo event listener for page load
120
- document.addEventListener('turbo:load', () => {
121
- initializeSvelteComponents(componentsRoot, components, true);
122
- });
123
-
124
- // Turbo event listener for cleanup before page cache
125
- document.addEventListener('turbo:before-cache', () => {
126
- cleanupSvelteComponents(false);
127
- });
128
- JAVASCRIPT
129
- puts "Created initializer file at file://#{config_path}"
130
- end
131
- end
132
-
133
- def self.create_svelte_hello_world
134
- file_path = Rails.root.join("app", "frontend", "javascript", "components", "HelloWorld.svelte")
135
- if File.exist?(file_path)
136
- puts "Hello World file already exists: file://#{file_path}"
137
- else
138
- File.write(file_path, <<~HTML)
139
- <script>
140
- export let items
141
- let count = 0;
142
-
143
- function increment() {
144
- count += 1;
145
- }
146
- </script>
147
-
148
- <h1>Greetings from svelte</h1>
149
-
150
- <button on:click={increment}>Increment: {count}</button>
151
- <ul>
152
- {#each items as item}
153
- <li>{item}</li>
154
- {/each}
155
- </ul>
156
-
157
- <style>
158
- button {
159
- background-color: darkred;
160
- color: white;
161
- padding: 10px;
162
- border: none;
163
- }
164
- </style>
165
- HTML
166
- puts "Hello World file at file://#{file_path}"
167
- end
168
- end
169
- end
170
- end