isorun 0.1.0.pre-arm64-darwin

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f40fd29fe6e6af7cea7300d59bfbc8a952e5057134f8572085fbb0d5b82714ea
4
+ data.tar.gz: 24a68c47f8da2a0b3bb1500bc7f9df0c52d3ec0593518dbccb1d78cc8ce8df34
5
+ SHA512:
6
+ metadata.gz: 91cf98f13188aeb1dd2d702d44eb584e5d28ae21533c500387fe5348d9d4646b28f4eb6e8b7479968f1d944a911ed2b601d11a58eae9ba009f8a5064739d0add
7
+ data.tar.gz: ff48f42eee5c832796ee7da6a0a2928dbcdafa168b7a199cae2ffea26100da617c92fd3ecc6c435c73e6c49707b61990db6aa5291d14e83f775584b2e977b08d
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