svelte-on-rails 0.0.18 → 0.0.21

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.
@@ -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,91 @@
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
+ `bundle exec vite install`
29
+ end
30
+
31
+ end
32
+
33
+ # check existence
34
+
35
+ iu.check_file_exists('config/vite.json')
36
+ iu.check_file_exists('vite.config.ts')
37
+ iu.check_file_exists('package.json')
38
+ iu.check_file_exists('package-lock.json')
39
+ iu.check_file_exists('app/frontend/entrypoints/application.js')
40
+ iu.check_folder_exists('app/frontend')
41
+
42
+ # check npm package version
43
+
44
+ ni = SvelteOnRails::Installer::Npm
45
+ ni.install_or_update_package('vite', minimal_version: [6, 1])
46
+
47
+ end
48
+
49
+ def self.configure_for_svelte
50
+
51
+ # add import statement
52
+
53
+ js_i = SvelteOnRails::Installer::Javascript
54
+ pkg = '@sveltejs/vite-plugin-svelte'
55
+ js_i.append_import_statement('vite.config.ts', pkg, "import {svelte} from '#{pkg}'")
56
+ npm_i = SvelteOnRails::Installer::Npm
57
+ npm_i.install_or_update_package(pkg)
58
+
59
+ # add plugin
60
+
61
+ file_content = File.read('vite.config.ts')
62
+
63
+ if file_content.match?(/svelte/)
64
+ puts "Svelte seams already configured in vite.config.ts, nothing changed here."
65
+ else
66
+
67
+ # Regex to match the plugins array and locate RubyPlugin()
68
+ plugins_regex = /(plugins:\s*\[\s*)(\s*)RubyPlugin\(\),\s*([^]]*?\])/m
69
+
70
+ # Check if plugins array with RubyPlugin exists
71
+ unless file_content.match?(plugins_regex)
72
+ puts "Error: No plugins array with RubyPlugin() found in the input."
73
+ return file_content
74
+ end
75
+
76
+ # Insert svelte({}), after RubyPlugin() with proper indentation
77
+ modified_content = file_content.gsub(plugins_regex) do |match|
78
+ prefix = $1 # Start of plugins array (e.g., "plugins: [")
79
+ indent = ' ' # Indentation before RubyPlugin()
80
+ rest = $3 # Remaining plugins and closing bracket
81
+ "#{prefix}#{indent}RubyPlugin(),\n#{indent}svelte({}),\n#{indent}#{rest}"
82
+ end
83
+
84
+ File.write('vite.config.ts', modified_content)
85
+ puts "Updated vite.config.ts with svelte() plugin."
86
+ end
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -3,11 +3,14 @@ module SvelteOnRails
3
3
 
4
4
  require 'open3'
5
5
 
6
- def initialize(filename)
6
+ def initialize(filename, base_path: SvelteOnRails::Configuration.instance.components_folder_full)
7
7
 
8
- @svelte_file = SvelteOnRails::Configuration.instance.components_folder_full + filename
8
+ fn = (filename.match(/\.svelte$/) ? filename[0..-8] : filename)
9
+ @svelte_file = base_path + filename
9
10
  @svelte_filename = filename
10
- @compiled_file = "#{SvelteOnRails::Configuration.instance.dist_folder}/#{(filename.match(/\.svelte$/) ? filename[0..-8] : filename)}"
11
+ cnf = SvelteOnRails::Configuration.instance
12
+ cf = cnf.dist_folder + cnf.components_folder + fn
13
+ @compiled_file = cf.to_s
11
14
 
12
15
  end
13
16
 
@@ -62,7 +65,7 @@ module SvelteOnRails
62
65
 
63
66
  stdout, stderr, status = Open3.capture3(cmd, chdir: self.class.gem_app_dir)
64
67
  unless status.to_s.match(/^pid [0-9]+ exit 0$/)
65
- raise "Render Svelte Server-side:\n" + stderr
68
+ raise "Render Svelte Server-side:\nrender.js => #{compiled_js_file}\n" + stderr
66
69
  end
67
70
 
68
71
  begin
@@ -88,13 +91,15 @@ module SvelteOnRails
88
91
 
89
92
  start_time = Time.now
90
93
 
91
- dist = SvelteOnRails::Configuration.instance.dist_folder
94
+ cnf = SvelteOnRails::Configuration.instance
92
95
  subs = @svelte_filename.split('/')[0..-2].join('/')
