svelte-on-rails 4.1.1 → 5.1.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 +212 -32
- data/lib/generators/svelte_on_rails/install/install_generator.rb +25 -0
- data/lib/svelte-on-rails.rb +5 -1
- data/lib/svelte_on_rails/action_cable.rb +42 -0
- 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/renderer/render.js +11 -4
- data/lib/svelte_on_rails/renderer/renderer.rb +12 -3
- data/lib/svelte_on_rails/turbo_stream.rb +47 -0
- data/templates/config_base/config/svelte_on_rails.yml +6 -0
- data/templates/rails_vite_hello_world/app/channels/svelte_on_rails_channel.rb +9 -0
- data/templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb +29 -0
- data/templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte +57 -0
- data/templates/rails_vite_hello_world/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte +1 -1
- 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 +6 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0e2782073f696f72736004ae21d57a71e4429fb878f915954e36628e16904de9
|
4
|
+
data.tar.gz: 2d041a60e7908ce20ab64f490ccebe3d3c673882986c83a64981aaf1e9931768
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d1fef990a79dfa782b0101f33ed64f3631d126ea6ef16dfde8d21d240653d8d938a87689955af288b862362216fbb760db61e79d709c512b242d0f86ad823d1
|
7
|
+
data.tar.gz: 430b9cf7ab45fb7e0ab629b4de95bd262e8b02bd68a58793dcd614832dfd2bb04d63f52d20ddc6639dc951d8ae132188dee755dadab1042f98a0268dcf4bab63
|
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
|
|
@@ -314,13 +315,13 @@ When using the `cached_svelte_component` helper you must have the `redis` gem in
|
|
314
315
|
|
315
316
|
This caches on a key like `svelte-on-rails:development:SvelteOnRailsHelloWorld.svelte-1xq5tnu-User1:fscyhz-18bm76a` which includes:
|
316
317
|
|
317
|
-
- gem
|
318
|
+
- Namespace, if configured, otherwise the default is gem-name and environment
|
318
319
|
- component filename
|
319
|
-
-
|
320
|
+
- checksum of the file-path
|
320
321
|
- `cache_key` if given as argument to the view-helper
|
321
322
|
- can be a array of active-record objects or strings or a single element instead of a array
|
322
|
-
-
|
323
|
-
-
|
323
|
+
- checksum of the last modification timestamp of the component (only when `watch_changes` is set to true)
|
324
|
+
- checksum of the given attributes
|
324
325
|
|
325
326
|
**Configuration**
|
326
327
|
|
@@ -339,12 +340,187 @@ redis_cache_store:
|
|
339
340
|
expires_in: 2.hours
|
340
341
|
```
|
341
342
|
|
342
|
-
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.
|
343
344
|
|
344
345
|
**Check if it works**
|
345
346
|
|
346
347
|
Pass `debug: true` to the helper and you will see on the logs how your configuration works.
|
347
348
|
|
349
|
+
## ActionCable / TurboStream
|
350
|
+
|
351
|
+
There are two ways that the server can talk to the client over Websocket:
|
352
|
+
|
353
|
+
- ActionCable transmits directly to the frontends javascript
|
354
|
+
- TurboStreams is a wrapper around ActionCable
|
355
|
+
- You always need a html part for communication
|
356
|
+
|
357
|
+
**Configs**
|
358
|
+
|
359
|
+
Check the regarding keys and their 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).
|
360
|
+
From there, you just need:
|
361
|
+
|
362
|
+
```yaml
|
363
|
+
turbo_stream:
|
364
|
+
target_html_id: 'svelte-on-rails-stream-actions-box'
|
365
|
+
channel: 'public'
|
366
|
+
```
|
367
|
+
|
368
|
+
## ActionCable!
|
369
|
+
|
370
|
+
ActionCable is the more basic library behind `TurboStream` and it is a second Option to call Javascript Actions from the server.
|
371
|
+
|
372
|
+
**Setup**
|
373
|
+
|
374
|
+
Add `app/channels/svelte_on_rails_channel.rb`
|
375
|
+
|
376
|
+
```ruby
|
377
|
+
class SvelteOnRailsChannel < ApplicationCable::Channel
|
378
|
+
def subscribed
|
379
|
+
stream_from "svelte_on_rails_channel"
|
380
|
+
end
|
381
|
+
|
382
|
+
def unsubscribed
|
383
|
+
# Any cleanup needed when channel is unsubscribed
|
384
|
+
end
|
385
|
+
end
|
386
|
+
```
|
387
|
+
|
388
|
+
run
|
389
|
+
|
390
|
+
```shell
|
391
|
+
npm i @rails/actioncable
|
392
|
+
```
|
393
|
+
|
394
|
+
Add to `application.js`
|
395
|
+
|
396
|
+
```javascript
|
397
|
+
import { createConsumer } from "@rails/actioncable"
|
398
|
+
import {dispatchSvelteStreamEvent, actionCableDebugLog} from '@csedl/svelte-on-rails'
|
399
|
+
|
400
|
+
const consumer = createConsumer()
|
401
|
+
|
402
|
+
consumer.subscriptions.create("SvelteOnRailsChannel", {
|
403
|
+
connected() {
|
404
|
+
actionCableDebugLog("Connected to SvelteOnRailsChannel")
|
405
|
+
},
|
406
|
+
disconnected() {
|
407
|
+
actionCableDebugLog("Disconnected from SvelteOnRailsChannel")
|
408
|
+
},
|
409
|
+
received(data) {
|
410
|
+
actionCableDebugLog("Received:", data)
|
411
|
+
dispatchSvelteStreamEvent(data)
|
412
|
+
}
|
413
|
+
})
|
414
|
+
```
|
415
|
+
|
416
|
+
By setting `debug = true` [node package](https://www.npmjs.com/package/@csedl/svelte-on-rails) you see what it is doing.
|
417
|
+
|
418
|
+
**WhatdispatchSvelteStreamEvent does**
|
419
|
+
|
420
|
+
- Without the attribute `component` given,
|
421
|
+
it searches for all elements with the class `svelte-component` and fires the event `channel-action`
|
422
|
+
- When `selector` is given, it searches for all matching elements within each component.
|
423
|
+
- The event can be overriden by the argument `event`
|
424
|
+
|
425
|
+
**Usage**
|
426
|
+
|
427
|
+
Now you can dispatch events on the component by:
|
428
|
+
|
429
|
+
```ruby
|
430
|
+
ActionCable.server.broadcast("svelte_on_rails_channel",
|
431
|
+
{
|
432
|
+
eventDetail: { message: "Sent by ActionCable: äöü🤣🌴🌍漢字" },
|
433
|
+
component: '/javascript/components/ReceiveFromChannel',
|
434
|
+
}
|
435
|
+
)
|
436
|
+
```
|
437
|
+
|
438
|
+
# Turbo::StreamsChannel.send!
|
439
|
+
|
440
|
+
Few setup is needed for that:
|
441
|
+
|
442
|
+
**Setup**
|
443
|
+
|
444
|
+
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), which mainly is:
|
445
|
+
|
446
|
+
```shell
|
447
|
+
bundle add turbo-rails
|
448
|
+
rails turbo:install
|
449
|
+
```
|
450
|
+
|
451
|
+
make sure that `import "@hotwired/turbo-rails"` is on your application.js and set the view helper
|
452
|
+
`<%= turbo_stream_from 'public' %>` to your view.
|
453
|
+
|
454
|
+
If a channel (e.g.: `public`) is active and you have an HTML element with a HTML-ID (e.g.: `svelte-on-rails-stream-actions-box`),
|
455
|
+
you can test it by:
|
456
|
+
|
457
|
+
```ruby
|
458
|
+
Turbo::StreamsChannel.send(
|
459
|
+
"broadcast_append_to",
|
460
|
+
'public',
|
461
|
+
target: 'svelte-on-rails-stream-actions-box',
|
462
|
+
content: "<div>Turbo-Streams are working!</div>"
|
463
|
+
)
|
464
|
+
```
|
465
|
+
|
466
|
+
When this works you are good to go.
|
467
|
+
|
468
|
+
**Minimal Usage Example**
|
469
|
+
|
470
|
+
```sveltehtml
|
471
|
+
<script>
|
472
|
+
import {addComponentStreamListener} from '@csedl/svelte-on-rails/src/componentStreamListener'
|
473
|
+
|
474
|
+
function handleCableEvent() {
|
475
|
+
console.log('Event received by Turbo Stream')
|
476
|
+
}
|
477
|
+
</script>
|
478
|
+
<!--on any element:-->
|
479
|
+
<h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
|
480
|
+
```
|
481
|
+
|
482
|
+
And call this by:
|
483
|
+
|
484
|
+
```ruby
|
485
|
+
SvelteOnRails::TurboStream.new.dispatch_event
|
486
|
+
```
|
487
|
+
**What it does**
|
488
|
+
|
489
|
+
- The `addComponentStreamListener` adds the eventListener `stream-action` on the wrapping Element
|
490
|
+
- The «wrapping Element» is the Element from the view helper `svelte_component` with the class `svelte-component`.
|
491
|
+
- A Stimulus controller is pushed to all subscribers of the configured channel.
|
492
|
+
- You can override the channel name by passing `channel` to the method.
|
493
|
+
|
494
|
+
**Usage Example, more detailed**
|
495
|
+
|
496
|
+
If you want to fire a event on a specific element within the component, you do not need `addComponentStreamListener`.
|
497
|
+
Just do something like:
|
498
|
+
|
499
|
+
```sveltehtml
|
500
|
+
<script>
|
501
|
+
function logStreamMessage(event) {
|
502
|
+
console.log(event.detail.message)
|
503
|
+
}
|
504
|
+
</script>
|
505
|
+
<button class="counter-button" onclick="{logStreamMessage}">Show Contents</button>
|
506
|
+
```
|
507
|
+
|
508
|
+
within the svelte component.
|
509
|
+
|
510
|
+
Then, call it by:
|
511
|
+
|
512
|
+
```ruby
|
513
|
+
SvelteOnRails::TurboStream.new.dispatch_event(
|
514
|
+
channel: 'my-custom-stream',
|
515
|
+
component: '/javascript/components/TurboStreamsChannel',
|
516
|
+
selector: '.counter-button',
|
517
|
+
event: 'click',
|
518
|
+
event_detail: { message: "Greetings from Server: äöü🤣🌴🌍漢字" }
|
519
|
+
)
|
520
|
+
```
|
521
|
+
|
522
|
+
|
523
|
+
|
348
524
|
## More rake tasks
|
349
525
|
|
350
526
|
This tasks are more for testing/playground purposes
|
@@ -398,10 +574,14 @@ This will cause the installer, to install the npm package from a local path inst
|
|
398
574
|
|
399
575
|
Then run the tests and start contributing.
|
400
576
|
|
577
|
+
**RUN THE FIRST TEST (!)**: Testing is complex here because of the design based on the installer test.
|
578
|
+
On Problems, i always run the `Installer > destroy and create rails app > FIRST TEST > [...] check if javascript works`. When this passes,
|
579
|
+
all the others passing mostly.
|
580
|
+
|
401
581
|
Tests are based on the included templates, like the `hello world template` and on the installer.
|
402
582
|
|
403
583
|
Here I learned how to write tests! Testing installers is heavy, you will read about it in the
|
404
|
-
test_helpers. But I have
|
584
|
+
test_helpers. But I have done my best to give all the code a clear structure.
|
405
585
|
I hope you like it, and improvements are welcome.
|
406
586
|
|
407
587
|
**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,13 @@
|
|
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/action_cable"
|
5
|
+
require "svelte_on_rails/railtie" if defined?(Rails)
|
6
|
+
|
3
7
|
require "svelte_on_rails/renderer/renderer"
|
8
|
+
|
4
9
|
require "svelte_on_rails/lib/utils"
|
5
10
|
require "svelte_on_rails/lib/view_helper_support"
|
6
|
-
require "svelte_on_rails/railtie" if defined?(Rails)
|
7
11
|
|
8
12
|
# installer
|
9
13
|
require 'svelte_on_rails/installer/utils'
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SvelteOnRails
|
2
|
+
class ActionCable
|
3
|
+
def dispatch_event(event: 'stream-action', event_detail: nil, selector: nil, component: nil, channel: "svelte_on_rails_channel")
|
4
|
+
|
5
|
+
if event != 'stream-action' && !selector
|
6
|
+
raise "Another event name than the default one is only possible together with a selector"
|
7
|
+
end
|
8
|
+
|
9
|
+
args = {
|
10
|
+
eventDetail: event_detail,
|
11
|
+
component: component,
|
12
|
+
event: event,
|
13
|
+
selector: selector
|
14
|
+
}
|
15
|
+
|
16
|
+
#args_enc = Base64.strict_encode64(args.to_json)
|
17
|
+
|
18
|
+
ActionCable.server.broadcast(channel, args)
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def configs
|
25
|
+
@configs ||= begin
|
26
|
+
|
27
|
+
conf = SvelteOnRails::Configuration.instance
|
28
|
+
unless conf.configs[:turbo_stream]
|
29
|
+
raise '[svelte-on-rails] missing configuration: :turbo_stream'
|
30
|
+
end
|
31
|
+
unless conf.configs[:turbo_stream]['target_html_id']
|
32
|
+
raise '[svelte-on-rails] missing configuration: turbo_stream/target_html_id'
|
33
|
+
end
|
34
|
+
unless conf.configs[:turbo_stream]['channel']
|
35
|
+
raise '[svelte-on-rails] missing configuration: turbo_stream/channel'
|
36
|
+
end
|
37
|
+
|
38
|
+
conf.configs[:turbo_stream]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -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
|
@@ -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,47 @@
|
|
1
|
+
module SvelteOnRails
|
2
|
+
class TurboStream
|
3
|
+
def dispatch_event(event: 'stream-action', event_detail: nil, selector: nil, component: nil, channel: nil)
|
4
|
+
|
5
|
+
if event != 'stream-action' && !selector
|
6
|
+
raise "Another event name than the default one is only possible together with a selector"
|
7
|
+
end
|
8
|
+
|
9
|
+
args = {
|
10
|
+
eventDetail: event_detail,
|
11
|
+
component: component,
|
12
|
+
event: event,
|
13
|
+
selector: selector
|
14
|
+
}
|
15
|
+
|
16
|
+
args_enc = Base64.strict_encode64(args.to_json)
|
17
|
+
|
18
|
+
Turbo::StreamsChannel.send(
|
19
|
+
"broadcast_append_to",
|
20
|
+
channel || configs['channel'],
|
21
|
+
target: configs['target_html_id'],
|
22
|
+
content: "<div style=\"display: none;\" data-controller=\"svelte-on-rails-turbo-stream\" data-args=\"#{args_enc}\"></div>"
|
23
|
+
)
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def configs
|
30
|
+
@configs ||= begin
|
31
|
+
|
32
|
+
conf = SvelteOnRails::Configuration.instance
|
33
|
+
unless conf.configs[:turbo_stream]
|
34
|
+
raise '[svelte-on-rails] missing configuration: :turbo_stream'
|
35
|
+
end
|
36
|
+
unless conf.configs[:turbo_stream]['target_html_id']
|
37
|
+
raise '[svelte-on-rails] missing configuration: turbo_stream/target_html_id'
|
38
|
+
end
|
39
|
+
unless conf.configs[:turbo_stream]['channel']
|
40
|
+
raise '[svelte-on-rails] missing configuration: turbo_stream/channel'
|
41
|
+
end
|
42
|
+
|
43
|
+
conf.configs[:turbo_stream]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
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
|
+
channel: '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,33 @@ 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/ReceiveFromChannel'
|
13
|
+
)
|
14
|
+
render plain: 'increase-action streamed'
|
15
|
+
elsif params['action_cable']
|
16
|
+
|
17
|
+
ActionCable.server.broadcast("svelte_on_rails_channel",
|
18
|
+
{
|
19
|
+
eventDetail: { message: "Sent by ActionCable: äöü🤣🌴🌍漢字" },
|
20
|
+
component: '/javascript/components/ReceiveFromChannel',
|
21
|
+
}
|
22
|
+
)
|
23
|
+
#SvelteOnRails::ActionCable.new.dispatch_event(channel: "svelte_on_rails_channel", event_detail: { message: "Hello from SvelteChannel!" })
|
24
|
+
|
25
|
+
render plain: 'ActionCable streaming'
|
26
|
+
else
|
27
|
+
SvelteOnRails::TurboStream.new.dispatch_event(
|
28
|
+
event_detail: { message: "Sent by TurboStream: äöü🤣🌴🌍漢字", user: "Müller" }
|
29
|
+
)
|
30
|
+
render plain: 'component-action streamed'
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
6
35
|
end
|
data/templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte
ADDED
@@ -0,0 +1,57 @@
|
|
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
|
+
onMount(() => {
|
11
|
+
incrBtn.addEventListener('click', increaseCounter)
|
12
|
+
});
|
13
|
+
|
14
|
+
function callChannelAction(params) {
|
15
|
+
results = []
|
16
|
+
axios.get(`/svelte_on_rails_hello_world/turbo_stream_action?${params}`)
|
17
|
+
.then(function (response) {
|
18
|
+
results.push(
|
19
|
+
{
|
20
|
+
text: `server action called, status: ${response.status}`,
|
21
|
+
class: 'called-for-action'
|
22
|
+
})
|
23
|
+
})
|
24
|
+
}
|
25
|
+
|
26
|
+
function increaseCounter() {
|
27
|
+
counter += 1
|
28
|
+
}
|
29
|
+
|
30
|
+
const handleCableEvent = (ev) => {
|
31
|
+
results.push(
|
32
|
+
{
|
33
|
+
text: `Message received from Server: ${JSON.stringify(ev.detail.message)}`,
|
34
|
+
class: 'stream-action-received'
|
35
|
+
}
|
36
|
+
)
|
37
|
+
};
|
38
|
+
|
39
|
+
</script>
|
40
|
+
|
41
|
+
<h1 use:addComponentStreamListener={handleCableEvent}>Test TurboStreams Channel</h1>
|
42
|
+
|
43
|
+
<p>Actions that are triggered by SvelteOnRails::TurboStream channel from the server</p>
|
44
|
+
|
45
|
+
<button onstream-action="{() => callChannelAction('increase=true')}">Call increase action over Channel</button>
|
46
|
+
|
47
|
+
<button bind:this={incrBtn} class="counter-button" onclick="{increaseCounter}">increase {counter}</button>
|
48
|
+
|
49
|
+
<button class="call-channel-action" onclick="{callChannelAction}">Action</button>
|
50
|
+
|
51
|
+
<button onclick="{() => callChannelAction('action_cable=true')}">Call ActionCable Action</button>
|
52
|
+
|
53
|
+
<ul class="results">
|
54
|
+
{#each results as result}
|
55
|
+
<li class="{result.class}">{result.text}</li>
|
56
|
+
{/each}
|
57
|
+
</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.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christian Sedlmair
|
@@ -31,6 +31,7 @@ files:
|
|
31
31
|
- README.md
|
32
32
|
- lib/generators/svelte_on_rails/install/install_generator.rb
|
33
33
|
- lib/svelte-on-rails.rb
|
34
|
+
- lib/svelte_on_rails/action_cable.rb
|
34
35
|
- lib/svelte_on_rails/configuration.rb
|
35
36
|
- lib/svelte_on_rails/installer/gem_utils.rb
|
36
37
|
- lib/svelte_on_rails/installer/haml.rb
|
@@ -47,11 +48,13 @@ files:
|
|
47
48
|
- lib/svelte_on_rails/renderer/render.js
|
48
49
|
- lib/svelte_on_rails/renderer/renderer.rb
|
49
50
|
- lib/svelte_on_rails/renderer/utils.js
|
51
|
+
- lib/svelte_on_rails/turbo_stream.rb
|
50
52
|
- lib/svelte_on_rails/view_helpers.rb
|
51
53
|
- lib/tasks/svelte_on_rails_tasks.rake
|
52
54
|
- templates/config_base/app/frontend/ssr/ssr.js
|
53
55
|
- templates/config_base/config/svelte_on_rails.yml
|
54
56
|
- templates/config_base/vite-ssr.config.ts
|
57
|
+
- templates/rails_vite_hello_world/app/channels/svelte_on_rails_channel.rb
|
55
58
|
- templates/rails_vite_hello_world/app/controllers/svelte_on_rails_hello_world_controller.rb
|
56
59
|
- templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-england.png
|
57
60
|
- templates/rails_vite_hello_world/app/frontend/images/svelte-on-rails-hello-world-face-smile-wink.svg
|
@@ -60,6 +63,7 @@ files:
|
|
60
63
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/JpgImport.svelte
|
61
64
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/ParentWithChild.svelte
|
62
65
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/PngImport.svelte
|
66
|
+
- templates/rails_vite_hello_world/app/frontend/javascript/components/ReceiveFromChannel.svelte
|
63
67
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/SvelteOnRailsHelloWorld.svelte
|
64
68
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/SvgRawImport.svelte
|
65
69
|
- templates/rails_vite_hello_world/app/frontend/javascript/components/sub/NestedComponent.svelte
|
@@ -70,6 +74,7 @@ files:
|
|
70
74
|
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/backend_frontend_rendered.html.erb
|
71
75
|
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/index.html.erb
|
72
76
|
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/ssr_auto_rendered.html.erb
|
77
|
+
- templates/rails_vite_hello_world/app/views/svelte_on_rails_hello_world/turbo_streams_channel.html.erb
|
73
78
|
homepage: https://gitlab.com/sedl/svelte-on-rails
|
74
79
|
licenses:
|
75
80
|
- MIT
|