stimulus_reflex 3.5.0.pre9 → 3.5.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of stimulus_reflex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +122 -127
- data/README.md +13 -19
- data/app/assets/javascripts/stimulus_reflex.js +1017 -523
- data/app/assets/javascripts/stimulus_reflex.umd.js +940 -496
- data/app/channels/stimulus_reflex/channel.rb +9 -24
- data/bin/console +0 -2
- data/bin/standardize +2 -1
- data/lib/generators/stimulus_reflex/stimulus_reflex_generator.rb +68 -9
- data/lib/generators/stimulus_reflex/templates/app/controllers/examples_controller.rb.tt +9 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/channels/consumer.js.tt +6 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.esbuild.tt +4 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.importmap.tt +2 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.shakapacker.tt +5 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.vite.tt +1 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.webpacker.tt +5 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/config/cable_ready.js.tt +4 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/config/index.js.tt +2 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/config/mrujs.js.tt +9 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/config/stimulus_reflex.js.tt +5 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/%file_name%_controller.js.tt +141 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/application.js.tt +11 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/application_controller.js.tt +74 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.esbuild.tt +7 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.importmap.tt +5 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.shakapacker.tt +5 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.vite.tt +5 -0
- data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.webpacker.tt +5 -0
- data/{test/tmp/app/reflexes/user_reflex.rb → lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt} +38 -9
- data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +27 -0
- data/lib/generators/stimulus_reflex/templates/app/views/examples/show.html.erb.tt +207 -0
- data/lib/generators/stimulus_reflex/templates/config/initializers/cable_ready.rb +27 -0
- data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +18 -13
- data/lib/generators/stimulus_reflex/templates/esbuild.config.mjs.tt +94 -0
- data/lib/install/action_cable.rb +155 -0
- data/lib/install/broadcaster.rb +90 -0
- data/lib/install/bundle.rb +56 -0
- data/lib/install/compression.rb +41 -0
- data/lib/install/config.rb +87 -0
- data/lib/install/development.rb +110 -0
- data/lib/install/esbuild.rb +114 -0
- data/lib/install/example.rb +22 -0
- data/lib/install/importmap.rb +133 -0
- data/lib/install/initializers.rb +25 -0
- data/lib/install/mrujs.rb +133 -0
- data/lib/install/npm_packages.rb +25 -0
- data/lib/install/reflexes.rb +25 -0
- data/lib/install/shakapacker.rb +64 -0
- data/lib/install/spring.rb +54 -0
- data/lib/install/updatable.rb +34 -0
- data/lib/install/vite.rb +64 -0
- data/lib/install/webpacker.rb +90 -0
- data/lib/install/yarn.rb +55 -0
- data/lib/stimulus_reflex/broadcasters/broadcaster.rb +15 -8
- data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +7 -8
- data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +10 -10
- data/lib/stimulus_reflex/broadcasters/update.rb +3 -0
- data/lib/stimulus_reflex/cable_readiness.rb +29 -0
- data/lib/stimulus_reflex/cable_ready_channels.rb +6 -5
- data/lib/stimulus_reflex/callbacks.rb +17 -1
- data/lib/stimulus_reflex/concern_enhancer.rb +6 -4
- data/lib/stimulus_reflex/configuration.rb +12 -2
- data/lib/stimulus_reflex/dataset.rb +11 -1
- data/lib/stimulus_reflex/engine.rb +16 -9
- data/lib/stimulus_reflex/html/document.rb +59 -0
- data/lib/stimulus_reflex/html/document_fragment.rb +13 -0
- data/lib/stimulus_reflex/importmap.rb +6 -3
- data/lib/stimulus_reflex/installer.rb +274 -0
- data/lib/stimulus_reflex/open_struct_fix.rb +2 -0
- data/lib/stimulus_reflex/reflex.rb +40 -31
- data/lib/stimulus_reflex/reflex_data.rb +19 -3
- data/lib/stimulus_reflex/reflex_factory.rb +6 -3
- data/lib/stimulus_reflex/request_parameters.rb +2 -0
- data/lib/stimulus_reflex/utils/logger.rb +10 -0
- data/lib/stimulus_reflex/utils/sanity_checker.rb +8 -48
- data/lib/stimulus_reflex/version.rb +1 -1
- data/lib/stimulus_reflex/version_checker.rb +54 -0
- data/lib/stimulus_reflex.rb +2 -0
- data/lib/tasks/stimulus_reflex/stimulus_reflex.rake +250 -0
- data/package.json +36 -28
- data/{rollup.config.js → rollup.config.mjs} +6 -24
- data/stimulus_reflex.gemspec +16 -19
- data/yarn.lock +1331 -748
- metadata +129 -79
- data/LATEST +0 -1
- data/app/assets/javascripts/stimulus_reflex.min.js +0 -2
- data/app/assets/javascripts/stimulus_reflex.min.js.map +0 -1
- data/app/assets/javascripts/stimulus_reflex.umd.min.js +0 -905
- data/app/assets/javascripts/stimulus_reflex.umd.min.js.map +0 -1
- data/lib/generators/stimulus_reflex/initializer_generator.rb +0 -14
- data/test/broadcasters/broadcaster_test.rb +0 -11
- data/test/broadcasters/broadcaster_test_case.rb +0 -39
- data/test/broadcasters/nothing_broadcaster_test.rb +0 -31
- data/test/broadcasters/page_broadcaster_test.rb +0 -79
- data/test/broadcasters/selector_broadcaster_test.rb +0 -173
- data/test/callbacks_test.rb +0 -652
- data/test/concern_enhancer_test.rb +0 -54
- data/test/element_test.rb +0 -254
- data/test/generators/stimulus_reflex_generator_test.rb +0 -58
- data/test/reflex_test.rb +0 -43
- data/test/test_helper.rb +0 -71
- data/test/tmp/app/reflexes/application_reflex.rb +0 -12
- data/yarn-error.log +0 -4964
@@ -0,0 +1,207 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<title>StimulusReflex Demo</title>
|
5
|
+
<meta charset="utf-8">
|
6
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
7
|
+
<%%= csrf_meta_tags %>
|
8
|
+
<%%= csp_meta_tag %>
|
9
|
+
|
10
|
+
<%% unless Rails.root.join("config/importmap.rb").exist? %>
|
11
|
+
<script type="importmap">
|
12
|
+
{
|
13
|
+
"imports": {
|
14
|
+
"fireworks-js": "https://ga.jspm.io/npm:fireworks-js@2.10.0/dist/index.es.js"
|
15
|
+
}
|
16
|
+
}
|
17
|
+
</script>
|
18
|
+
<%% end %>
|
19
|
+
|
20
|
+
<%% if respond_to?(:vite_javascript_tag) %>
|
21
|
+
<%%= vite_client_tag %>
|
22
|
+
<%%= vite_javascript_tag "application", defer: true %>
|
23
|
+
<%% elsif respond_to?(:javascript_pack_tag) %>
|
24
|
+
<%%= javascript_pack_tag "application", defer: true %>
|
25
|
+
<%% elsif respond_to?(:javascript_importmap_tags) %>
|
26
|
+
<%%= javascript_importmap_tags %>
|
27
|
+
<%% elsif respond_to?(:javascript_include_tag) %>
|
28
|
+
<%%= javascript_include_tag "application", defer: true %>
|
29
|
+
<%% end %>
|
30
|
+
|
31
|
+
<script async src="https://ga.jspm.io/npm:es-module-shims@1.5.1/dist/es-module-shims.js" crossorigin="anonymous"></script>
|
32
|
+
<script type="module">
|
33
|
+
import { Fireworks } from 'fireworks-js'
|
34
|
+
|
35
|
+
const fireworks = new Fireworks(document.querySelector('.fireworks'))
|
36
|
+
document.addEventListener('fireworks', () => fireworks.launch(12))
|
37
|
+
</script>
|
38
|
+
|
39
|
+
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
|
40
|
+
</head>
|
41
|
+
|
42
|
+
<body style="cursor: auto;">
|
43
|
+
<main class="container">
|
44
|
+
<h1>StimulusReflex</h1>
|
45
|
+
|
46
|
+
<p>Actual demonstrations will show up in this space, soon. In the meantime, verify that your installation was successful:</p>
|
47
|
+
|
48
|
+
<button data-reflex="click->Example#increment">Increment</button>
|
49
|
+
<button data-reflex="click->Example#reset">Reset</button>
|
50
|
+
|
51
|
+
<br />
|
52
|
+
|
53
|
+
<div>
|
54
|
+
Last Refresh:
|
55
|
+
<b><mark id="time"><%%= Time.now %></mark></b>
|
56
|
+
</div>
|
57
|
+
|
58
|
+
<div>
|
59
|
+
Clicked
|
60
|
+
<b><mark id="count"><%%= session[:count] || 0 %></mark></b>
|
61
|
+
times
|
62
|
+
</div>
|
63
|
+
|
64
|
+
<br/>
|
65
|
+
|
66
|
+
<progress value="<%%= session[:count] %>" max="10"></progress>
|
67
|
+
|
68
|
+
<div id="reload"></div>
|
69
|
+
|
70
|
+
<h1>CableReady</h1>
|
71
|
+
|
72
|
+
<p>CableReady lets you control one or many clients from the server in real-time.</p>
|
73
|
+
|
74
|
+
<p>Everything in CableReady revolves around its <a href="https://cableready.stimulusreflex.com/reference/operations" target="_blank">38+</a> <b>operations</b>, which are commands that can update content, raise events, write cookies and even play audio. A group of one or more operations is called a <b>broadcast</b>. Broadcasts follow a <a href="https://dev.to/leastbad/the-cableready-language-implementation-project-4hjd">simple JSON format</a>.</p>
|
75
|
+
|
76
|
+
<p>We're going to go through the main ways developers use CableReady with some live demonstrations and code samples. We recommend that you open the controller class and ERB template for this page to follow along.</p>
|
77
|
+
|
78
|
+
<article>
|
79
|
+
<h3>Subscribe to a channel to receive broadcasts</h3>
|
80
|
+
|
81
|
+
WebSockets is the primary way most Rails developers use CableReady, via the <ins>cable_ready</ins> method.
|
82
|
+
|
83
|
+
<p>Use the <ins>cable_ready_stream_from</ins> helper to create a secure Action Cable subscription:</p>
|
84
|
+
|
85
|
+
<kbd>cable_ready_stream_from :example_page</kbd>
|
86
|
+
|
87
|
+
<%%= cable_ready_stream_from :example_page %>
|
88
|
+
|
89
|
+
<p style="margin-top: 1rem;">Every user looking at a page subscribed to the <ins>:example_page</ins> channel will receive the same broadcasts.</p>
|
90
|
+
|
91
|
+
<p>You can call <ins>cable_ready</ins> <a href="https://cableready.stimulusreflex.com/guide/cableready-everywhere" target="_blank">pretty much anywhere</a> in your application. Try it in your <kbd>rails console</kbd> now:</p>
|
92
|
+
|
93
|
+
<kbd>include CableReady::Broadcaster<br>cable_ready[:example_page].text_content("#cable_ready_stream_from_output", text: "Hello from the console!").broadcast</kbd>
|
94
|
+
|
95
|
+
<p style="margin-top: 1rem;">Any message you send will appear in the <ins>#cable_ready_stream_from_output</ins> DIV below — even if you <i>open multiple tabs</i>.</p>
|
96
|
+
|
97
|
+
<div id="cable_ready_stream_from_output" style="height: 2rem; font-weight: bolder; border: coral 2px dashed; padding: 0.15rem 0.4rem;"></div>
|
98
|
+
|
99
|
+
<p style="margin-top: 1rem;">While it's easy to <a href="https://cableready.stimulusreflex.com/guide/broadcasting-to-resources" target="_blank">create your own custom Action Cable channels</a>, <ins>cable_ready_stream_from</ins> will be the first tool you reach for, because it doesn't require any additional code.</p>
|
100
|
+
|
101
|
+
<p>Specify Active Record models or compound qualifiers to go full-ninja: 🥷</p>
|
102
|
+
|
103
|
+
<kbd>cable_ready_stream_from current_user</kbd><br>
|
104
|
+
|
105
|
+
<kbd style="margin-top: 0.3rem;">cable_ready_stream_from @post, :comments</kbd>
|
106
|
+
|
107
|
+
<p style="margin-top: 1rem;">These examples barely scrape the surface of what's possible. Be sure to check out the <a href="https://cableready.stimulusreflex.com/guide/identifiers" target="_blank">Stream Identifiers</a> chapter.</p>
|
108
|
+
</article>
|
109
|
+
|
110
|
+
<article>
|
111
|
+
<h3>Updatable: magically update the DOM when server-side data changes</h3>
|
112
|
+
|
113
|
+
<p>The <ins>updates_for</ins> helper allow you to designate sections of your page that will <a href="https://cableready.stimulusreflex.com/guide/updatable" target="_blank">update automatically</a> with new content when an Active Record model changes. 🤯</p>
|
114
|
+
|
115
|
+
<small>It's difficult to demonstrate this feature without creating a temporary model and a migration; a road to hell, paved with good intentions. However, you likely have these models (or similar) in your application. Uncomment, tweak if necessary and follow along!</small>
|
116
|
+
|
117
|
+
<p style="margin-top: 1rem;">First, call <ins>enable_updates</ins> in your model. You can use it on associations, too.</p>
|
118
|
+
|
119
|
+
<kbd> class User < ApplicationRecord<br>
|
120
|
+
enable_updates<br>
|
121
|
+
has_many :posts, enable_updates: true<br>
|
122
|
+
end<br>
|
123
|
+
<br>
|
124
|
+
class Post < ApplicationRecord<br>
|
125
|
+
belongs_to :user<br>
|
126
|
+
end
|
127
|
+
</kbd>
|
128
|
+
|
129
|
+
<p style="margin-top: 1rem;">By default, updates will be broadcast when any CRUD action is performed on the model. You can customize this behavior by passing options such as <ins>on: [:create, :update]</ins> or <ins>if: -> { id.even? }</ins>.</p>
|
130
|
+
|
131
|
+
<p>Next, use the <ins>updates_for</ins> helper to create one or more containers that will receive content updates.</p>
|
132
|
+
|
133
|
+
<kbd> <%= cable_ready_updates_for current_user do %><br>
|
134
|
+
<%= current_user.name %><br>
|
135
|
+
<% end %>
|
136
|
+
</kbd>
|
137
|
+
|
138
|
+
<!--
|
139
|
+
<%%#= cable_ready_updates_for current_user do %>
|
140
|
+
<p style="margin-top: 1rem;"><%%#= current_user.name %></p>
|
141
|
+
<%%# end %>
|
142
|
+
-->
|
143
|
+
|
144
|
+
<p style="margin-top: 1rem;">Update the current user in Rails console, and your page instantly reflects the new name. 🪄</p>
|
145
|
+
|
146
|
+
<p>Specify the class constant to get updates when records are created or deleted:</p>
|
147
|
+
|
148
|
+
<kbd> <%= cable_ready_updates_for User do %><br>
|
149
|
+
<ul><br>
|
150
|
+
<% @users.each do |user| %><br>
|
151
|
+
<li><%= user.name %></li><br>
|
152
|
+
<% end %><br>
|
153
|
+
</ul><br>
|
154
|
+
<% end %>
|
155
|
+
</kbd>
|
156
|
+
|
157
|
+
<!--
|
158
|
+
<%%#= cable_ready_updates_for User do %>
|
159
|
+
<ul style="margin-top: 1rem;">
|
160
|
+
<%%# @users.each do |user| %>
|
161
|
+
<li><%%#= user.name %></li>
|
162
|
+
<%%# end %>
|
163
|
+
</ul>
|
164
|
+
<%%# end %>
|
165
|
+
-->
|
166
|
+
|
167
|
+
<p style="margin-top: 1rem;">Update when new posts are created by the current user:</p>
|
168
|
+
|
169
|
+
<kbd> <%= cable_ready_updates_for current_user, :posts do %><br>
|
170
|
+
<ul><br>
|
171
|
+
<% @posts.each do |post| %><br>
|
172
|
+
<li><%= post.title %></li><br>
|
173
|
+
<% end %><br>
|
174
|
+
</ul><br>
|
175
|
+
<% end %>
|
176
|
+
</kbd>
|
177
|
+
|
178
|
+
<!--
|
179
|
+
<%%#= cable_ready_updates_for current_user, :posts do %>
|
180
|
+
<ul style="margin-top: 1rem;">
|
181
|
+
<%%# @posts.each do |post| %>
|
182
|
+
<li><%%#= post.title %></li>
|
183
|
+
<%%# end %>
|
184
|
+
</ul>
|
185
|
+
<%%# end %>
|
186
|
+
-->
|
187
|
+
|
188
|
+
<p style="margin-top: 1rem;">One major advantage of the Updatable approach is that each visitor sees <b>personalized content</b>. This is difficult with a WebSockets broadcast, where every subscriber receives the same data.</p>
|
189
|
+
|
190
|
+
<p>Instead, Updatable notifies all subscribers that an update is available, prompting each client to make a fetch request and refresh sections of the page.</p>
|
191
|
+
|
192
|
+
<p>There's more to <a href="https://cableready.stimulusreflex.com/guide/updatable" target="_blank">Updatable</a> than what's covered here... <i>but, not much more</i>. It really is that simple.</p>
|
193
|
+
</article>
|
194
|
+
|
195
|
+
<article>
|
196
|
+
<p>If you're finished with this example page and resource controller, you can destroy them:</p>
|
197
|
+
|
198
|
+
<kbd>rails destroy stimulus_reflex example</kbd>
|
199
|
+
</article>
|
200
|
+
|
201
|
+
<p>As always, please drop by the <a href="https://discord.gg/stimulus-reflex" target="_blank">StimulusReflex Discord server</a> if you have any questions or need support of any kind. We're incredibly proud of the community that has formed around these libraries, and we discuss everything from JavaScript/Ruby/CSS to View Component/Phlex to databases and CRDTs. <b>We'd love to hear what you're building with StimulusReflex and CableReady.</b></p>
|
202
|
+
|
203
|
+
<p>You can find the documentation for StimulusReflex <a href="https://docs.stimulusreflex.com" target="_blank">here</a> and CableReady <a href="https://cableready.stimulusreflex.com" target="_blank">here</a>.</p>
|
204
|
+
</main>
|
205
|
+
<div class="fireworks" style="position: fixed; bottom: 0; width: 100vw; height: 100vh; pointer-events: none;"></div>
|
206
|
+
</body>
|
207
|
+
</html>
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
CableReady.configure do |config|
|
4
|
+
# Enable/disable exiting / warning when the sanity checks fail options:
|
5
|
+
# `:exit` or `:warn` or `:ignore`
|
6
|
+
#
|
7
|
+
# config.on_failed_sanity_checks = :exit
|
8
|
+
|
9
|
+
# Enable/disable assets compilation
|
10
|
+
# `true` or `false`
|
11
|
+
#
|
12
|
+
# config.precompile_assets = true
|
13
|
+
|
14
|
+
# Define your own custom operations
|
15
|
+
# https://cableready.stimulusreflex.com/customization#custom-operations
|
16
|
+
#
|
17
|
+
# config.add_operation_name :jazz_hands
|
18
|
+
|
19
|
+
# Change the default Active Job queue used for broadcast_later and broadcast_later_to
|
20
|
+
#
|
21
|
+
# config.broadcast_job_queue = :default
|
22
|
+
|
23
|
+
# Specify a default debounce time for CableReady::Updatable callbacks
|
24
|
+
# Doing so is a best practice to avoid heavy ActionCable traffic
|
25
|
+
#
|
26
|
+
# config.updatable_debounce_time = 0.1.seconds
|
27
|
+
end
|
@@ -6,44 +6,49 @@
|
|
6
6
|
# ActionCable.server.config.logger = Logger.new(nil)
|
7
7
|
|
8
8
|
StimulusReflex.configure do |config|
|
9
|
-
# Enable/disable exiting / warning when the sanity checks fail
|
9
|
+
# Enable/disable exiting / warning when the sanity checks fail:
|
10
10
|
# `:exit` or `:warn` or `:ignore`
|
11
|
-
|
11
|
+
#
|
12
12
|
# config.on_failed_sanity_checks = :exit
|
13
13
|
|
14
|
-
# Enable/disable exiting / warning when there's a new StimulusReflex release
|
15
|
-
# `:exit` or `:warn` or `:ignore`
|
16
|
-
|
17
|
-
# config.on_new_version_available = :ignore
|
18
|
-
|
19
14
|
# Enable/disable exiting / warning when there is no default URLs specified in environment config
|
20
15
|
# `:warn` or `:ignore`
|
21
|
-
|
16
|
+
#
|
22
17
|
# config.on_missing_default_urls = :warn
|
23
18
|
|
24
|
-
#
|
19
|
+
# Enable/disable assets compilation
|
20
|
+
# `true` or `false`
|
21
|
+
#
|
22
|
+
# config.precompile_assets = true
|
25
23
|
|
24
|
+
# Override the CableReady operation used for morphing and replacing content
|
25
|
+
#
|
26
|
+
# config.morph_operation = :morph
|
27
|
+
# config.replace_operation = :inner_html
|
28
|
+
|
29
|
+
# Override the parent class that the StimulusReflex ActionCable channel inherits from
|
30
|
+
#
|
26
31
|
# config.parent_channel = "ApplicationCable::Channel"
|
27
32
|
|
28
33
|
# Override the logger that the StimulusReflex uses; default is Rails' logger
|
29
34
|
# eg. Logger.new(RAILS_ROOT + "/log/reflex.log")
|
30
|
-
|
35
|
+
#
|
31
36
|
# config.logger = Rails.logger
|
32
37
|
|
33
38
|
# Customize server-side Reflex logging format, with optional colorization:
|
34
|
-
# Available tokens: session_id, session_id_full, reflex_info, operation,
|
39
|
+
# Available tokens: session_id, session_id_full, reflex_info, operation, id, id_full, mode, selector, operation_counter, connection_id, connection_id_full, timestamp
|
35
40
|
# Available colors: red, green, yellow, blue, magenta, cyan, white
|
36
41
|
# You can also use attributes from your ActionCable Connection's identifiers that resolve to valid ActiveRecord models
|
37
42
|
# eg. if your connection is `identified_by :current_user` and your User model has an email attribute, you can access r.email (it will display `-` if the user isn't logged in)
|
38
43
|
# Learn more at: https://docs.stimulusreflex.com/appendices/troubleshooting#stimulusreflex-logging
|
39
|
-
|
44
|
+
#
|
40
45
|
# config.logging = proc { "[#{session_id}] #{operation_counter.magenta} #{reflex_info.green} -> #{selector.cyan} via #{mode} Morph (#{operation.yellow})" }
|
41
46
|
|
42
47
|
# Optimized for speed, StimulusReflex doesn't enable Rack middleware by default.
|
43
48
|
# If you are using Page Morphs and your app uses Rack middleware to rewrite part of the request path, you must enable those middleware modules in StimulusReflex.
|
44
49
|
#
|
45
50
|
# Learn more about registering Rack middleware in Rails here: https://guides.rubyonrails.org/rails_on_rack.html#configuring-middleware-stack
|
46
|
-
|
51
|
+
#
|
47
52
|
# config.middleware.use FirstRackMiddleware
|
48
53
|
# config.middleware.use SecondRackMiddleware
|
49
54
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
// Esbuild is configured with 3 modes:
|
4
|
+
//
|
5
|
+
// `yarn build` - Build JavaScript and exit
|
6
|
+
// `yarn build --watch` - Rebuild JavaScript on change
|
7
|
+
// `yarn build --reload` - Reloads page when views, JavaScript, or stylesheets change
|
8
|
+
//
|
9
|
+
// Minify is enabled when "RAILS_ENV=production"
|
10
|
+
// Sourcemaps are enabled in non-production environments
|
11
|
+
|
12
|
+
import * as esbuild from "esbuild"
|
13
|
+
import path from "path"
|
14
|
+
import rails from "esbuild-rails"
|
15
|
+
import chokidar from "chokidar"
|
16
|
+
import http from "http"
|
17
|
+
import { setTimeout } from "timers/promises"
|
18
|
+
|
19
|
+
const clients = []
|
20
|
+
|
21
|
+
const entryPoints = [
|
22
|
+
"application.js"
|
23
|
+
]
|
24
|
+
|
25
|
+
const watchDirectories = [
|
26
|
+
"./app/javascript/**/*.js",
|
27
|
+
"./app/views/**/*.html.erb",
|
28
|
+
"./app/assets/builds/**/*.css", // Wait for cssbundling changes
|
29
|
+
]
|
30
|
+
|
31
|
+
const config = {
|
32
|
+
absWorkingDir: path.join(process.cwd(), "app/javascript"),
|
33
|
+
bundle: true,
|
34
|
+
entryPoints: entryPoints,
|
35
|
+
minify: process.env.RAILS_ENV == "production",
|
36
|
+
outdir: path.join(process.cwd(), "app/assets/builds"),
|
37
|
+
plugins: [rails()],
|
38
|
+
sourcemap: process.env.RAILS_ENV != "production"
|
39
|
+
}
|
40
|
+
|
41
|
+
async function buildAndReload() {
|
42
|
+
// Foreman & Overmind assign a separate PORT for each process
|
43
|
+
const port = parseInt(process.env.PORT)
|
44
|
+
const context = await esbuild.context({
|
45
|
+
...config,
|
46
|
+
banner: {
|
47
|
+
js: ` (() => new EventSource("http://localhost:${port}").onmessage = () => location.reload())();`,
|
48
|
+
}
|
49
|
+
})
|
50
|
+
|
51
|
+
// Reload uses an HTTP server as an even stream to reload the browser
|
52
|
+
http.createServer((req, res) => {
|
53
|
+
return clients.push(
|
54
|
+
res.writeHead(200, {
|
55
|
+
"Content-Type": "text/event-stream",
|
56
|
+
"Cache-Control": "no-cache",
|
57
|
+
"Access-Control-Allow-Origin": "*",
|
58
|
+
Connection: "keep-alive",
|
59
|
+
})
|
60
|
+
)
|
61
|
+
}).listen(port)
|
62
|
+
|
63
|
+
await context.rebuild()
|
64
|
+
console.log("[reload] initial build succeeded")
|
65
|
+
|
66
|
+
let ready = false
|
67
|
+
chokidar.watch(watchDirectories).on("ready", () => {
|
68
|
+
console.log("[reload] ready")
|
69
|
+
ready = true
|
70
|
+
}).on("all", async (event, path) => {
|
71
|
+
if (ready === false) return
|
72
|
+
|
73
|
+
if (path.includes("javascript")) {
|
74
|
+
try {
|
75
|
+
await setTimeout(20)
|
76
|
+
await context.rebuild()
|
77
|
+
console.log("[reload] build succeeded")
|
78
|
+
} catch (error) {
|
79
|
+
console.error("[reload] build failed", error)
|
80
|
+
}
|
81
|
+
}
|
82
|
+
clients.forEach((res) => res.write("data: update\n\n"))
|
83
|
+
clients.length = 0
|
84
|
+
})
|
85
|
+
}
|
86
|
+
|
87
|
+
if (process.argv.includes("--reload")) {
|
88
|
+
buildAndReload()
|
89
|
+
} else if (process.argv.includes("--watch")) {
|
90
|
+
let context = await esbuild.context({...config, logLevel: 'info'})
|
91
|
+
context.watch()
|
92
|
+
} else {
|
93
|
+
esbuild.build(config)
|
94
|
+
}
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stimulus_reflex/installer"
|
4
|
+
|
5
|
+
# verify that Action Cable is installed
|
6
|
+
if defined?(ActionCable::Engine)
|
7
|
+
say "⏩ ActionCable::Engine is already loaded and in scope. Skipping"
|
8
|
+
else
|
9
|
+
halt "ActionCable::Engine is not loaded, please add or uncomment `require \"action_cable/engine\"` to your `config/application.rb`"
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
return if pack_path_missing?
|
14
|
+
|
15
|
+
# verify that the Action Cable pubsub config is created
|
16
|
+
cable_config = Rails.root.join("config/cable.yml")
|
17
|
+
|
18
|
+
if cable_config.exist?
|
19
|
+
say "⏩ config/cable.yml is already present. Skipping."
|
20
|
+
else
|
21
|
+
inside "config" do
|
22
|
+
template "cable.yml"
|
23
|
+
end
|
24
|
+
|
25
|
+
say "✅ Created config/cable.yml"
|
26
|
+
end
|
27
|
+
|
28
|
+
# verify that the Action Cable pubsub is set to use redis in development
|
29
|
+
yaml = YAML.safe_load(cable_config.read)
|
30
|
+
app_name = Rails.application.class.module_parent.name.underscore
|
31
|
+
|
32
|
+
if yaml["development"]["adapter"] == "redis"
|
33
|
+
say "⏩ config/cable.yml is already configured to use the redis adapter in development. Skipping."
|
34
|
+
elsif yaml["development"]["adapter"] == "async"
|
35
|
+
yaml["development"] = {
|
36
|
+
"adapter" => "redis",
|
37
|
+
"url" => "<%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>",
|
38
|
+
"channel_prefix" => "#{app_name}_development"
|
39
|
+
}
|
40
|
+
backup(cable_config) do
|
41
|
+
cable_config.write(yaml.to_yaml)
|
42
|
+
end
|
43
|
+
say "✅ config/cable.yml was updated to use the redis adapter in development"
|
44
|
+
else
|
45
|
+
say "🤷 config/cable.yml should use the redis adapter - or something like it - in development. You have something else specified, and we trust that you know what you're doing."
|
46
|
+
end
|
47
|
+
|
48
|
+
if gemfile.match?(/gem ['"]redis['"]/)
|
49
|
+
say "⏩ redis gem is already present in Gemfile. Skipping."
|
50
|
+
elsif Rails::VERSION::MAJOR >= 7
|
51
|
+
add_gem "redis@~> 5"
|
52
|
+
else
|
53
|
+
add_gem "redis@~> 4"
|
54
|
+
end
|
55
|
+
|
56
|
+
# install action-cable-redis-backport gem if using Action Cable < 7.1
|
57
|
+
unless ActionCable::VERSION::MAJOR >= 7 && ActionCable::VERSION::MINOR >= 1
|
58
|
+
if gemfile.match?(/gem ['"]action-cable-redis-backport['"]/)
|
59
|
+
say "⏩ action-cable-redis-backport gem is already present in Gemfile. Skipping."
|
60
|
+
else
|
61
|
+
add_gem "action-cable-redis-backport@~> 1"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# verify that the Action Cable channels folder and consumer class is available
|
66
|
+
step_path = "/app/javascript/channels/"
|
67
|
+
channels_path = Rails.root.join(entrypoint, "channels")
|
68
|
+
consumer_src = fetch(step_path, "consumer.js.tt")
|
69
|
+
consumer_path = channels_path / "consumer.js"
|
70
|
+
index_src = fetch(step_path, "index.js.#{bundler}.tt")
|
71
|
+
index_path = channels_path / "index.js"
|
72
|
+
friendly_index_path = index_path.relative_path_from(Rails.root).to_s
|
73
|
+
|
74
|
+
empty_directory channels_path unless channels_path.exist?
|
75
|
+
|
76
|
+
copy_file(consumer_src, consumer_path) unless consumer_path.exist?
|
77
|
+
|
78
|
+
if index_path.exist?
|
79
|
+
if index_path.read == index_src.read
|
80
|
+
say "⏩ #{friendly_index_path} is already present. Skipping."
|
81
|
+
else
|
82
|
+
backup(index_path) do
|
83
|
+
copy_file(index_src, index_path, verbose: false)
|
84
|
+
end
|
85
|
+
say "✅ #{friendly_index_path} has been updated"
|
86
|
+
end
|
87
|
+
else
|
88
|
+
copy_file(index_src, index_path)
|
89
|
+
say "✅ #{friendly_index_path} has been created"
|
90
|
+
end
|
91
|
+
|
92
|
+
# import Action Cable channels into application pack
|
93
|
+
channels_pattern = /import ['"](\.\.\/|\.\/)?channels['"]/
|
94
|
+
channels_commented_pattern = /\s*\/\/\s*#{channels_pattern}/
|
95
|
+
channel_import = "import \"#{prefix}channels\"\n"
|
96
|
+
|
97
|
+
if pack.match?(channels_pattern)
|
98
|
+
if pack.match?(channels_commented_pattern)
|
99
|
+
proceed = if options.key? "uncomment"
|
100
|
+
options["uncomment"]
|
101
|
+
else
|
102
|
+
!no?("✨ Action Cable seems to be commented out in your application.js. Do you want to uncomment it? (Y/n)")
|
103
|
+
end
|
104
|
+
|
105
|
+
if proceed
|
106
|
+
# uncomment_lines only works with Ruby comments 🙄
|
107
|
+
lines = pack_path.readlines
|
108
|
+
matches = lines.select { |line| line =~ channels_commented_pattern }
|
109
|
+
lines[lines.index(matches.last).to_i] = channel_import
|
110
|
+
pack_path.write lines.join
|
111
|
+
say "✅ Uncommented channels import in #{friendly_pack_path}"
|
112
|
+
else
|
113
|
+
say "🤷 your Action Cable channels are not being imported in your application.js. We trust that you have a reason for this."
|
114
|
+
end
|
115
|
+
else
|
116
|
+
say "⏩ channels are already being imported in #{friendly_pack_path}. Skipping."
|
117
|
+
end
|
118
|
+
else
|
119
|
+
lines = pack_path.readlines
|
120
|
+
matches = lines.select { |line| line =~ /^import / }
|
121
|
+
lines.insert lines.index(matches.last).to_i + 1, channel_import
|
122
|
+
pack_path.write lines.join
|
123
|
+
say "✅ channels imported in #{friendly_pack_path}"
|
124
|
+
end
|
125
|
+
|
126
|
+
# create working copy of Action Cable initializer in tmp
|
127
|
+
if action_cable_initializer_path.exist?
|
128
|
+
FileUtils.cp(action_cable_initializer_path, action_cable_initializer_working_path)
|
129
|
+
|
130
|
+
say "⏩ Action Cable initializer already exists. Skipping"
|
131
|
+
else
|
132
|
+
# create Action Cable initializer if it doesn't already exist
|
133
|
+
create_file(action_cable_initializer_working_path, verbose: false) do
|
134
|
+
<<~RUBY
|
135
|
+
# frozen_string_literal: true
|
136
|
+
|
137
|
+
RUBY
|
138
|
+
end
|
139
|
+
say "✅ Action Cable initializer created"
|
140
|
+
end
|
141
|
+
|
142
|
+
# silence notoriously chatty Action Cable logs
|
143
|
+
if action_cable_initializer_working_path.read.match?(/^[^#]*ActionCable.server.config.logger/)
|
144
|
+
say "⏩ Action Cable logger is already being silenced. Skipping"
|
145
|
+
else
|
146
|
+
append_file(action_cable_initializer_working_path, verbose: false) do
|
147
|
+
<<~RUBY
|
148
|
+
ActionCable.server.config.logger = Logger.new(nil)
|
149
|
+
|
150
|
+
RUBY
|
151
|
+
end
|
152
|
+
say "✅ Action Cable logger silenced for performance and legibility"
|
153
|
+
end
|
154
|
+
|
155
|
+
complete_step :action_cable
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "stimulus_reflex/installer"
|
4
|
+
|
5
|
+
def needs_broadcaster?(path)
|
6
|
+
return false unless path.exist?
|
7
|
+
|
8
|
+
!path.readlines.index { |line| line =~ /^\s*include CableReady::Broadcaster/ }
|
9
|
+
end
|
10
|
+
|
11
|
+
channel_path = Rails.root.join("app/channels/application_cable/channel.rb")
|
12
|
+
controller_path = Rails.root.join("app/controllers/application_controller.rb")
|
13
|
+
job_path = Rails.root.join("app/jobs/application_job.rb")
|
14
|
+
model_path = Rails.root.join(application_record_path)
|
15
|
+
|
16
|
+
include_in_channel = needs_broadcaster?(channel_path)
|
17
|
+
include_in_controller = needs_broadcaster?(controller_path)
|
18
|
+
include_in_job = needs_broadcaster?(job_path)
|
19
|
+
include_in_model = needs_broadcaster?(model_path)
|
20
|
+
|
21
|
+
proceed = [include_in_channel, include_in_controller, include_in_job, include_in_model].reduce(:|)
|
22
|
+
|
23
|
+
unless proceed
|
24
|
+
complete_step :broadcaster
|
25
|
+
|
26
|
+
puts "⏩ CableReady::Broadcaster already included in all files. Skipping."
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
proceed = if options.key? "broadcaster"
|
31
|
+
options["broadcaster"]
|
32
|
+
else
|
33
|
+
!no?("✨ Make CableReady::Broadcaster available to channels, controllers, jobs and models? (Y/n)")
|
34
|
+
end
|
35
|
+
|
36
|
+
unless proceed
|
37
|
+
complete_step :broadcaster
|
38
|
+
|
39
|
+
puts "⏩ Skipping."
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
broadcaster_include = "\n include CableReady::Broadcaster\n"
|
44
|
+
|
45
|
+
# include CableReady::Broadcaster in Action Cable Channel classes
|
46
|
+
if include_in_channel
|
47
|
+
backup(channel_path) do
|
48
|
+
inject_into_file channel_path, broadcaster_include, after: /class (ApplicationCable::)?Channel < ActionCable::Channel::Base/, verbose: false
|
49
|
+
end
|
50
|
+
|
51
|
+
puts "✅ include CableReady::Broadcaster in ApplicationCable::Channel"
|
52
|
+
else
|
53
|
+
puts "⏩ Not including CableReady::Broadcaster in ApplicationCable::Channel channels. Skipping."
|
54
|
+
end
|
55
|
+
|
56
|
+
# include CableReady::Broadcaster in Action Controller classes
|
57
|
+
if include_in_controller
|
58
|
+
backup(controller_path) do
|
59
|
+
inject_into_class controller_path, "ApplicationController", broadcaster_include, verbose: false
|
60
|
+
end
|
61
|
+
|
62
|
+
puts "✅ include CableReady::Broadcaster in ApplicationController"
|
63
|
+
else
|
64
|
+
puts "⏩ Not including CableReady::Broadcaster in ApplicationController. Skipping."
|
65
|
+
end
|
66
|
+
|
67
|
+
# include CableReady::Broadcaster in Active Job classes, if present
|
68
|
+
|
69
|
+
if include_in_job
|
70
|
+
backup(job_path) do
|
71
|
+
inject_into_class job_path, "ApplicationJob", broadcaster_include, verbose: false
|
72
|
+
end
|
73
|
+
|
74
|
+
puts "✅ include CableReady::Broadcaster in ApplicationJob"
|
75
|
+
else
|
76
|
+
puts "⏩ Not including CableReady::Broadcaster in ApplicationJob. Skipping."
|
77
|
+
end
|
78
|
+
|
79
|
+
# include CableReady::Broadcaster in Active Record model classes
|
80
|
+
if include_in_model
|
81
|
+
backup(application_record_path) do
|
82
|
+
inject_into_class application_record_path, "ApplicationRecord", broadcaster_include, verbose: false
|
83
|
+
end
|
84
|
+
|
85
|
+
puts "✅ include CableReady::Broadcaster in ApplicationRecord"
|
86
|
+
else
|
87
|
+
puts "⏩ Not including CableReady::Broadcaster in ApplicationRecord. Skipping"
|
88
|
+
end
|
89
|
+
|
90
|
+
complete_step :broadcaster
|