96
+ dist = cnf.dist_folder + cnf.components_folder + subs
97
+
93
98
  cmd = [
94
99
  'node',
95
100
  self.class.gem_app_dir + 'compile.js',
96
101
  @svelte_file,
97
- dist.join(subs),
102
+ dist,
98
103
  self.class.gem_app_dir
99
104
  ].join(' ')
100
105
 
@@ -112,6 +117,17 @@ module SvelteOnRails
112
117
  File.expand_path('../svelte_on_rails', __dir__) + '/'
113
118
  end
114
119
 
120
+ def self.file_exist_case_sensitive?(containing_dir, filename)
121
+ # Combine the directory path and filename
122
+ full_path = File.join(containing_dir, filename)
123
+
124
+ # Check if the file exists and the path matches case-sensitively
125
+ File.exist?(full_path) && Dir[File.join(containing_dir, "**/*")].any? do |f|
126
+ f == full_path
127
+ end
128
+ end
129
+
130
+ # assets:precompile
115
131
  def self.reset_dist
116
132
  unless Dir.exist?(SvelteOnRails::Configuration.instance.dist_folder)
117
133
  FileUtils.mkdir_p(SvelteOnRails::Configuration.instance.dist_folder)
@@ -120,16 +136,21 @@ module SvelteOnRails
120
136
  FileUtils.touch SvelteOnRails::Configuration.instance.dist_folder.join('reset_timestamp')
121
137
  end
122
138
 
123
- def self.file_exist_case_sensitive?(containing_dir, filename)
124
- # Combine the directory path and filename
125
- full_path = File.join(containing_dir, filename)
139
+ def self.reset_and_compile_all
140
+ SvelteOnRails::RenderServerSide.reset_dist
141
+ cnf = SvelteOnRails::Configuration.instance
142
+ frontend_folder = cnf.frontend_folder_full
143
+ sveltes = Dir.glob(cnf.frontend_folder_full.join('**/*.svelte'))
144
+ sveltes.each_with_index do |file, ind|
145
+ comp_name = file.to_s[(cnf.frontend_folder_full.to_s.length + 1)..-1]
146
+
147
+ n = SvelteOnRails::RenderServerSide.new(comp_name, base_path: frontend_folder)
148
+ n.compile
149
+
150
+ puts "compiled #{ind + 1}/#{sveltes.length}: #{comp_name}"
126
151
 
127
- # Check if the file exists and the path matches case-sensitively
128
- File.exist?(full_path) && Dir[File.join(containing_dir, "**/*")].any? do |f|
129
- f == full_path
130
152
  end
131
153
  end
132
154
 
133
-
134
155
  end
135
156
  end
@@ -18,15 +18,15 @@ module SvelteOnRails
18
18
 
19
19
  # check what to do
20
20
 
21
- rss = if props.key?(:render_server_side)
22
- props.delete(:render_server_side)
21
+ rss = if props.key?(:ssr)
22
+ props.delete(:ssr)
23
23
  else
24
- conf.render_server_side
24
+ conf.ssr
25
25
  end
26
26
  unless [true, false, :auto].include?(rss)
27
- raise "Only true, false or auto are allowed for the argument #render_server_side"
27
+ raise "Only true, false or auto are allowed for the argument #ssr"
28
28
  end
29
- render_server_side = (rss == :auto && request.headers['X-Turbo-Request-ID'].blank? || rss)
29
+ ssr = (rss == :auto && request.headers['X-Turbo-Request-ID'].blank? || rss)
30
30
 
31
31
  hydrate = if props.key?(:hydrate)
32
32
  props[:hydrate]
@@ -37,7 +37,7 @@ module SvelteOnRails
37
37
  # separate hashes
38
38
 
39
39
  props.delete(:hydrate)
40
- props.delete(:render_server_side)
40
+ props.delete(:ssr)
41
41
  options = props.slice(:class, :id, :style)
42
42
  props.delete(:class)
43
43
  props.delete(:id)
@@ -45,13 +45,13 @@ module SvelteOnRails
45
45
 
46
46
  # set up html
47
47
 
48
- options[:class] = options[:class].to_s
49
- options[:class] += ' svelte-component svelte-on-rails-not-initialized-component'
48
+ options[:class] = options[:class].to_s + ' svelte-component'
49
+ options[:class] += ' please-hydrate-me-svelte-on-rails' if hydrate
50
50
  options[:data] ||= {}
51
51
  options[:data][:props] = props.to_json
52
52
  options[:data][:svelte_component] = filename
53
53
 
54
- if render_server_side
54
+ if ssr
55
55
 
56
56
  # render server side
57
57