isorun 0.1.0.pre-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8cc68f577d99bfd8223095b62ee554d7feb224e7509f87c2941c1c6a26dc437e
4
+ data.tar.gz: dcd6232870f55c04db9cc2caa754bf92d4aa84057182f73a95e0613f7f467657
5
+ SHA512:
6
+ metadata.gz: 6f027689bc2ed2be4769cc4df7e9d2adf794f9e3e62546ee6678e57a2608682ceddf36f19ba2ea8dc6a4a4e0ddfd6525427921ad9482a7feb0b409d66c2cd451
7
+ data.tar.gz: 83613b98a938a5abd2113ed12dad879216ccadadb2b012d7f2cead5a68b1a79dba0c6b4832c75d5da26571de60585ce114147937028a6b0beca99d14b632ad6e
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Hannes Moser
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,257 @@
1
+ <p align="center">
2
+ <img alt="isorun" src="./docs/assets/logo.png" width="200" />
3
+ </p>
4
+
5
+ ---
6
+
7
+ > Run JavaScript applications in your Rails application.
8
+
9
+ ## Features
10
+
11
+ * Import JavaScript functions, objects, or just values and use them in Ruby
12
+ * An EMCAScript like Ruby DSL to load modules and import items
13
+ * Automatically converts arguments and return values
14
+ * Send messages between *JavaScript*<->*Ruby* (allows to intercept network requests and avoid network round-trips for e.g. API calls)
15
+ * Automatically reload modules when updated in development
16
+ * Automatically extracts state (Apollo) and hydrates client-side
17
+ * Supports server-side rendering of multiple apps on a single page
18
+ * Examples for [React](./examples/rails-react-app), [Vue](./examples/rails-vue-app), [D3](./examples/rails-d3-app) and a [multi-app](./examples/rails-multi-app) setup
19
+
20
+ ## How to
21
+
22
+ ### Plain JavaScript
23
+
24
+ ```js
25
+ // module.js
26
+ export function say(word) {
27
+ return word;
28
+ }
29
+ ```
30
+
31
+ ```ruby
32
+ context = Isorun::Context.new
33
+
34
+ # import `export function say` from module
35
+ say = context.import(:say).from("./module.js")
36
+ say.call("Hello!") # "Hello!"
37
+ ```
38
+
39
+ ### Simple React app
40
+
41
+ ```bash
42
+ rails new myproject --javascript esbuild
43
+ cd myproject
44
+ ```
45
+
46
+ ```js
47
+ // package.json
48
+ {
49
+ "scripts": {
50
+ "build": "esbuild app/javascript/app.jsx --bundle --sourcemap --outdir=app/assets/builds --public-path=assets",
51
+ "build-server": "esbuild app/javascript/app-server.jsx --bundle --sourcemap --outdir=app/assets/builds --public-path=assets --format=esm"
52
+ }
53
+ }
54
+ ```
55
+
56
+ ```bash
57
+ # Procfile.dev
58
+ web: bin/rails server -p 3000
59
+ js: yarn build --watch
60
+ ssr: yarn build-server --watch
61
+ ```
62
+
63
+ ```ruby
64
+ # config/initializers/isorun.rb
65
+ Isorun.configure do
66
+ # …configure isorun
67
+ end
68
+ ```
69
+
70
+ ```jsx
71
+ // app/javascript/my_app.jsx
72
+ import * as React from "react";
73
+ import {hydrateRoot} from "react-dom/client";
74
+
75
+ import {App} from "./my_app/App.jsx";
76
+
77
+ const container = document.querySelector('#my_app');
78
+ hydrateRoot(container, <App/>);
79
+
80
+ ```
81
+
82
+ ```jsx
83
+ // app/javascript/my_app-server.jsx
84
+ import * as React from "react";
85
+ import * as Server from "react-dom/server";
86
+
87
+ import {App} from "./my_app/App.jsx";
88
+
89
+ export default async function() {
90
+ return Server.renderToString(<App/>);
91
+ }
92
+ ```
93
+
94
+ ```erb
95
+ <!--my_view.html.erb-->
96
+ <%= isorun_app("my_app") %>
97
+ ```
98
+
99
+ ## Ruby and platform support
100
+
101
+ Ruby versions:
102
+ - `2.7`
103
+ - `3.0`
104
+ - `3.1`.
105
+
106
+ Platforms and architectures:
107
+ - `x86_64-linux`
108
+ - `x86_64-apple`
109
+ - `arm64-apple`
110
+
111
+ ## Demo
112
+
113
+ You can also check out this demo video on YouTube. It shows how you can utilize
114
+ *isorun* to render SVGs with Ruby on the server, utilizing JavaScript and the
115
+ D3 library.
116
+
117
+ [![How to use d3 in Ruby](./docs/assets/how-to-use-d3-in-ruby.png)](https://www.youtube.com/watch?v=EPHX4po4X4g)
118
+
119
+ ## Why server-side rendering (SSR)?
120
+
121
+ The fastest way to deliver an application to the user is streaming HTML directly
122
+ to the browser. The slowest way to deliver an application, is downloading a
123
+ JavaScript file first, parse and execute it on the client side.
124
+
125
+ Server-side rendering is taking advantage of the fact that we can render a
126
+ JavaScript application directly on the server, and stream the resulting HTML
127
+ directly to the browser.
128
+ Then we fetch the JavaScript file and eventually the application will
129
+ (re-)hydrate the already rendered user interface.
130
+
131
+ You can take this concept even further and make your application work without
132
+ JavaScript at all, but still use React or Vue (or any other view-controller
133
+ library) to define your user interface.
134
+
135
+ Read
136
+ more: [Netflix functions without client-side React, and it's a good thing](https://jakearchibald.com/2017/netflix-and-react/).
137
+
138
+ Server-side rendering has a few challenges:
139
+
140
+ 1. You need something that can compile and run JavaScript
141
+ 1. You need to be able to integrate the app with your preferred framework
142
+ 1. You need to deal with the reality of frontend clients making network requests and managing state
143
+
144
+ **isorun** aims to make it as simple as possible to integrate a
145
+ JavaScript application into your server-side development and deployment
146
+ workflow, without changing the development workflow for frontend engineers.
147
+
148
+ This gem provides a helper that can render a JavaScript application directly in
149
+ your Ruby process, embedding Google's *v8* library via [*deno_core*](https://crates.io/crates/deno_core).
150
+ You can think of it as running a headless JavaScript browser directly in your
151
+ Ruby process (threads). Using *v8* allows us to completely separate the
152
+ execution environments between individual renders and therefore prevent any
153
+ potential [Cross-Request State Pollution](https://vuejs.org/guide/scaling-up/ssr.html#cross-request-state-pollution).
154
+ It is essentiallly the same as opening many tabs in one browser.
155
+
156
+ ## Why SSR for Ruby (on Rails)?
157
+
158
+ I personally enjoy and use *Ruby on Rails* a lot, but I like to use some
159
+ Vue and React for frontend work. The integration of frontend and backend always
160
+ felt a bit off, and I wanted something that "just works" for most of my use
161
+ cases.
162
+
163
+ One goal of **isorun** is that server-side rendering should feel naturally in
164
+ Ruby and Rails. A simple tag helper should be enough to render, deliver, and
165
+ hydrate your complex JavaScript application. And if we want to do something
166
+ nice with visualization libraries, it should be possible to run any JavaScript
167
+ program and return the result to the user without spinning up a separate
168
+ service.
169
+
170
+ ### Alternatives
171
+
172
+ #### "No" JavaScript
173
+
174
+ If you want to go all-in on the server side, I highly recommend taking a look at
175
+ [HTML over the Wire](https://hotwired.dev/), and [StimulusReflex](https://docs.stimulusreflex.com/).
176
+
177
+ #### Run a Node.js, deno, or bun service
178
+
179
+ **isorun** does SSR a bit different from how you would do it in a regular
180
+ Node.js service. In addition to being able to render the application, it also
181
+ supports more powerful features like network intercepts. This means, that you
182
+ can directly call into the Ruby process from the JavaScript application and
183
+ e.g. fetch data from the database. This is helpful for applications that
184
+ utilize APIs to fetch their data.
185
+ Even when server-side rendered, these applications issue network requests
186
+ against the production API endpoints to get access to data. In a lot of cases,
187
+ we can accelerate this process by forwarding the network requests directly to
188
+ the target controller/action in Rails.Instead of fetching
189
+
190
+ **Example** A React applications queries a Rails GraphQL API
191
+
192
+ We can override the HttpLink `fetch` method and utilize the `@isorun/rails`
193
+ package to send the HTTP request for the GraphQL API directly to the Ruby
194
+ process, instead of sending it over the network.
195
+
196
+ ```js
197
+ import {apollo} from "@isorun/rails";
198
+
199
+ import {App} from "../my_app/App.jsx";
200
+
201
+ const apolloClient = new ApolloClient({
202
+ ssrMode: true,
203
+ cache: new InMemoryCache(),
204
+ link: new HttpLink({
205
+ uri: 'http://localhost:3000/graphql',
206
+ fetch: apollo.fetch
207
+ })
208
+ });
209
+ ```
210
+
211
+ ```ruby
212
+ Isorun.configure do
213
+ receiver do |request|
214
+ query, variables, context, operation_name = parse(request)
215
+
216
+ RailsAppSchema.execute(
217
+ query,
218
+ variables: variables,
219
+ context: context,
220
+ operation_name: operation_name
221
+ )
222
+ end
223
+ end
224
+ ```
225
+
226
+ ## Installation
227
+
228
+ Install the gem and add to the application's Gemfile by executing:
229
+
230
+ $ bundle add isorun
231
+
232
+ If bundler is not being used to manage dependencies, install the gem by
233
+ executing:
234
+
235
+ $ gem install isorun
236
+
237
+ ## Development
238
+
239
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run
240
+ `rake spec` to run the tests. You can also run `bin/console` for an interactive
241
+ prompt that will allow you to experiment.
242
+
243
+ To install this gem onto your local machine, run `bundle exec rake install`. To
244
+ release a new version, update the version number in `version.rb`, and then run
245
+ `bundle exec rake release`, which will create a git tag for the version, push
246
+ git commits and the created tag, and push the `.gem` file to
247
+ [rubygems.org](https://rubygems.org).
248
+
249
+ ## Contributing
250
+
251
+ Bug reports and pull requests are welcome on GitHub at
252
+ https://github.com/eliias/isorun.
253
+
254
+ ## License
255
+
256
+ The gem is available as open source under the terms of the
257
+ [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rubygems/package_task"
5
+ require "rake/testtask"
6
+ require "rake/extensiontask"
7
+ require "rb_sys"
8
+
9
+ cross_rubies = %w[3.1.0 3.0.0 2.7.0]
10
+ cross_platforms = %w[
11
+ arm64-darwin
12
+ x86_64-darwin
13
+ x86_64-linux
14
+ ]
15
+
16
+ spec = Bundler.load_gemspec("isorun.gemspec")
17
+
18
+ Gem::PackageTask.new(spec).define
19
+
20
+ Rake::ExtensionTask.new("isorun", spec) do |ext|
21
+ ext.source_pattern = "*.{rs,toml}"
22
+ ext.lib_dir = "lib/isorun"
23
+ ext.cross_compile = true
24
+ ext.cross_platform = cross_platforms
25
+ ext.config_script = ENV["ALTERNATE_CONFIG_SCRIPT"] || "extconf.rb"
26
+ ext.cross_compiling do |c|
27
+ c.files.reject! { |file| File.fnmatch?("*.tar.gz", file) }
28
+ c.dependencies.reject! { |dep| dep.name == "rb-sys" }
29
+ end
30
+ end
31
+
32
+ namespace "gem" do
33
+ task "prepare" do # rubocop:disable Rails/RakeEnvironment
34
+ sh "bundle"
35
+ end
36
+
37
+ cross_platforms.each do |plat|
38
+ desc "Build the native gem for #{plat}"
39
+ task plat => "prepare" do
40
+ require "rake_compiler_dock"
41
+
42
+ ENV["RCD_IMAGE"] = "rbsys/#{plat}:#{RbSys::VERSION}"
43
+
44
+ RakeCompilerDock.sh <<~SH, platform: plat
45
+ bundle && \
46
+ RUBY_CC_VERSION="#{cross_rubies.join(":")}" \
47
+ rake native:#{plat} pkg/#{spec.full_name}-#{plat}.gem
48
+ SH
49
+ end
50
+ end
51
+ end
52
+
53
+ begin
54
+ require "rspec/core/rake_task"
55
+ RSpec::Core::RakeTask.new(:spec, [] => [:compile])
56
+ task test: :spec
57
+ task default: %i[test]
58
+ rescue LoadError
59
+ # Ok
60
+ end
61
+
62
+ begin
63
+ require "rubocop/rake_task"
64
+
65
+ RuboCop::RakeTask.new
66
+ rescue LoadError
67
+ # Ok
68
+ end
69
+
70
+ begin
71
+ require "yard"
72
+
73
+ YARD::Rake::YardocTask.new
74
+
75
+ task docs: :environment do
76
+ `yard server --reload`
77
+ end
78
+ rescue LoadError
79
+ # Ok
80
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Isorun
4
+ module AppHelper
5
+ def isorun_app(id) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
6
+ module_path = Isorun.configuration.module_resolver.call(id)
7
+
8
+ ssr_html = Isorun::Context.create do |context|
9
+ render_context = { environment: Rails.env.to_s }
10
+ render_function = context.import.from(module_path)
11
+
12
+ if render_function.blank?
13
+ Rails.logger.warn("[ISORUN] the requested app does not exist or " \
14
+ "does not have a server entrypoint. Please " \
15
+ "check if an asset with filename " + "
16
+ `#{id}-server.js` exists.")
17
+ end
18
+
19
+ Isorun.with_receiver(Isorun.configuration.receiver) do
20
+ render_function.call_without_gvl(render_context)
21
+ end
22
+ end
23
+
24
+ html = if ssr_html.present?
25
+ tag.div id: id do
26
+ ssr_html.html_safe # rubocop:disable Rails/OutputSafety
27
+ end
28
+ else
29
+ Rails.logger.warn("[ISORUN] The server-side rendered result is empty.")
30
+ ""
31
+ end
32
+
33
+ html += "\n"
34
+ html += javascript_include_tag(id, defer: true)
35
+ html.html_safe # rubocop:disable Rails/OutputSafety
36
+ end
37
+ end
38
+ end