svelte-on-rails 4.1.0 → 5.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 +4 -4
- data/README.md +132 -45
- data/lib/generators/svelte_on_rails/install/install_generator.rb +25 -0
- data/lib/svelte-on-rails.rb +4 -1
- data/lib/svelte_on_rails/installer/gem_utils.rb +1 -1
- data/lib/svelte_on_rails/installer/hello_world.rb +1 -0
- data/lib/svelte_on_rails/installer/utils.rb +21 -9
- data/lib/svelte_on_rails/lib/view_helper_support.rb +10 -13
- data/lib/svelte_on_rails/renderer/render.js +11 -4
- data/lib/svelte_on_rails/renderer/renderer.rb +12 -3
- data/lib/svelte_on_rails/turbo_stream.rb +43 -0
- data/templates/config_base/config/svelte_on_rails.yml +6 -0
- data/templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb +19 -0
- data/templates/rails_vite_hello_world/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte +1 -1
- data/templates/rails_vite_hello_world/app/frontend/javascript/components/TurboStreamsChannel.svelte +56 -0
- data/templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_nav.html.erb +2 -0
- data/templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/_styles.html.erb +10 -0
- data/templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb +12 -0
- metadata +4 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 702cfaf256c84b2e8f17356ed333b63e14d4d1252f7e1e2dabd1698d350a4461
|
4
|
+
data.tar.gz: d7b63bfce50f3aba78c2bfd5f646bba5e02e0ae351d6cf4cc6129b8ba4ce26f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f86aafc1b3e91cbe2eff633fc40325dc048f00d5d668d52f7ea03c130f9171ecea1101a63c871883ac8ec173dfbaaa5932cb37e1de83a49fea5e3c09993463ef
|
7
|
+
data.tar.gz: 197a951604f5dcdf6c64bf7ccfcfe0fd65cd91e2ae8cb6d55c53de3bcd0b0821292856ba2cfdffb56f72a3b3d6e93b76a4e63ea08c8eb16548a03b4166cdcf10
|
data/README.md
CHANGED
@@ -12,34 +12,35 @@ Svelte.
|
|
12
12
|
|
13
13
|
Vite.
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
- Works
|
22
|
-
-
|
23
|
-
-
|
24
|
-
-
|
25
|
-
-
|
26
|
-
-
|
27
|
-
|
28
|
-
-
|
29
|
-
-
|
30
|
-
|
31
|
-
-
|
15
|
+
# Why Choose Svelte for Integration?
|
16
|
+
|
17
|
+
Svelte delivers the robust frontend experience missing from DHH’s
|
18
|
+
vision while aligning seamlessly with Rails’ full-stack philosophy.
|
19
|
+
|
20
|
+
- **Seamless Integration**
|
21
|
+
- Works flawlessly with Hotwired/Turbo
|
22
|
+
- Complements Hotwired perfectly
|
23
|
+
- **Developer-Friendly**
|
24
|
+
- Easy to learn
|
25
|
+
- Intuitive and powerful
|
26
|
+
- Lightning-fast performance
|
27
|
+
- **Compared to Single Page Apps (SPAs)**
|
28
|
+
- Full-stack development delivers maximum value:
|
29
|
+
- Unified testing from database to frontend
|
30
|
+
- Single-source system delivery
|
31
|
+
- For the most HTML Hotwired is enough
|
32
|
+
- **Compared to Hotwired**
|
33
|
+
- Stimulus is not a tool for writing frontend-apps
|
34
|
+
- Svelte eliminates redundant HTML state logic
|
35
|
+
- Consolidates component logic into a single file
|
36
|
+
- Offloads rendering of certain HTML components to frontend JavaScript, reducing server load
|
32
37
|
- **Compared to React**
|
33
|
-
-
|
34
|
-
-
|
35
|
-
-
|
36
|
-
-
|
38
|
+
- Watch Rich Harris’ [Rethinking Reactivity](https://svelte.dev/blog/svelte-3-rethinking-reactivity) (3:50–6:40) for a compelling comparison
|
39
|
+
- No shadow DOM or performance-heavy packages (e.g., useCallback)
|
40
|
+
- Leaner, slimmer packages
|
41
|
+
- Easier to learn and faster performance
|
37
42
|
|
38
|
-
|
39
|
-
by Rich Harris, especially from 3:50 to 6:40 and his comparison to react.
|
40
|
-
|
41
|
-
This all fits in perfectly with the Rails way of minimalist javascript,
|
42
|
-
but: Where needed, we want full power.
|
43
|
+
Svelte empowers Rails’ full-stack vision with modern, streamlined frontend integration.
|
43
44
|
|
44
45
|
# Features
|
45
46
|
|
@@ -300,6 +301,12 @@ the Svelte component is not visible for the first moment after rendering.
|
|
300
301
|
Server-side rendering is unnecessary here. You can pass 'ssr: false' to the view helper.
|
301
302
|
This relieves the server and reduces loading time.
|
302
303
|
|
304
|
+
**Tip: Testing**
|
305
|
+
|
306
|
+
Consider setting `ssr: false` for testing only for performance reasons.
|
307
|
+
Yo can override this on a attribute on the view-helper for specific components.
|
308
|
+
But, in normal cases it should not be neccessary testing ssr explicitly.
|
309
|
+
|
303
310
|
## Caching
|
304
311
|
|
305
312
|
Caching only is relevant for `ssr`
|
@@ -308,32 +315,22 @@ When using the `cached_svelte_component` helper you must have the `redis` gem in
|
|
308
315
|
|
309
316
|
This caches on a key like `svelte-on-rails:development:SvelteOnRailsHelloWorld.svelte-1xq5tnu-User1:fscyhz-18bm76a` which includes:
|
310
317
|
|
311
|
-
- gem
|
318
|
+
- Namespace, if configured, otherwise the default is gem-name and environment
|
312
319
|
- component filename
|
313
|
-
-
|
320
|
+
- checksum of the file-path
|
314
321
|
- `cache_key` if given as argument to the view-helper
|
315
322
|
- can be a array of active-record objects or strings or a single element instead of a array
|
316
|
-
-
|
317
|
-
-
|
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.
|
323
|
+
- checksum of the last modification timestamp of the component (only when `watch_changes` is set to true)
|
324
|
+
- checksum of the given attributes
|
325
325
|
|
326
|
-
|
327
|
-
redis configuration.
|
328
|
-
|
329
|
-
**Cache configuration**
|
326
|
+
**Configuration**
|
330
327
|
|
331
328
|
Like usually you can configure your cache store on your environment by something like:
|
332
329
|
|
333
330
|
```ruby
|
334
331
|
config.cache_store = :redis_cache_store, { url: 'redis://localhost:6379/2',
|
335
332
|
expires_in: 90.minutes,
|
336
|
-
namespace: '
|
333
|
+
namespace: 'my-example-app' }
|
337
334
|
```
|
338
335
|
|
339
336
|
And you can override this by
|
@@ -343,12 +340,98 @@ redis_cache_store:
|
|
343
340
|
expires_in: 2.hours
|
344
341
|
```
|
345
342
|
|
346
|
-
on the svelte-on-rails config file.
|
343
|
+
on the svelte-on-rails config file or pass the `expires_in` as argument to the view helper.
|
347
344
|
|
348
|
-
**
|
345
|
+
**Check if it works**
|
349
346
|
|
350
347
|
Pass `debug: true` to the helper and you will see on the logs how your configuration works.
|
351
348
|
|
349
|
+
# Turbo::StreamsChannel.send!
|
350
|
+
|
351
|
+
There are some methods that making it easier to let the server speak to the Components on the front.
|
352
|
+
|
353
|
+
Theese are independent class methods that can be executed from every place on the app.
|
354
|
+
|
355
|
+
Few setup is needed for that:
|
356
|
+
|
357
|
+
**Setup**
|
358
|
+
|
359
|
+
Please setup the `turbo-rails` gem and follow the chapter [Come alive with Turbo Streams](https://github.com/hotwired/turbo-rails?tab=readme-ov-file#come-alive-with-turbo-streams)
|
360
|
+
|
361
|
+
If a channel - let's call us it `public` is working and you have a html-tag on the front
|
362
|
+
with a ID - let's call us it `svelte-on-rails-stream-actions-box` (This can be any element,
|
363
|
+
you could just give the body-tag a ID // it is just the way hotwired works)
|
364
|
+
and a code Block like
|
365
|
+
|
366
|
+
```ruby
|
367
|
+
Turbo::StreamsChannel.send(
|
368
|
+
"broadcast_append_to",
|
369
|
+
'public',
|
370
|
+
target: 'svelte-on-rails-stream-actions-box',
|
371
|
+
content: "<div>Hello World</div>"
|
372
|
+
)
|
373
|
+
```
|
374
|
+
|
375
|
+
is exposing a «Hello World» on the front then you are good to go.
|
376
|
+
Please check the regarding keys and theyr commends on the [config file](https://gitlab.com/sedl/svelte-on-rails/-/blob/main/templates/config_base/config/svelte_on_rails.yml?ref_type=heads).
|
377
|
+
|
378
|
+
**Usage Example: Component Event**
|
379
|
+
|
380
|
+
The easiest way on the svelte component is:
|
381
|
+
|
382
|
+
```sveltehtml
|
383
|
+
<script>
|
384
|
+
import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
|
385
|
+
|
386
|
+
function handleCableEvent(event) {
|
387
|
+
console.log(event.detail.message)
|
388
|
+
}
|
389
|
+
</script>
|
390
|
+
<!--on any element:-->
|
391
|
+
<h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
|
392
|
+
```
|
393
|
+
|
394
|
+
And call this by:
|
395
|
+
|
396
|
+
```ruby
|
397
|
+
SvelteOnRails::TurboStream.new.dispatch_event(
|
398
|
+
event_detail: { message: "Special Chars from the server: äöü🤣🌴🌍漢字" }
|
399
|
+
)
|
400
|
+
```
|
401
|
+
|
402
|
+
**What is this doing?**
|
403
|
+
|
404
|
+
- The `addComponentStreamListener` adds a `eventListener` on the Element, that wraps each
|
405
|
+
component that is rendered by the view helper `svelte_component`, found by `currentElement.closest('.svelte-component')`
|
406
|
+
- A Stimulus controller is pushed to all subscribers of the configured channel.
|
407
|
+
- You can override this by passing `stream_name` to the method.
|
408
|
+
- Without the attribute `component` given,
|
409
|
+
it searches for all elements with the class `svelte-component` and fires the event `channel-action`
|
410
|
+
- When `selector` is given, it searches for all matching elements within each component.
|
411
|
+
- The event can be overriden by the argument `event`
|
412
|
+
|
413
|
+
By setting `debug = true` [node package](https://www.npmjs.com/package/@csedl/svelte-on-rails) you see what it is doing.
|
414
|
+
|
415
|
+
**Usage Example: Event on components element**
|
416
|
+
|
417
|
+
If you want to fire a event on a specific element within the component, you do not need `addComponentStreamListener`.
|
418
|
+
Just do something like
|
419
|
+
|
420
|
+
```sveltehtml
|
421
|
+
<button class="counter-button" onclick="{increaseCounter}">increase {counter}</button>
|
422
|
+
```
|
423
|
+
|
424
|
+
within the svelte component and call it by
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
SvelteOnRails::TurboStream.new.dispatch_event(
|
428
|
+
event: 'click',
|
429
|
+
selector: '.counter-button',
|
430
|
+
component: '/javascript/components/TurboStreamsChannel'
|
431
|
+
)
|
432
|
+
```
|
433
|
+
|
434
|
+
|
352
435
|
## More rake tasks
|
353
436
|
|
354
437
|
This tasks are more for testing/playground purposes
|
@@ -402,10 +485,14 @@ This will cause the installer, to install the npm package from a local path inst
|
|
402
485
|
|
403
486
|
Then run the tests and start contributing.
|
404
487
|
|
488
|
+
**RUN THE FIRST TEST (!)**: Testing is complex here because of the design based on the installer test.
|
489
|
+
On Problems, i always run the `Installer > destroy and create rails app > FIRST TEST > [...] check if javascript works`. When this passes,
|
490
|
+
all the others passing mostly.
|
491
|
+
|
405
492
|
Tests are based on the included templates, like the `hello world template` and on the installer.
|
406
493
|
|
407
494
|
Here I learned how to write tests! Testing installers is heavy, you will read about it in the
|
408
|
-
test_helpers. But I have
|
495
|
+
test_helpers. But I have done my best to give all the code a clear structure.
|
409
496
|
I hope you like it, and improvements are welcome.
|
410
497
|
|
411
498
|
**Installer tests** starting with completely destroy the rails app within the `generated_test_app_path`,
|
@@ -9,6 +9,7 @@ module SvelteOnRails
|
|
9
9
|
class_option :vite, type: :boolean, default: false, desc: "Use Vite"
|
10
10
|
class_option :haml, type: :boolean, default: false, desc: "Use Haml"
|
11
11
|
class_option :turbo, type: :boolean, default: false, desc: "Use @hotwired/turbo-rails"
|
12
|
+
class_option :turbo_streams, type: :boolean, default: false, desc: "Turbo::StreamsChannel"
|
12
13
|
class_option :svelte, type: :boolean, default: false, desc: "Install Svelte"
|
13
14
|
class_option :pug, type: :boolean, default: false, desc: "Install Pug"
|
14
15
|
class_option :hello_world, type: :boolean, default: false, desc: "Create Hello World component"
|
@@ -88,6 +89,20 @@ module SvelteOnRails
|
|
88
89
|
js_i = SvelteOnRails::Installer::Javascript
|
89
90
|
js_i.append_import_statement(application_js_path, tr_pkg, "import '#{tr_pkg}';")
|
90
91
|
|
92
|
+
return unless options[:turbo_streams] || options[:full]
|
93
|
+
|
94
|
+
puts '-' * 80
|
95
|
+
puts ' ▶︎▶︎▶︎ INSTALLING Turbo::StreamsChannel'
|
96
|
+
puts '-' * 80
|
97
|
+
|
98
|
+
gem_uts = SvelteOnRails::GemUtils
|
99
|
+
puts '• installing turbo-rails ...'
|
100
|
+
gem_uts.install_gem('turbo-rails')
|
101
|
+
puts '• installed turbo-rails!'
|
102
|
+
utils_i = SvelteOnRails::Installer::Utils
|
103
|
+
puts '• running turbo:install ...'
|
104
|
+
utils_i.run_command('bundle exec rails turbo:install', success_message: 'You must import')
|
105
|
+
puts '• turbo:install finished!'
|
91
106
|
end
|
92
107
|
|
93
108
|
def svelte
|
@@ -112,6 +127,12 @@ module SvelteOnRails
|
|
112
127
|
puts '-' * 80
|
113
128
|
|
114
129
|
hw_i = SvelteOnRails::Installer::HelloWorld
|
130
|
+
|
131
|
+
utils_i = SvelteOnRails::Installer::Utils
|
132
|
+
utils_i.add_route(" get \"svelte_on_rails_hello_world/turbo_streams_channel\"", app_root: Rails.root)
|
133
|
+
utils_i.add_route(" get \"svelte_on_rails_hello_world/turbo_stream_action\"", app_root: Rails.root)
|
134
|
+
npm_i = SvelteOnRails::Installer::Npm
|
135
|
+
npm_i.install_or_update_package('axios')
|
115
136
|
@hello_world_path = hw_i.install_hello_world(['rails_vite_hello_world'], app_root: nil, force: true, silent: true)
|
116
137
|
end
|
117
138
|
|
@@ -160,6 +181,10 @@ module SvelteOnRails
|
|
160
181
|
valid_display = valid_options.map { |opt| opt.gsub(/^--/, '') }.join(', ')
|
161
182
|
raise Thor::Error, "Unknown options: #{unknown_options.join(', ')}. Valid options are: #{valid_display}\nNothing done."
|
162
183
|
end
|
184
|
+
|
185
|
+
if options[:turbo_streams] && !(options[:full] || options[:turbo])
|
186
|
+
raise Thor::Error, "--turbo-streams option cannot be used without --turbo"
|
187
|
+
end
|
163
188
|
end
|
164
189
|
|
165
190
|
def application_js_path
|
data/lib/svelte-on-rails.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require "svelte_on_rails/configuration"
|
2
2
|
require "svelte_on_rails/view_helpers"
|
3
|
+
require "svelte_on_rails/turbo_stream"
|
4
|
+
require "svelte_on_rails/railtie" if defined?(Rails)
|
5
|
+
|
3
6
|
require "svelte_on_rails/renderer/renderer"
|
7
|
+
|
4
8
|
require "svelte_on_rails/lib/utils"
|
5
9
|
require "svelte_on_rails/lib/view_helper_support"
|
6
|
-
require "svelte_on_rails/railtie" if defined?(Rails)
|
7
10
|
|
8
11
|
# installer
|
9
12
|
require 'svelte_on_rails/installer/utils'
|
@@ -133,15 +133,26 @@ module SvelteOnRails
|
|
133
133
|
end
|
134
134
|
end
|
135
135
|
|
136
|
-
def self.run_command(command)
|
136
|
+
def self.run_command(command, success_message: false)
|
137
137
|
|
138
138
|
Dir.chdir(Rails.root) do
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
139
|
+
Bundler.with_unbundled_env do
|
140
|
+
`pwd`
|
141
|
+
stdout, stderr, status = Open3.capture3(command)
|
142
|
+
err_msg = if stderr.present?
|
143
|
+
stderr.strip
|
144
|
+
elsif success_message && !stdout.match(/#{success_message}/)
|
145
|
+
"missing string «#{success_message}» on result:\n\n+++#{stdout}\n+++"
|
146
|
+
else
|
147
|
+
end
|
148
|
+
|
149
|
+
if err_msg
|
150
|
+
puts "Error running command «#{command}»:"
|
151
|
+
raise err_msg
|
152
|
+
else
|
153
|
+
puts "#{command} => Success"
|
154
|
+
end
|
155
|
+
|
145
156
|
end
|
146
157
|
end
|
147
158
|
|
@@ -179,11 +190,12 @@ module SvelteOnRails
|
|
179
190
|
|
180
191
|
if existing.present? && ask_for_overwrite
|
181
192
|
begin
|
182
|
-
puts "#{'File'.pluralize(existing.length)} already exists:\n#{existing.map { |p| p[1] }.join("\n")}.\nOverwrite? (y/n)
|
193
|
+
puts "#{'File'.pluralize(existing.length)} already exists:\n#{existing.map { |p| p[1] }.join("\n")}.\nOverwrite? (y/n)
|
194
|
+
"
|
183
195
|
continue = STDIN.gets.chomp.downcase[0]
|
184
196
|
end until ['y', 'n'].include?(continue)
|
185
197
|
if continue == 'n'
|
186
|
-
puts "Skipping write #{'template'.pluralize(templates.length)}."
|
198
|
+
puts " Skipping write #{'template'.pluralize(templates.length)}."
|
187
199
|
return
|
188
200
|
end
|
189
201
|
end
|
@@ -5,14 +5,14 @@ module SvelteOnRails
|
|
5
5
|
class ViewHelperSupport
|
6
6
|
attr_reader :filename, :helper_options, :html_options, :request, :conf
|
7
7
|
|
8
|
-
def initialize(file,
|
8
|
+
def initialize(file, args, request, caching = false)
|
9
9
|
|
10
10
|
@start_time = Time.now
|
11
11
|
@filename = (file.match(/\.svelte$/) ? file[0..-8] : file)
|
12
|
+
@args_checksum = Zlib.crc32(args.to_json).to_s(36)
|
12
13
|
@conf = SvelteOnRails::Configuration.instance
|
13
|
-
@
|
14
|
-
|
15
|
-
props,
|
14
|
+
@helper_options, @html_options, @svelte_props = split_props(
|
15
|
+
args,
|
16
16
|
%i[class id style],
|
17
17
|
%i[ssr hydrate debug cache_key expires_in]
|
18
18
|
)
|
@@ -36,8 +36,6 @@ module SvelteOnRails
|
|
36
36
|
return
|
37
37
|
end
|
38
38
|
|
39
|
-
return unless ssr?
|
40
|
-
|
41
39
|
generate_cache_key
|
42
40
|
|
43
41
|
end
|
@@ -98,7 +96,7 @@ module SvelteOnRails
|
|
98
96
|
|
99
97
|
def render_ssr
|
100
98
|
renderer = SvelteOnRails::Renderer.new(filename)
|
101
|
-
renderer.render(@
|
99
|
+
renderer.render(@svelte_props)
|
102
100
|
end
|
103
101
|
|
104
102
|
def custom_cache_key
|
@@ -163,14 +161,14 @@ module SvelteOnRails
|
|
163
161
|
|
164
162
|
last_part = [
|
165
163
|
(@conf.watch_changes? ? Zlib.crc32(mtime).to_s(36) : nil),
|
166
|
-
|
164
|
+
@args_checksum
|
167
165
|
].compact.join('-')
|
168
166
|
|
169
167
|
@cache_key = [@cache_key_primary, last_part].join(':')
|
170
168
|
|
171
169
|
end
|
172
170
|
|
173
|
-
def split_props(
|
171
|
+
def split_props(args, html_options, helper_options)
|
174
172
|
prp = {}
|
175
173
|
hlp_opts = {}
|
176
174
|
ht_opts = {
|
@@ -180,7 +178,7 @@ module SvelteOnRails
|
|
180
178
|
}
|
181
179
|
}
|
182
180
|
|
183
|
-
|
181
|
+
args.each do |k, v|
|
184
182
|
_k = k.to_sym
|
185
183
|
if helper_options.include?(_k)
|
186
184
|
hlp_opts[_k] = v
|
@@ -192,11 +190,10 @@ module SvelteOnRails
|
|
192
190
|
end
|
193
191
|
|
194
192
|
ht_opts[:class] = "#{ht_opts[:class]} svelte-component".strip
|
195
|
-
|
196
|
-
ht_opts[:data][:props] = prp_js
|
193
|
+
ht_opts[:data][:props] = prp.to_json
|
197
194
|
ht_opts[:data][:svelte_status] = 'do-not-hydrate-me' if hlp_opts[:hydrate] == false
|
198
195
|
|
199
|
-
[hlp_opts, ht_opts, prp
|
196
|
+
[hlp_opts, ht_opts, prp]
|
200
197
|
end
|
201
198
|
|
202
199
|
end
|
@@ -2,18 +2,25 @@ import {loadComponentModule, readPropsFromStdin} from './utils.js';
|
|
2
2
|
|
3
3
|
(async () => {
|
4
4
|
|
5
|
+
console.log(`[svelte-on-rails:debug] awaiting load component => «${process.argv[2]}»`)
|
6
|
+
|
5
7
|
const MyComponent = await loadComponentModule(process.argv[2]);
|
8
|
+
console.log(`[svelte-on-rails:debug] component read: «${MyComponent}}»`)
|
9
|
+
|
6
10
|
const props = await readPropsFromStdin();
|
7
|
-
|
11
|
+
console.log(`[svelte-on-rails:debug] props read: «${props}»`)
|
12
|
+
|
13
|
+
const payload = {out: []};
|
8
14
|
|
9
15
|
try {
|
10
16
|
MyComponent(payload, props); // Writes directly to payload.out
|
17
|
+
console.log(`[svelte-on-rails:debug] written to payload`)
|
11
18
|
} catch (error) {
|
12
|
-
console.error('Error rendering component:', error);
|
19
|
+
console.error('[svelte-on-rails:debug] Error rendering component:', error);
|
13
20
|
process.exit(1);
|
14
21
|
}
|
15
22
|
|
16
|
-
const res = {status: 'SUCCESS', html: payload.out};
|
17
|
-
console.log(JSON.stringify(res));
|
23
|
+
const res = {status: 'SUCCESS', html: payload.out.join('')};
|
24
|
+
console.log('[svelte-on-rails:successful-json-response]' + JSON.stringify(res));
|
18
25
|
})();
|
19
26
|
|
@@ -32,10 +32,19 @@ module SvelteOnRails
|
|
32
32
|
].join(' ')
|
33
33
|
|
34
34
|
Dir.chdir(cnf.rails_root) do
|
35
|
-
stdout, stderr, status = Open3.capture3(cmd, stdin_data: props.to_json
|
35
|
+
stdout, stderr, status = Open3.capture3(cmd, stdin_data: props.to_json)
|
36
|
+
|
37
|
+
ary = stdout.split('[svelte-on-rails:successful-json-response]')
|
38
|
+
|
39
|
+
unless ary.length == 2
|
40
|
+
raise "[svelte-on-rails] render ERROR for component: #{@component_files[:svelte_filename]}\n\ncommand:\n+++\n#{cmd}\n+++\n\nstdout:\n+++\n#{stdout}+++\n\n\nstderr:\n+++\n#{stderr}+++"
|
41
|
+
end
|
42
|
+
|
36
43
|
|
37
44
|
begin
|
38
|
-
|
45
|
+
|
46
|
+
|
47
|
+
res = JSON.parse(ary[1])
|
39
48
|
css_file = @component_files[:compiled_file] + '.css'
|
40
49
|
if File.exist?(css_file)
|
41
50
|
res['css'] = File.read(css_file)
|
@@ -50,7 +59,7 @@ module SvelteOnRails
|
|
50
59
|
return res
|
51
60
|
rescue JSON::ParserError => e
|
52
61
|
|
53
|
-
raise "[svelte-on-rails] render ERROR Svelte Server-side
|
62
|
+
raise "[svelte-on-rails] render ERROR Svelte Server-side for component: #{@component_files[:svelte_filename]}\n\nError message:\n+++\n#{e.message}\n+++\n\nstdout:\n+++\n#{stdout}+++\n\n\nstderr:\n+++\n#{stderr}+++"
|
54
63
|
end
|
55
64
|
end
|
56
65
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SvelteOnRails
|
2
|
+
class TurboStream
|
3
|
+
def dispatch_event(event: 'stream-action', event_detail: nil, selector: nil, component: nil, stream_name: nil)
|
4
|
+
|
5
|
+
args = {
|
6
|
+
eventDetail: event_detail,
|
7
|
+
component: component,
|
8
|
+
event: event,
|
9
|
+
selector: selector
|
10
|
+
}
|
11
|
+
|
12
|
+
args_enc = Base64.strict_encode64(args.to_json)
|
13
|
+
|
14
|
+
Turbo::StreamsChannel.send(
|
15
|
+
"broadcast_append_to",
|
16
|
+
stream_name || configs['stream_name'],
|
17
|
+
target: configs['target_html_id'],
|
18
|
+
content: "<div style=\"display: none;\" data-controller=\"svelte-on-rails-turbo-stream\" data-args=\"#{args_enc}\"></div>"
|
19
|
+
)
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def configs
|
26
|
+
@configs ||= begin
|
27
|
+
|
28
|
+
conf = SvelteOnRails::Configuration.instance
|
29
|
+
unless conf.configs[:turbo_stream]
|
30
|
+
raise '[svelte-on-rails] missing configuration: :turbo_stream'
|
31
|
+
end
|
32
|
+
unless conf.configs[:turbo_stream]['target_html_id']
|
33
|
+
raise '[svelte-on-rails] missing configuration: turbo_stream/target_html_id'
|
34
|
+
end
|
35
|
+
unless conf.configs[:turbo_stream]['stream_name']
|
36
|
+
raise '[svelte-on-rails] missing configuration: turbo_stream/stream_name'
|
37
|
+
end
|
38
|
+
|
39
|
+
conf.configs[:turbo_stream]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -21,6 +21,12 @@ non_ssr_request_header: 'X-Turbo-Request-ID'
|
|
21
21
|
# expires_in: 90.minutes (=> default / fallback: 1 hour)
|
22
22
|
# namespace: 'my-app-svelte-on-rails'
|
23
23
|
|
24
|
+
turbo_stream:
|
25
|
+
# this part you need when you want to be able to trigger javascript actions on svelte-components from the backend
|
26
|
+
target_html_id: 'svelte-on-rails-stream-actions-box'
|
27
|
+
# html-id of any element that must exist for being able to receive actions (turbo streams can only work this way)
|
28
|
+
stream_name: 'public'
|
29
|
+
|
24
30
|
development:
|
25
31
|
watch_changes: true
|
26
32
|
# Check on every request if any file within the svelte components folder have changed, for recompiling
|
data/templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb
CHANGED
@@ -3,4 +3,23 @@ class SvelteOnRailsHelloWorldController < ApplicationController
|
|
3
3
|
def index
|
4
4
|
end
|
5
5
|
|
6
|
+
def turbo_stream_action
|
7
|
+
|
8
|
+
if params['increase']
|
9
|
+
SvelteOnRails::TurboStream.new.dispatch_event(
|
10
|
+
event: 'click',
|
11
|
+
selector: '.counter-button',
|
12
|
+
component: '/javascript/components/TurboStreamsChannel'
|
13
|
+
)
|
14
|
+
render plain: 'increase-action streamed'
|
15
|
+
else
|
16
|
+
SvelteOnRails::TurboStream.new.dispatch_event(
|
17
|
+
event_detail: { message: "Special Chars from the server: äöü🤣🌴🌍漢字", user: "Müller" }
|
18
|
+
)
|
19
|
+
render plain: 'component-action streamed'
|
20
|
+
end
|
21
|
+
|
22
|
+
head :ok
|
23
|
+
end
|
24
|
+
|
6
25
|
end
|
data/templates/rails_vite_hello_world/app/frontend/javascript/components/TurboStreamsChannel.svelte
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import axios from "axios";
|
3
|
+
import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
|
4
|
+
import { onMount } from "svelte";
|
5
|
+
|
6
|
+
let counter = $state(0)
|
7
|
+
let results = $state([])
|
8
|
+
let incrBtn
|
9
|
+
|
10
|
+
|
11
|
+
onMount(() => {
|
12
|
+
incrBtn.addEventListener('click', increaseCounter)
|
13
|
+
});
|
14
|
+
|
15
|
+
function callChannelAction(params) {
|
16
|
+
results = []
|
17
|
+
axios.get(`/svelte_on_rails_hello_world/turbo_stream_action?${params}`)
|
18
|
+
.then(function (response) {
|
19
|
+
results.push(
|
20
|
+
{
|
21
|
+
text: `server action called, status: ${response.status}`,
|
22
|
+
class: 'called-for-action'
|
23
|
+
})
|
24
|
+
})
|
25
|
+
}
|
26
|
+
|
27
|
+
function increaseCounter() {
|
28
|
+
counter += 1
|
29
|
+
}
|
30
|
+
|
31
|
+
const handleCableEvent = (ev) => {
|
32
|
+
results.push(
|
33
|
+
{
|
34
|
+
text: `Message received from TurboStream: ${JSON.stringify(ev.detail.message)}`,
|
35
|
+
class: 'stream-action-received'
|
36
|
+
}
|
37
|
+
)
|
38
|
+
};
|
39
|
+
|
40
|
+
</script>
|
41
|
+
|
42
|
+
<h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
|
43
|
+
|
44
|
+
<p>Actions that are triggered by SvelteOnRails::TurboStream channel from the server</p>
|
45
|
+
|
46
|
+
<button onstream-action="{() => callChannelAction('increase=true')}">Call increase action over Channel</button>
|
47
|
+
|
48
|
+
<button bind:this={incrBtn} class="counter-button" onclick="{increaseCounter}">increase {counter}</button>
|
49
|
+
|
50
|
+
<button class="call-channel-action" onclick="{callChannelAction}">Action</button>
|
51
|
+
|
52
|
+
<ul class="results">
|
53
|
+
{#each results as result}
|
54
|
+
<li class="{result.class}">{result.text}</li>
|
55
|
+
{/each}
|
56
|
+
</ul>
|
@@ -4,6 +4,8 @@
|
|
4
4
|
<%= link_to 'Backend/Frontend Test', '/svelte_on_rails_hello_world/backend_frontend_rendered' %>
|
5
5
|
|
|
6
6
|
<%= link_to 'SSR-Auto rendered (default)', '/svelte_on_rails_hello_world/ssr_auto_rendered' %>
|
7
|
+
|
|
8
|
+
<%= link_to 'Turbo Streams', '/svelte_on_rails_hello_world/turbo_streams_channel' %>
|
7
9
|
</div>
|
8
10
|
|
9
11
|
<% turbo_id = request.headers['X-Turbo-Request-ID'] %>
|
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:
|
4
|
+
version: 5.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Sedlmair
|
@@ -47,6 +47,7 @@ files:
|
|
47
47
|
- lib/svelte_on_rails/renderer/render.js
|
48
48
|
- lib/svelte_on_rails/renderer/renderer.rb
|
49
49
|
- lib/svelte_on_rails/renderer/utils.js
|
50
|
+
- lib/svelte_on_rails/turbo_stream.rb
|
50
51
|
- lib/svelte_on_rails/view_helpers.rb
|
51
52
|
- lib/tasks/svelte_on_rails_tasks.rake
|
52
53
|
- templates/config_base/app/frontend/ssr/ssr.js
|
@@ -62,6 +63,7 @@ files:
|
|
62
63
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/PngImport.svelte
|
63
64
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
|
64
65
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/SvgRawImport.svelte
|
66
|
+
- templates/rails_vite_hello_world/app/frontend/javascript/components/TurboStreamsChannel.svelte
|
65
67
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/sub/NestedComponent.svelte
|
66
68
|
- templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascript.js
|
67
69
|
- templates/rails_vite_hello_world/app/frontend/javascript/nestedJavascriptToggled.js
|
@@ -70,6 +72,7 @@ files:
|
|
70
72
|
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
|
71
73
|
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/index.html.erb
|
72
74
|
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
|
75
|
+
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb
|
73
76
|
homepage: https://gitlab.com/sedl/svelte-on-rails
|
74
77
|
licenses:
|
75
78
|
- MIT
|