svelte-on-rails 3.1.1 → 4.0.0

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: a8c8933b55b56b0a2adcc4c06542b0a9078b8e36408ae6afc9316289f2f2f924
4
- data.tar.gz: 777395f46db7c8e2c88df9090b646088e39cb10cf9c2881ff7cd2f7a78dc8065
3
+ metadata.gz: 6ea6749866c7f8dc1f8a29c24c617d359e9b92c9c2d62557b32d475a4bbccdc8
4
+ data.tar.gz: 81b36d010aab7e8691a78f25a6b98acc69dab5eb1d238ab7c342afab81d170e5
5
5
  SHA512:
6
- metadata.gz: 803bb9b519cc14baa104b41689c56dbe168fae17a1ec540a07dedcb8acc1200418f231446fe6ccd611f84d1c0607ffa335a312bed905512b5a95e12592bb6805
7
- data.tar.gz: 51d06bdd84b8272d5af59bd9bbc4efd407d39a9b57c343a8d96a68e504770a135a951e18defa50b18e95443c319194c33e31287d922d63e7af4520f35345b85a
6
+ metadata.gz: 89a88fb47bb30c0b7119ea47b05734d5bdbed9dbe82413099316a29251ccdbabb55092760cf0473d6629326c89a4b50b585a2c023f62c48b771712a6c43395a4
7
+ data.tar.gz: ac545bd664f6ac91a9245c447954d01821d70af98443c5764efc4d5c84c063b83d592049a857ac85f65bd24c3f8f200964b20a7e784644e1d67346b3a5728968
data/README.md CHANGED
@@ -28,6 +28,7 @@ On every app there are parts where you want it to shine. This is where Svelte co
28
28
  - One Logic in one file!
29
29
  - Render logic and script logic is in one file and, maybe, some css too.
30
30
  - Stimulus is great for rails views with some javascript, but, complex parts: You will love svelte.
31
+ - Improved Backend Response Time: Certain HTML components can be rendered directly by frontend JavaScript.
31
32
  - **Compared to React**
32
33
  - No more shadow dom and all those packages that are supposed to improve performance (e.g. useCallback...)
33
34
  - Slimmer packages
@@ -82,7 +83,7 @@ If you have issues, please open one, and contributors are welcome!
82
83
  - vite@6 (v7 not supported, see issues)
83
84
  - turbolinks and hotwired/turbo
84
85
  - vite_rails (the installer will install it by option --full or --vite)
85
- - svelte v5 (see: [how to install svelte on rails/vite](https://dev.to/chmich/setup-inertia-and-svelte-on-rails-7-3glk))
86
+ - svelte@5, vite-plugin-svelte@5 (see: [how to install svelte on rails/vite](https://dev.to/chmich/setup-inertia-and-svelte-on-rails-7-3glk))
86
87
  - turbo (recommended / [how to install turbo on rails](https://github.com/hotwired/turbo-rails?tab=readme-ov-file#installation))
87
88
  - if you use special packages (like pug) that requires commonjs, you may need
88
89
  - npm on latest versions
@@ -299,6 +300,55 @@ the Svelte component is not visible for the first moment after rendering.
299
300
  Server-side rendering is unnecessary here. You can pass 'ssr: false' to the view helper.
300
301
  This relieves the server and reduces loading time.
301
302
 
303
+ ## Caching
304
+
305
+ Caching only is relevant for `ssr`
306
+
307
+ When using the `cached_svelte_component` helper you must have the `redis` gem installed.
308
+
309
+ This caches on a key like `svelte-on-rails:development:SvelteOnRailsHelloWorld.svelte-1xq5tnu-User1:fscyhz-18bm76a` which includes:
310
+
311
+ - gem name and environment
312
+ - component filename
313
+ - hash of the file-path
314
+ - `cache_key` if given as argument to the view-helper
315
+ - can be a array of active-record objects or strings or a single element instead of a array
316
+ - hash of the last modification timestamp of the component (only when `watch_changes` is set to true)
317
+ - hash of the given properties
318
+
319
+ **Caching strategy (!)**
320
+
321
+ Everytime the properties are changing, which are the last two elements of the hash key, which means all after the last colon,
322
+ Previous hash keys are cleared by, for example: `svelte-on-rails:development:SvelteOnRailsHelloWorld.svelte-1xq5tnu-User1:*`
323
+
324
+ That means: Please be aware to set your `cache_key` precisely for not invalidating cache keys that should not be cleared.
325
+
326
+ On the other hand, please be aware that generating lots of record dependent cache keys will consume your memory, dependent on your
327
+ redis configuration.
328
+
329
+ **Cache configuration**
330
+
331
+ Like usually you can configure your cache store on your environment by something like:
332
+
333
+ ```ruby
334
+ config.cache_store = :redis_cache_store, { url: 'redis://localhost:6379/2',
335
+ expires_in: 90.minutes,
336
+ namespace: 'arcdigital_demo_cache' }
337
+ ```
338
+
339
+ And you can override this by
340
+
341
+ ```yaml
342
+ redis_cache_store:
343
+ expires_in: 2.hours
344
+ ```
345
+
346
+ on the svelte-on-rails config file.
347
+
348
+ **Tip**
349
+
350
+ Pass `debug: true` to the helper and you will see on the logs how your configuration works.
351
+
302
352
  ## More rake tasks
303
353
 
304
354
  This tasks are more for testing/playground purposes
@@ -43,7 +43,7 @@ module SvelteOnRails
43
43
  puts '-' * 80
44
44
  puts '• INSTALLING VITE-RAILS'
45
45
  vite_i = SvelteOnRails::Installer::Vite
46
- vite_i.install_vite(major_version: 6)
46
+ vite_i.install_vite(version_specifier: '6')
47
47
  end
48
48
 
49
49
  def svelte_on_rails
@@ -98,7 +98,10 @@ module SvelteOnRails
98
98
 
99
99
  puts '-' * 80
100
100
  svelte_i = SvelteOnRails::Installer::Svelte
101
- svelte_i.install_svelte
101
+ svelte_i.install_svelte(
102
+ svelte_version_specifier: '5',
103
+ vite_plugin_svelte_version_specifier: '5'
104
+ )
102
105
  end
103
106
 
104
107
  def hello_world
@@ -2,6 +2,7 @@ require "svelte_on_rails/configuration"
2
2
  require "svelte_on_rails/view_helpers"
3
3
  require "svelte_on_rails/renderer/renderer"
4
4
  require "svelte_on_rails/lib/utils"
5
+ require "svelte_on_rails/lib/view_helper_support"
5
6
  require "svelte_on_rails/railtie" if defined?(Rails)
6
7
 
7
8
  # installer
@@ -10,23 +10,49 @@ module SvelteOnRails
10
10
  attr_accessor :configs
11
11
 
12
12
  def initialize
13
- @configs = {
14
- development: {
15
- watch_changes: true,
16
- },
17
- test: {
18
- watch_changes: true,
19
- }
20
- }
21
- load_yaml_config if defined?(Rails)
13
+
14
+ @configs = redis_cache_store_configs
15
+
16
+ return unless defined?(Rails.root)
17
+
18
+ config_path = Rails.root.join("config", "svelte_on_rails.yml")
19
+ return unless File.exist?(config_path)
20
+
21
+ environments = Dir[Rails.root.join('config', 'environments', '*.rb')]
22
+ .map { |file| File.basename(file, '.rb') }
23
+
24
+ configs_base = YAML.load_file(config_path)
25
+
26
+ @configs = @configs.deep_merge(configs_base.reject { |k, _| environments.include?(k) })
27
+
28
+ @configs = @configs.deep_merge(configs_base[Rails.env] || {})
29
+
30
+ @configs = @configs.symbolize_keys
31
+
32
+ if @configs[:redis_cache_store]
33
+ if @configs[:redis_cache_store]['expires_in'].is_a?(String)
34
+ @configs[:redis_cache_store]['expires_in'] = parse_duration(
35
+ @configs[:redis_cache_store]['expires_in']
36
+ )
37
+ end
38
+ end
39
+
40
+ if defined? Redis
41
+ @redis_instance = Redis.new(url: redis_cache_store[:url])
42
+ end
43
+
44
+ end
45
+
46
+ def redis_cache_store
47
+ (@configs[:redis_cache_store] || {}).with_indifferent_access
48
+ end
49
+
50
+ def redis_instance
51
+ @redis_instance
22
52
  end
23
53
 
24
54
  def watch_changes?
25
- if @configs[Rails.env]
26
- @configs[Rails.env]['watch_changes'] == true
27
- else
28
- false
29
- end
55
+ @configs[:watch_changes] == true
30
56
  end
31
57
 
32
58
  def rails_root(root_url = nil)
@@ -34,25 +60,21 @@ module SvelteOnRails
34
60
  end
35
61
 
36
62
  def frontend_folder
37
- Pathname.new(@configs['frontend_folder'].to_s)
63
+ Pathname.new(@configs[:frontend_folder].to_s)
38
64
  end
39
65
 
40
66
  def frontend_folder_full
41
- rails_root.join(@configs['frontend_folder'].to_s)
67
+ rails_root.join(@configs[:frontend_folder].to_s)
42
68
  end
43
69
 
44
70
  def components_folder
45
- Pathname.new(@configs['components_folder'].to_s)
71
+ Pathname.new(@configs[:components_folder].to_s)
46
72
  end
47
73
 
48
74
  def components_folder_full
49
75
  Pathname.new(frontend_folder_full).join(components_folder.to_s)
50
76
  end
51
77
 
52
- def assets_folder
53
- dist_folder.join('assets')
54
- end
55
-
56
78
  def ssr_manifest
57
79
  file = rails_root.join('public', 'vite-ssr', 'manifest.json')
58
80
 
@@ -68,14 +90,8 @@ module SvelteOnRails
68
90
 
69
91
  end
70
92
 
71
- # def manifest=(manifest_hash)
72
- # file = dist_folder.join('manifest.json')
73
- # @manifest = manifest_hash
74
- # File.write(file, JSON.pretty_generate(manifest_hash))
75
- # end
76
-
77
93
  def ssr
78
- rss = @configs['ssr']
94
+ rss = @configs[:ssr]
79
95
  if rss == false || rss == :auto
80
96
  rss
81
97
  else
@@ -84,7 +100,7 @@ module SvelteOnRails
84
100
  end
85
101
 
86
102
  def non_ssr_request_header
87
- rss = @configs['non_ssr_request_header']
103
+ rss = @configs[:non_ssr_request_header]
88
104
  if rss.present?
89
105
  rss
90
106
  else
@@ -92,10 +108,6 @@ module SvelteOnRails
92
108
  end
93
109
  end
94
110
 
95
- # def dist_folder(app_root = nil)
96
- # rails_root(app_root).join('public', 'svelteDist')
97
- # end
98
-
99
111
  def client_dist_folder(app_root = nil)
100
112
  if Rails.env.development?
101
113
  rails_root(app_root).join('public', 'vite')
@@ -157,19 +169,25 @@ module SvelteOnRails
157
169
 
158
170
  private
159
171
 
160
- def load_yaml_config
161
- begin
162
- config_path = Rails.root.join("config", "svelte_on_rails.yml")
163
- return unless File.exist?(config_path)
164
- rescue
165
- return
172
+ def redis_cache_store_configs
173
+ if defined?(Rails.application) && Rails.application.config.cache_store.is_a?(Array) && Rails.application.config.cache_store.first == :redis_cache_store
174
+ { 'redis_cache_store' => Rails.application.config.cache_store.second.stringify_keys }
175
+ else
176
+ {}
166
177
  end
178
+ end
167
179
 
168
- config_data = YAML.load_file(config_path)
180
+ def parse_duration(string)
181
+ # Extract number and unit
182
+ match = string.match(/^(\d+)\.(\w+)$/)
183
+ raise ArgumentError, "Invalid duration format: #{string}" unless match
169
184
 
170
- if config_data
171
- @configs = @configs.merge(config_data)
172
- end
185
+ number, unit = match[1].to_i, match[2]
186
+ # Ensure the unit is a valid ActiveSupport duration method
187
+ valid_units = %w[seconds minutes hours days weeks months years]
188
+ raise ArgumentError, "Invalid unit: #{unit} (valid: #{valid_units})" unless valid_units.include?(unit)
189
+ ActiveSupport::Duration.build(number.send(unit))
173
190
  end
191
+
174
192
  end
175
193
  end
@@ -2,24 +2,21 @@ module SvelteOnRails
2
2
  module Installer
3
3
  module Npm
4
4
 
5
- def self.install_or_update_package(package_name, minimal_version: nil, major_version: nil, update_to_latest: true, dev_dependency: false)
5
+ def self.install_or_update_package(package_name, version_specifier: 'latest', dev_dependency: false)
6
6
  pkg = inspect_package(package_name)
7
- puts "#{package_name} already installed (#{pkg[:version].join('.')})" if pkg
7
+ puts "#{package_name} already installed (#{pkg[:version].join('.')}, specified: #{version_specifier})" if pkg
8
+ major_version = version_specifier.to_s.match(/^\d+(?=\.|$)/)
8
9
 
9
10
  to_do = if !pkg
10
11
  true
11
12
  elsif major_version
12
- r = pkg[:version].first != major_version
13
+ r = pkg[:version].first != major_version.to_s.to_i
13
14
  if r
14
15
  puts "updating to major version #{major_version}"
15
16
  else
16
17
  puts "nothing to do (major version #{major_version} OK)"
17
18
  end
18
19
  r
19
- elsif minimal_version
20
- r = pkg[:version].first < minimal_version.first || pkg[:version].second < minimal_version.second
21
- puts "updating to minimal version #{minimal_version}" if r
22
- r
23
20
  else
24
21
  puts 'nothing to do'
25
22
  false
@@ -28,13 +25,7 @@ module SvelteOnRails
28
25
  save_dev = (dev_dependency ? ' --save-dev' : '')
29
26
  if to_do
30
27
 
31
- cmd = if major_version
32
- "npm install #{package_name}@#{major_version}#{save_dev}"
33
- elsif update_to_latest
34
- "npm install #{package_name}@latest#{save_dev}"
35
- else
36
- raise "ERROR: not implemented"
37
- end
28
+ cmd = "npm install #{package_name}#{'@' + version_specifier if version_specifier}#{save_dev}"
38
29
 
39
30
  stdout, stderr, status = Open3.capture3(cmd)
40
31
 
@@ -55,8 +46,7 @@ module SvelteOnRails
55
46
  puts notice
56
47
 
57
48
  else
58
- min_str = minimal_version.present? ? ", required: >= #{minimal_version.join('.')}" : ''
59
- puts "#{package_name} already installed (#{pkg[:version].join('.')}#{min_str}), skipping."
49
+ puts "#{package_name} already installed (#{pkg[:version].join('.')}, required: «#{version_specifier}»), skipping."
60
50
  end
61
51
  end
62
52
 
@@ -99,34 +89,6 @@ module SvelteOnRails
99
89
 
100
90
  end
101
91
 
102
- # def self.check_version(current_version, minimal_version)
103
- # if !current_version
104
- # return false
105
- # elsif !minimal_version.present?
106
- # return true
107
- # else
108
- # compare_version_arrays(current_version, minimal_version)
109
- # end
110
- # end
111
-
112
- private
113
-
114
- # def self.compare_version_arrays(current_version, minimal_version)
115
- # raise "ERROR: current_version must be an array" unless current_version.is_a?(Array)
116
- #
117
- # current_version.each_with_index do |v, i|
118
- # raise "ERROR: current_version entries must be an integer" unless v.is_a?(Integer)
119
- #
120
- # if minimal_version[i]
121
- # raise "ERROR: minimal_version entries must be an integer" unless minimal_version[i].is_a?(Integer)
122
- # if v < minimal_version[i]
123
- # return false
124
- # end
125
- # end
126
- # end
127
- # true
128
- # end
129
-
130
92
  end
131
93
  end
132
94
  end
@@ -2,18 +2,18 @@ module SvelteOnRails
2
2
  module Installer
3
3
  module Svelte
4
4
 
5
- def self.install_svelte(force: false)
5
+ def self.install_svelte(svelte_version_specifier: 'latest', vite_plugin_svelte_version_specifier: 'latest')
6
6
  puts '-' * 80
7
7
 
8
8
  # check npm package version
9
9
 
10
10
  npm_i = SvelteOnRails::Installer::Npm
11
- npm_i.install_or_update_package('svelte', minimal_version: [5])
11
+ npm_i.install_or_update_package('svelte', version_specifier: svelte_version_specifier)
12
12
 
13
13
  # configure vite
14
14
 
15
15
  vite_i = SvelteOnRails::Installer::Vite
16
- vite_i.configure_for_svelte
16
+ vite_i.configure_for_svelte(vite_plugin_svelte_version_specifier: vite_plugin_svelte_version_specifier)
17
17
 
18
18
  end
19
19
 
@@ -4,7 +4,7 @@ module SvelteOnRails
4
4
 
5
5
  module Vite
6
6
 
7
- def self.install_vite(major_version: nil, minimal_version: nil)
7
+ def self.install_vite(version_specifier: 'latest')
8
8
  iu = SvelteOnRails::Installer::Utils
9
9
 
10
10
  puts '-' * 80
@@ -44,18 +44,18 @@ module SvelteOnRails
44
44
  # check npm package version
45
45
 
46
46
  ni = SvelteOnRails::Installer::Npm
47
- ni.install_or_update_package('vite', major_version: major_version, minimal_version: minimal_version)
47
+ ni.install_or_update_package('vite', version_specifier: version_specifier)
48
48
 
49
49
  end
50
50
 
51
- def self.configure_for_svelte(force: false)
51
+ def self.configure_for_svelte(vite_plugin_svelte_version_specifier: nil)
52
52
 
53
53
  # add import statement
54
54
 
55
55
  js_i = SvelteOnRails::Installer::Javascript
56
56
  pkg = '@sveltejs/vite-plugin-svelte'
57
57
  npm_i = SvelteOnRails::Installer::Npm
58
- npm_i.install_or_update_package(pkg)
58
+ npm_i.install_or_update_package(pkg, version_specifier: vite_plugin_svelte_version_specifier)
59
59
 
60
60
  # add plugin
61
61
 
@@ -0,0 +1,193 @@
1
+ # lib/svelte_on_rails/view_helper_support.rb
2
+ module SvelteOnRails
3
+
4
+ module Lib
5
+ class ViewHelperSupport
6
+ attr_reader :filename, :helper_options, :html_options, :request, :conf
7
+
8
+ def initialize(file, props, request, caching = false)
9
+
10
+ @start_time = Time.now
11
+ @filename = (file.match(/\.svelte$/) ? file[0..-8] : file)
12
+ @conf = SvelteOnRails::Configuration.instance
13
+ @helper_options, @html_options, @props, @props_json = split_props(
14
+ props,
15
+ %i[class id style],
16
+ %i[ssr hydrate debug cache_key expires_in]
17
+ )
18
+ @request = request
19
+ @ssr = determine_ssr
20
+ validate_file if @conf.watch_changes?
21
+
22
+ # precompile
23
+
24
+ if !Dir.exist?(@conf.ssr_dist_folder) || @conf.watch_changes?
25
+ SvelteOnRails::Lib::Utils.watch_changes_and_precompile
26
+ end
27
+
28
+ # caching
29
+
30
+ if caching
31
+ raise '[svelte-on-rails] Caching required but Redis is not defined' unless defined?(Redis)
32
+ else
33
+ raise '[svelte-on-rails] :expires_in is not allowed for this helper' if @helper_options.key?(:expires_in)
34
+ raise '[svelte-on-rails] :cache_key is not allowed for this helper' if @helper_options.key?(:cache_key)
35
+ return
36
+ end
37
+
38
+ return unless ssr?
39
+
40
+ generate_cache_key
41
+
42
+ end
43
+
44
+ def debug?
45
+ @helper_options[:debug]
46
+ end
47
+
48
+ def redis_expiration_seconds
49
+ (conf.redis_cache_store[:expires_in] || 1.hour).to_i
50
+ end
51
+
52
+ def split_props(props, html_options, helper_options)
53
+ prp = {}
54
+ hlp_opts = {}
55
+ ht_opts = {
56
+ data: {
57
+ svelte_component: "/#{conf.components_folder + filename}",
58
+ controller: 'svelte-on-rails',
59
+ }
60
+ }
61
+
62
+ props.each do |k, v|
63
+ _k = k.to_sym
64
+ if helper_options.include?(_k)
65
+ hlp_opts[_k] = v
66
+ elsif html_options.include?(_k)
67
+ ht_opts[_k] = v
68
+ else
69
+ prp[_k] = v
70
+ end
71
+ end
72
+
73
+ ht_opts[:class] = "#{ht_opts[:class]} svelte-component".strip
74
+ prp_js = prp.to_json
75
+ ht_opts[:data][:props] = prp_js
76
+ ht_opts[:data][:svelte_status] = 'do-not-hydrate-me' if hlp_opts[:hydrate] == false
77
+
78
+ [hlp_opts, ht_opts, prp, prp_js]
79
+ end
80
+
81
+ def elapsed_milliseconds
82
+ ((Time.now - @start_time) * 1000).round(1)
83
+ end
84
+
85
+ def cache_key_primary
86
+ @cache_key_primary
87
+ end
88
+
89
+ def cache_key
90
+ @cache_key
91
+ end
92
+
93
+ def ssr?
94
+ @ssr
95
+ end
96
+
97
+ def file_not_found_message
98
+ ff = conf.frontend_folder
99
+ cf = conf.components_folder
100
+ "svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" +
101
+ "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
102
+ "Your configurations are:\n\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
103
+ end
104
+
105
+ def file_case_sensitive_message
106
+ ff = conf.frontend_folder
107
+ cf = conf.components_folder
108
+ "svelte-on-rails gem, view helper #svelte_component\n\n" +
109
+ "File found but Upper and lower case letters are incorrect:\n" +
110
+ "(This check is only on development environments when watch_changes is true)\n\n" +
111
+ "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
112
+ "Your configurations are:\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
113
+ end
114
+
115
+ def log_rendering(message)
116
+
117
+ Rails.logger.info " #{message} (#{elapsed_milliseconds}ms)"
118
+
119
+ end
120
+
121
+ def debug_log(message)
122
+ if debug?
123
+ Rails.logger.debug(" [svelte_component] #{message} (#{elapsed_milliseconds} ms)")
124
+ end
125
+ end
126
+
127
+ def render_ssr
128
+ renderer = SvelteOnRails::Renderer.new(filename)
129
+ renderer.render(@props)
130
+ end
131
+
132
+ private
133
+
134
+ def validate_file
135
+ file_path = conf.components_folder_full + "#{filename}.svelte"
136
+ unless File.exist?(file_path)
137
+ raise file_not_found_message
138
+ end
139
+ if conf.watch_changes? && !SvelteOnRails::Lib::Utils.file_exist_case_sensitive?(conf.components_folder_full, "#{filename}.svelte")
140
+ raise file_case_sensitive_message
141
+ end
142
+ end
143
+
144
+ def determine_ssr
145
+ _ssr = if @helper_options.key?(:ssr)
146
+ if @conf.watch_changes?
147
+ unless [true, false, :auto].include?(@helper_options[:ssr])
148
+ raise "Only true, false, or :auto are allowed for the argument #ssr"
149
+ end
150
+ end
151
+ @helper_options[:ssr]
152
+ else
153
+ @conf.ssr
154
+ end
155
+ _ssr == :auto ? request.headers[conf.non_ssr_request_header].blank? : _ssr
156
+ end
157
+
158
+ private
159
+
160
+ def generate_cache_key
161
+
162
+ mtime_file = @conf.ssr_dist_folder.join('last_mtime')
163
+ mtime = File.read(mtime_file)
164
+
165
+ key2 = if @helper_options[:cache_key]
166
+ k2 = (@helper_options[:cache_key])
167
+ keys = k2.is_a?(Array) ? k2 : [k2]
168
+ keys.map { |k| k.is_a?(ActiveRecord::Base) ? "#{k.class.name}#{k.id}" : k.to_s }.join('-')
169
+ end
170
+
171
+ filename_part = [
172
+ "#{filename.split('/').last}.svelte",
173
+ Zlib.crc32(filename).to_s(36),
174
+ key2
175
+ ].join('-')
176
+
177
+ @cache_key_primary = [
178
+ conf.redis_cache_store[:namespace] ? conf.redis_cache_store[:namespace] : "svelte-on-rails:#{Rails.env}",
179
+ filename_part,
180
+ ].join(':')
181
+
182
+ last_part = [
183
+ (@conf.watch_changes? ? Zlib.crc32(mtime).to_s(36) : nil),
184
+ Zlib.crc32(@props_json).to_s(36)
185
+ ].compact.join('-')
186
+
187
+ @cache_key = [@cache_key_primary, last_part].join(':')
188
+
189
+ end
190
+
191
+ end
192
+ end
193
+ end
@@ -11,10 +11,6 @@ module SvelteOnRails
11
11
  raise "Node.js not found at '#{config.node_bin_path}'. Please configure SvelteOnRails.node_bin (e.g., to ~/.nvm/versions/node/vX.Y.Z/bin/node) or ensure 'node' is in the PATH. If using NVM, run `nvm which default` to find the path."
12
12
  end
13
13
 
14
- if !Dir.exist?(config.ssr_dist_folder) || config.watch_changes?
15
- SvelteOnRails::Lib::Utils.watch_changes_and_precompile
16
- end
17
-
18
14
  utils = SvelteOnRails::Lib::Utils
19
15
  @component_files = utils.component_files(component_name, base_path: base_path)
20
16
 
@@ -1,105 +1,84 @@
1
+ # lib/svelte_on_rails/view_helpers.rb
1
2
  module SvelteOnRails
2
3
  module ViewHelpers
3
4
 
4
5
  def svelte_component(filename, props = {})
5
6
 
6
- # check filename
7
- conf = SvelteOnRails::Configuration.instance
8
- cff = conf.components_folder_full
9
- file_path = cff + "#{filename}.svelte"
10
- if !File.exist?(file_path)
11
- raise file_not_found_messsage(filename)
12
- elsif conf.watch_changes? && !SvelteOnRails::Lib::Utils.file_exist_case_sensitive?(cff, filename + '.svelte')
13
- # on development environments we check case sensitivity too
14
- msg = "File found but Upper and lower case letters are incorrect\n" +
15
- "(This check is only on development environments when watch_changes is true):\n."
16
- raise file_case_sensitive_messsage(filename)
17
- end
7
+ support = SvelteOnRails::Lib::ViewHelperSupport.new(filename, props, request, false)
18
8
 
19
- # check what to do
9
+ support.debug_log("Rendering component: #{filename}")
10
+ log_message = '?'
20
11
 
21
- rss = if props.key?(:ssr)
22
- props.delete(:ssr)
23
- else
24
- conf.ssr
25
- end
26
- unless [true, false, :auto].include?(rss)
27
- raise "Only true, false or auto are allowed for the argument #ssr"
12
+ log_message = "Rendered #{support.filename}.svelte #{'as empty element that will be mounted on the client side' unless support.ssr?}"
13
+ res = render_component(support)
14
+
15
+ support.log_rendering(log_message)
16
+
17
+ res
18
+
19
+ end
20
+
21
+ def cached_svelte_component(filename, props = {})
22
+
23
+ support = SvelteOnRails::Lib::ViewHelperSupport.new(filename, props, request, true)
24
+
25
+ support.debug_log("Rendering component: #{filename}")
26
+ log_message = '?'
27
+ redis = support.conf.redis_instance
28
+
29
+ support.debug_log("Redis configuration: #{support.conf.redis_cache_store}")
30
+
31
+ # TTL
32
+ if support.debug?
33
+ ttl = redis.ttl(support.cache_key)
34
+ support.debug_log("Cache key: #{support.cache_key} (ttl was: #{ttl} seconds, now set to: #{support.redis_expiration_seconds} seconds)")
28
35
  end
36
+ redis.expire(support.cache_key, support.redis_expiration_seconds)
37
+
38
+ cached_content = redis.get(support.cache_key)
29
39
 
30
- ssr = if [:auto, 'auto'].include?(rss)
31
- request.headers[conf.non_ssr_request_header].blank?
40
+ res = if cached_content
41
+ log_message = "Returned #{support.filename}.svelte from cache"
42
+ cached_content.html_safe
32
43
  else
33
- rss
44
+ log_message = "Rendered #{support.filename}.svelte and stored to cache"
45
+ support.debug_log("cache recalculating for key: #{support.cache_key}")
46
+ r = render_component(support)
47
+ support.debug_log("cache recalculated")
48
+
49
+ redis.scan_each(match: "#{support.cache_key_primary}:*") do |key|
50
+ redis.del(key)
51
+ support.debug_log("deleted cache: #{key}")
52
+ end
53
+
54
+ redis.set(support.cache_key, r)
55
+ r
34
56
  end
35
57
 
36
- hydrate = if props.key?(:hydrate)
37
- props[:hydrate]
38
- else
39
- true
40
- end
41
-
42
- # separate hashes
43
-
44
- props.delete(:hydrate)
45
- props.delete(:ssr)
46
- options = props.slice(:class, :id, :style)
47
- props.delete(:class)
48
- props.delete(:id)
49
- props.delete(:style)
58
+ support.log_rendering(log_message)
50
59
 
51
- # set up html
60
+ res
52
61
 
53
- options[:class] = options[:class].to_s + ' svelte-component'
54
- options[:data] ||= {}
55
- options[:data][:svelte_status] = 'do-not-hydrate-me' unless hydrate
56
- options[:data][:props] = props.to_json
57
- options[:data][:svelte_component] = "/#{conf.components_folder + filename}"
58
- options[:data][:controller] = 'svelte-on-rails'
62
+ end
59
63
 
60
- if ssr
64
+ private
61
65
 
62
- # render server side
66
+ def render_component(support)
63
67
 
64
- start_time = Time.now
65
- rend = SvelteOnRails::Renderer.new(filename)
66
- res = rend.render(props)
67
- time = Time.now - start_time
68
- Rails.logger.info " Rendered #{filename}.svelte server-side: #{time.round(3)}ms"
68
+ if support.ssr?
69
69
 
70
- content_tag(:div, options) do
71
- r = content_tag(:style, res['css'], type: 'text/css')
72
- r << res['html'].html_safe
70
+ ssr_result = support.render_ssr
71
+ content_tag(:div, support.html_options) do
72
+ concat(content_tag(:style, ssr_result['css'], type: 'text/css'))
73
+ concat(ssr_result['html'].html_safe)
73
74
  end
74
75
 
75
76
  else
76
77
 
77
- # render empty element
78
- Rails.logger.info " Rendered #{filename}.svelte as empty element that will be mounted on the client side"
79
- content_tag(:div, options) {}
78
+ content_tag(:div, support.html_options) {}
80
79
 
81
80
  end
82
81
  end
83
82
 
84
- def file_not_found_messsage(filename)
85
- conf = SvelteOnRails::Configuration.instance
86
- ff = conf.frontend_folder
87
- cf = conf.components_folder
88
- "svelte-on-rails gem, view helper #svelte_component\n\nFile not found:\n" +
89
- "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
90
- "Your configurations are:\n\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
91
- end
92
-
93
- def file_case_sensitive_messsage(filename)
94
- conf = SvelteOnRails::Configuration.instance
95
- ff = conf.frontend_folder
96
- cf = conf.components_folder
97
- "svelte-on-rails gem, view helper #svelte_component\n\n" +
98
- "File found but Upper and lower case letters are incorrect:\n" +
99
- "(This check is only on development environments when watch_changes is true)\n\n" +
100
- "#{conf.components_folder_full + "#{filename}.svelte"}\n\n" +
101
- "Your configurations are:\nfrontend_folder: «#{ff}»\ncomponents_folder: «#{cf}»\n.. and the filename attribute for the view helper: «#{filename}»\n"
102
- end
103
-
104
83
  end
105
84
  end
@@ -16,6 +16,11 @@ non_ssr_request_header: 'X-Turbo-Request-ID'
16
16
  # when this header contains any value (not blank), we are skipping server-side rendering
17
17
  # when this header is empty we assume initial request and doing server-side rendering
18
18
 
19
+ # redis_cache_store:
20
+ # url: 'redis://localhost:6379/2'
21
+ # expires_in: 90.minutes (=> default / fallback: 1 hour)
22
+ # namespace: 'my-app-svelte-on-rails'
23
+
19
24
  development:
20
25
  watch_changes: true
21
26
  # Check on every request if any file within the svelte components folder have changed, for recompiling
@@ -24,5 +29,6 @@ development:
24
29
 
25
30
  test:
26
31
  watch_changes: true
32
+ cache_expiration_hours: 15
27
33
 
28
34
  production:
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: svelte-on-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Sedlmair
@@ -42,6 +42,7 @@ files:
42
42
  - lib/svelte_on_rails/installer/vite.rb
43
43
  - lib/svelte_on_rails/lib/development_utils.rb
44
44
  - lib/svelte_on_rails/lib/utils.rb
45
+ - lib/svelte_on_rails/lib/view_helper_support.rb
45
46
  - lib/svelte_on_rails/railtie.rb
46
47
  - lib/svelte_on_rails/renderer/render.js
47
48
  - lib/svelte_on_rails/renderer/renderer.rb