wasmify-rails 0.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 +7 -0
- data/CHANGELOG.md +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +168 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/checkpoint.rb +13 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/column.rb +4 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/configuration.rb +5 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/core.rb +391 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/empty_result.rb +41 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/index_definition.rb +5 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/null_object.rb +13 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/quoting.rb +3 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/statement.rb +15 -0
- data/lib/active_record/connection_adapters/nulldb_adapter/table_definition.rb +23 -0
- data/lib/active_record/connection_adapters/nulldb_adapter.rb +67 -0
- data/lib/active_record/connection_adapters/sqlite3_wasm_adapter.rb +163 -0
- data/lib/active_storage/null_delivery.rb +15 -0
- data/lib/generators/wasmify/install/USAGE +5 -0
- data/lib/generators/wasmify/install/install_generator.rb +73 -0
- data/lib/generators/wasmify/install/templates/config/environments/wasm.rb +30 -0
- data/lib/generators/wasmify/install/templates/config/wasmify.yml +17 -0
- data/lib/generators/wasmify/pwa/USAGE +5 -0
- data/lib/generators/wasmify/pwa/pwa_generator.rb +13 -0
- data/lib/generators/wasmify/pwa/templates/pwa/README.md +40 -0
- data/lib/generators/wasmify/pwa/templates/pwa/boot.html +102 -0
- data/lib/generators/wasmify/pwa/templates/pwa/boot.js +96 -0
- data/lib/generators/wasmify/pwa/templates/pwa/database.js +18 -0
- data/lib/generators/wasmify/pwa/templates/pwa/index.html +2 -0
- data/lib/generators/wasmify/pwa/templates/pwa/package.json.tt +20 -0
- data/lib/generators/wasmify/pwa/templates/pwa/rails.sw.js +115 -0
- data/lib/generators/wasmify/pwa/templates/pwa/vite.config.js +29 -0
- data/lib/image_processing/null.rb +22 -0
- data/lib/wasmify/rails/builder.rb +45 -0
- data/lib/wasmify/rails/configuration.rb +41 -0
- data/lib/wasmify/rails/packer.rb +60 -0
- data/lib/wasmify/rails/railtie.rb +11 -0
- data/lib/wasmify/rails/shim.rb +65 -0
- data/lib/wasmify/rails/shims/io/console/size.rb +0 -0
- data/lib/wasmify/rails/shims/io/console.rb +11 -0
- data/lib/wasmify/rails/shims/io/wait.rb +0 -0
- data/lib/wasmify/rails/shims/ipaddr.rb +7 -0
- data/lib/wasmify/rails/shims/nio.rb +0 -0
- data/lib/wasmify/rails/shims/nokogiri/nokogiri.rb +0 -0
- data/lib/wasmify/rails/shims/nokogiri.rb +0 -0
- data/lib/wasmify/rails/shims/rails-html-sanitizer.rb +163 -0
- data/lib/wasmify/rails/shims/socket.rb +21 -0
- data/lib/wasmify/rails/shims/sqlite3.rb +0 -0
- data/lib/wasmify/rails/tasks.rake +120 -0
- data/lib/wasmify/rails/version.rb +7 -0
- data/lib/wasmify/rails/wasmtimer.rb +31 -0
- data/lib/wasmify-rails.rb +25 -0
- metadata +200 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class ActiveRecord::ConnectionAdapters::NullDBAdapter
|
|
2
|
+
|
|
3
|
+
class Statement
|
|
4
|
+
attr_reader :entry_point, :content
|
|
5
|
+
|
|
6
|
+
def initialize(entry_point, content = "")
|
|
7
|
+
@entry_point, @content = entry_point, content
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def ==(other)
|
|
11
|
+
self.entry_point == other.entry_point
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
class ActiveRecord::ConnectionAdapters::NullDBAdapter
|
|
2
|
+
|
|
3
|
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
|
|
4
|
+
attr_accessor :name
|
|
5
|
+
alias_method :enum, :string
|
|
6
|
+
alias_method :uuid, :string
|
|
7
|
+
alias_method :citext, :text
|
|
8
|
+
alias_method :interval, :text
|
|
9
|
+
alias_method :geometry, :text
|
|
10
|
+
alias_method :serial, :integer
|
|
11
|
+
alias_method :bigserial, :integer
|
|
12
|
+
alias_method :inet, :string
|
|
13
|
+
alias_method :jsonb, :json if method_defined? :json
|
|
14
|
+
alias_method :hstore, :json
|
|
15
|
+
|
|
16
|
+
if ::ActiveRecord::VERSION::MAJOR == 7 && ::ActiveRecord::VERSION::MINOR >= 1
|
|
17
|
+
# Avoid check for option validity
|
|
18
|
+
def create_column_definition(name, type, options)
|
|
19
|
+
ActiveRecord::ConnectionAdapters::ColumnDefinition.new(name, type, options)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'logger'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
require 'singleton'
|
|
6
|
+
require 'pathname'
|
|
7
|
+
|
|
8
|
+
require 'active_support'
|
|
9
|
+
require 'active_support/deprecation'
|
|
10
|
+
require 'active_record/connection_adapters/nulldb_adapter'
|
|
11
|
+
|
|
12
|
+
module NullDB
|
|
13
|
+
class Configuration < Struct.new(:project_root); end
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def configure
|
|
17
|
+
@configuration = Configuration.new.tap {|c| yield c}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configuration
|
|
21
|
+
if @configuration.nil?
|
|
22
|
+
raise "NullDB not configured. Require a framework, ex 'nulldb/rails'"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@configuration
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def nullify(options={})
|
|
29
|
+
begin
|
|
30
|
+
@prev_connection = ActiveRecord::Base.connection_pool.try(:spec)
|
|
31
|
+
rescue ActiveRecord::ConnectionNotEstablished
|
|
32
|
+
end
|
|
33
|
+
ActiveRecord::Base.establish_connection(options.merge(:adapter => :nulldb))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def restore
|
|
37
|
+
if @prev_connection
|
|
38
|
+
ActiveRecord::Base.establish_connection(@prev_connection.config)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def checkpoint
|
|
43
|
+
ActiveRecord::Base.connection.checkpoint!
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Need to defer calling Rails.root because when bundler loads, Rails.root is nil
|
|
49
|
+
NullDB.configure {|ndb| def ndb.project_root;Rails.root;end}
|
|
50
|
+
|
|
51
|
+
class ActiveRecord::Base
|
|
52
|
+
# Instantiate a new NullDB connection. Used by ActiveRecord internally.
|
|
53
|
+
def self.nulldb_connection(config)
|
|
54
|
+
ActiveRecord::ConnectionAdapters::NullDBAdapter.new(config)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
require 'active_record/connection_adapters/nulldb_adapter/core'
|
|
59
|
+
require 'active_record/connection_adapters/nulldb_adapter/statement'
|
|
60
|
+
require 'active_record/connection_adapters/nulldb_adapter/checkpoint'
|
|
61
|
+
require 'active_record/connection_adapters/nulldb_adapter/column'
|
|
62
|
+
require 'active_record/connection_adapters/nulldb_adapter/configuration'
|
|
63
|
+
require 'active_record/connection_adapters/nulldb_adapter/empty_result'
|
|
64
|
+
require 'active_record/connection_adapters/nulldb_adapter/index_definition'
|
|
65
|
+
require 'active_record/connection_adapters/nulldb_adapter/null_object'
|
|
66
|
+
require 'active_record/connection_adapters/nulldb_adapter/table_definition'
|
|
67
|
+
require 'active_record/connection_adapters/nulldb_adapter/quoting'
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
require "active_record/connection_adapters/sqlite3_adapter"
|
|
2
|
+
|
|
3
|
+
module SQLite3
|
|
4
|
+
module Database
|
|
5
|
+
def self.quote(s) = s.gsub("'", "''")
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
module Pragmas
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ActiveRecord
|
|
13
|
+
module ConnectionHandling # :nodoc:
|
|
14
|
+
def sqlite3_wasm_adapter_class
|
|
15
|
+
ConnectionAdapters::SQLite3WasmAdapter
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def sqlite3_wasm_connection(config)
|
|
19
|
+
sqlite3_wasm_adapter_class.new(config)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
module ConnectionAdapters
|
|
24
|
+
class SQLite3WasmAdapter < SQLite3Adapter
|
|
25
|
+
class ExternalInterface
|
|
26
|
+
private attr_reader :js_interface
|
|
27
|
+
|
|
28
|
+
def initialize(config)
|
|
29
|
+
@js_interface = config.fetch(:js_interface, "sqlite4rails").to_sym
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def exec(...)
|
|
33
|
+
JS.global[js_interface].exec(...)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def busy_timeout(...) = nil
|
|
37
|
+
|
|
38
|
+
def execute(sql)
|
|
39
|
+
@last_statement = Statement.new(self, sql)
|
|
40
|
+
@last_statement.result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def prepare(sql) = Statement.new(self, sql)
|
|
44
|
+
|
|
45
|
+
def closed? = false
|
|
46
|
+
|
|
47
|
+
def transaction(mode = nil)
|
|
48
|
+
# deferred doesn't work 🤷♂️
|
|
49
|
+
mode = nil if mode.nil?
|
|
50
|
+
execute "begin #{mode} transaction"
|
|
51
|
+
|
|
52
|
+
if block_given?
|
|
53
|
+
abort = false
|
|
54
|
+
begin
|
|
55
|
+
yield self
|
|
56
|
+
rescue
|
|
57
|
+
abort = true
|
|
58
|
+
raise
|
|
59
|
+
ensure
|
|
60
|
+
abort and rollback or commit
|
|
61
|
+
end
|
|
62
|
+
else
|
|
63
|
+
true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def commit
|
|
68
|
+
execute "commit transaction"
|
|
69
|
+
true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def rollback
|
|
73
|
+
execute "rollback transaction"
|
|
74
|
+
true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def changes
|
|
78
|
+
JS.global[js_interface].changes.to_i
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class Statement
|
|
83
|
+
attr_reader :interface, :sql
|
|
84
|
+
|
|
85
|
+
def initialize(interface, sql)
|
|
86
|
+
@interface = interface
|
|
87
|
+
@sql = sql
|
|
88
|
+
@columns = nil
|
|
89
|
+
@rows = nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def close = nil
|
|
93
|
+
|
|
94
|
+
def columns
|
|
95
|
+
execute
|
|
96
|
+
@columns
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def result
|
|
100
|
+
execute
|
|
101
|
+
@rows.map { @columns.zip(_1).to_h }.freeze
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def to_a
|
|
105
|
+
execute
|
|
106
|
+
@rows
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def execute
|
|
110
|
+
return if @rows
|
|
111
|
+
|
|
112
|
+
res = interface.exec(sql)
|
|
113
|
+
# unwrap column names
|
|
114
|
+
cols = res[:cols].to_a.map(&:to_s)
|
|
115
|
+
# unwrap row values
|
|
116
|
+
rows = res[:rows].to_a.map do |row|
|
|
117
|
+
row.to_a.map do |val|
|
|
118
|
+
str_val = val.to_s
|
|
119
|
+
next str_val if val.typeof == "string"
|
|
120
|
+
next str_val == "true" if val.typeof == "boolean"
|
|
121
|
+
|
|
122
|
+
# handle integers and floats
|
|
123
|
+
next str_val.include?(".") ? val.to_f : val.to_i if val.typeof == "number"
|
|
124
|
+
|
|
125
|
+
str_val
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
@columns = cols
|
|
130
|
+
@rows = rows
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def reset!
|
|
134
|
+
@rows = nil
|
|
135
|
+
@columns = nil
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class << self
|
|
140
|
+
def database_exists?(config)
|
|
141
|
+
true
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def new_client(config) = ExternalInterface.new(config)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
attr_reader :external_interface
|
|
148
|
+
|
|
149
|
+
def initialize(...)
|
|
150
|
+
AbstractAdapter.instance_method(:initialize).bind_call(self, ...)
|
|
151
|
+
|
|
152
|
+
@prepared_statements = false
|
|
153
|
+
@memory_database = false
|
|
154
|
+
@connection_parameters = @config.merge(database: @config[:database].to_s, results_as_hash: true)
|
|
155
|
+
@use_insert_returning = @config.key?(:insert_returning) ? self.class.type_cast_config_to_boolean(@config[:insert_returning]) : true
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def database_exists? = true
|
|
159
|
+
|
|
160
|
+
def database_version = SQLite3Adapter::Version.new("3.45.1")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Wasmify::InstallGenerator < Rails::Generators::Base
|
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
|
5
|
+
|
|
6
|
+
def copy_files
|
|
7
|
+
template "config/wasmify.yml"
|
|
8
|
+
template "config/environments/wasm.rb"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def configure_database
|
|
12
|
+
append_to_file "config/database.yml", <<~EOF
|
|
13
|
+
wasm:
|
|
14
|
+
adapter: <%= ENV.fetch("ACTIVE_RECORD_ADAPTER") { "nulldb" } %>
|
|
15
|
+
EOF
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configure_action_cable
|
|
19
|
+
in_root do
|
|
20
|
+
next unless File.file?("config/cable.yml")
|
|
21
|
+
|
|
22
|
+
append_to_file "config/cable.yml", <<~EOF
|
|
23
|
+
|
|
24
|
+
wasm:
|
|
25
|
+
adapter: <%= ENV.fetch("ACTION_CABLE_ADAPTER") { "inline" } %>
|
|
26
|
+
EOF
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def configure_gitignore
|
|
31
|
+
append_to_file ".gitignore", <<~EOF
|
|
32
|
+
# Ignore the compiled WebAssembly modules
|
|
33
|
+
*.wasm
|
|
34
|
+
# Ignore ruby.wasm build artefacts
|
|
35
|
+
build/
|
|
36
|
+
rubies/
|
|
37
|
+
dist/
|
|
38
|
+
EOF
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inject_wasmify_shim_into_environment
|
|
42
|
+
inject_into_file "config/application.rb", after: /require_relative\s+['"]boot['"]\n/ do
|
|
43
|
+
<<~RUBY
|
|
44
|
+
|
|
45
|
+
require "wasmify/rails/shim"
|
|
46
|
+
RUBY
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def skip_bundler_setup_in_boot
|
|
51
|
+
inject_into_file "config/boot.rb", after: /require ['"]bundler\/setup['"]/ do
|
|
52
|
+
" unless RUBY_PLATFORM =~ /wasm/"
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def add_tzinfo_data_to_gemfile
|
|
57
|
+
append_to_file "Gemfile", <<~RUBY
|
|
58
|
+
|
|
59
|
+
group :wasm do
|
|
60
|
+
gem "tzinfo-data"
|
|
61
|
+
end
|
|
62
|
+
RUBY
|
|
63
|
+
|
|
64
|
+
run "bundle install"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def finish
|
|
68
|
+
say_status :info, "✅ The application is prepared for Wasm-ificaiton!\n\n" \
|
|
69
|
+
"Next steps are:\n" \
|
|
70
|
+
" - Gemfile: Add `group: [:default, :wasm]` to the dependencies required in Wasm runtime" \
|
|
71
|
+
" - Run `bin/rails wasmify:build:core:verify` to see if the bundle compiles"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "production"
|
|
4
|
+
|
|
5
|
+
Rails.application.configure do
|
|
6
|
+
config.enable_reloading = false
|
|
7
|
+
|
|
8
|
+
config.assume_ssl = false
|
|
9
|
+
config.force_ssl = false
|
|
10
|
+
|
|
11
|
+
# FIXME: Tags are not being reset right now
|
|
12
|
+
config.log_tags = []
|
|
13
|
+
|
|
14
|
+
if ENV["DEBUG"] == "1"
|
|
15
|
+
config.consider_all_requests_local = true
|
|
16
|
+
config.action_dispatch.show_exceptions = :none
|
|
17
|
+
config.log_level = :debug
|
|
18
|
+
config.logger = Logger.new($stdout)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
config.cache_store = :memory_store
|
|
22
|
+
config.active_job.queue_adapter = :inline
|
|
23
|
+
config.action_mailer.delivery_method = :null
|
|
24
|
+
|
|
25
|
+
if config.respond_to?(:active_storage)
|
|
26
|
+
config.active_storage.variant_processor = :null
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
config.secret_key_base = "<change-me>"
|
|
30
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Specify the list of directories to be included into the final
|
|
2
|
+
# app.wasm file.
|
|
3
|
+
pack_directories:
|
|
4
|
+
- app
|
|
5
|
+
- lib
|
|
6
|
+
- config
|
|
7
|
+
- db
|
|
8
|
+
- public
|
|
9
|
+
|
|
10
|
+
# Specify the list of gems to skip during the base module compiliation.
|
|
11
|
+
# Usually, you want to specify the gems with native extensions that are
|
|
12
|
+
# not currently Wasm-compatible.
|
|
13
|
+
exclude_gems:
|
|
14
|
+
- bigdecimal
|
|
15
|
+
- nio4r
|
|
16
|
+
- io-console
|
|
17
|
+
- psych
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Wasmify::PwaGenerator < Rails::Generators::Base
|
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
|
5
|
+
|
|
6
|
+
def copy_files
|
|
7
|
+
directory "pwa", "pwa"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def wasmify_rails_version = Wasmify::Rails::VERSION
|
|
13
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# PWA app to run a Rails Wasm app
|
|
2
|
+
|
|
3
|
+
The app provides a Service Worker to serve requests through the Rails/Wasm app.
|
|
4
|
+
|
|
5
|
+
## Running locallly
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
yarn install
|
|
9
|
+
|
|
10
|
+
yarn dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Then go to [http://localhost:5173](http://localhost:5173).
|
|
14
|
+
|
|
15
|
+
> [!NOTE]
|
|
16
|
+
> Use Chrome or another browser supporting [CookieStore API](https://caniuse.com/?search=cookiestore).
|
|
17
|
+
|
|
18
|
+
## Serving via HTTPS
|
|
19
|
+
|
|
20
|
+
Although `localhost` should work fine for development, you can also run the app over `https://` for a more production-like experience (and to attach a worker to a custom domain).
|
|
21
|
+
|
|
22
|
+
For that, we recommend using [puma-dev](https://github.com/puma/puma-dev).
|
|
23
|
+
|
|
24
|
+
Install `puma-dev` and add the port 5173 to its configuration:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
echo "5173" > ~/.puma-dev/rails-wasm
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Then, run the server as follows:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
yarn dev --host 0.0.0.0
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Go to [https://rails-wasm.test](https://rails-wasm.test) to open the app.
|
|
37
|
+
|
|
38
|
+
## Credits
|
|
39
|
+
|
|
40
|
+
The launcher HTML/JS is based on the [Yuta Saito](https://github.com/kateinoigakukun)'s work on [Mastodon in the browser](https://github.com/kateinoigakukun/mastodon/tree/katei/wasmify).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="og:title" content="Rails on Wasm" />
|
|
7
|
+
<meta name="og:description" content="Rails on Wasm launcher" />
|
|
8
|
+
<title>Rails on Wasm | Launch</title>
|
|
9
|
+
<script type="module" src="/boot.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
#boot-console-output {
|
|
12
|
+
background-color: #0c0c0c;
|
|
13
|
+
color: #f8f8f2;
|
|
14
|
+
font-family: monospace;
|
|
15
|
+
font-size: 0.8em;
|
|
16
|
+
padding: 1em;
|
|
17
|
+
white-space: pre-wrap;
|
|
18
|
+
min-height: 200px;
|
|
19
|
+
overflow-y: scroll;
|
|
20
|
+
}
|
|
21
|
+
#launch-button {
|
|
22
|
+
background-color: #007bff;
|
|
23
|
+
color: #fff;
|
|
24
|
+
border: none;
|
|
25
|
+
padding: 0.5em 1em;
|
|
26
|
+
font-size: 1em;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
border: 1px solid #ccc;
|
|
29
|
+
}
|
|
30
|
+
#launch-button:disabled {
|
|
31
|
+
background-color: #ccc;
|
|
32
|
+
color: #666;
|
|
33
|
+
}
|
|
34
|
+
#reboot-button {
|
|
35
|
+
background-color: #dc3545;
|
|
36
|
+
color: #fff;
|
|
37
|
+
border: none;
|
|
38
|
+
padding: 0.5em 1em;
|
|
39
|
+
font-size: 1em;
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
border: 1px solid #ccc;
|
|
42
|
+
}
|
|
43
|
+
#reboot-button:disabled {
|
|
44
|
+
background-color: #ccc;
|
|
45
|
+
color: #666;
|
|
46
|
+
}
|
|
47
|
+
#reload-button {
|
|
48
|
+
background-color: #eeeeee;
|
|
49
|
+
color: #333;
|
|
50
|
+
border: none;
|
|
51
|
+
padding: 0.5em 1em;
|
|
52
|
+
font-size: 1em;
|
|
53
|
+
cursor: pointer;
|
|
54
|
+
border: 1px solid #333;
|
|
55
|
+
}
|
|
56
|
+
#reload-button:disabled {
|
|
57
|
+
background-color: #ccc;
|
|
58
|
+
color: #666;
|
|
59
|
+
}
|
|
60
|
+
#reload-debug-button {
|
|
61
|
+
background-color: rgb(159, 166, 40);
|
|
62
|
+
color: #000;
|
|
63
|
+
border: none;
|
|
64
|
+
padding: 0.5em 1em;
|
|
65
|
+
font-size: 1em;
|
|
66
|
+
cursor: pointer;
|
|
67
|
+
border: 1px solid #000;
|
|
68
|
+
}
|
|
69
|
+
#reload-debug-button:disabled {
|
|
70
|
+
background-color: #ccc;
|
|
71
|
+
color: #666;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<h1>Rails on Wasm 🚀</h1>
|
|
77
|
+
<p>
|
|
78
|
+
Wait for the Service Worker to install and boot the app. This may
|
|
79
|
+
take a while.
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<p>
|
|
83
|
+
<button id="launch-button" disabled>Launch</button>
|
|
84
|
+
<button id="reload-button" disabled>Reload Rails</button>
|
|
85
|
+
<button id="reload-debug-button" disabled>
|
|
86
|
+
Reload Rails (debug mode)
|
|
87
|
+
</button>
|
|
88
|
+
<button id="reboot-button" disabled>Hard Reset*</button>
|
|
89
|
+
</p>
|
|
90
|
+
|
|
91
|
+
<div id="boot-message"></div>
|
|
92
|
+
<progress id="boot-progress" max="100" value="0"></progress>
|
|
93
|
+
<pre id="boot-console-output"></pre>
|
|
94
|
+
<hr />
|
|
95
|
+
<p>
|
|
96
|
+
<small>
|
|
97
|
+
* Please ensure you close all tabs of this app before rebooting
|
|
98
|
+
to unregister the old Service Worker.
|
|
99
|
+
</small>
|
|
100
|
+
</p>
|
|
101
|
+
</body>
|
|
102
|
+
</html>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
async function registerServiceWorker() {
|
|
2
|
+
const oldRegistrations = await navigator.serviceWorker.getRegistrations();
|
|
3
|
+
for (const registration of oldRegistrations) {
|
|
4
|
+
if (registration.installing.state === "installing") {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const workerUrl =
|
|
10
|
+
import.meta.env.MODE === "production"
|
|
11
|
+
? "/rails.sw.js"
|
|
12
|
+
: "/dev-sw.js?dev-sw";
|
|
13
|
+
|
|
14
|
+
await navigator.serviceWorker.register(workerUrl, {
|
|
15
|
+
scope: "/",
|
|
16
|
+
type: "module",
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function boot({ bootMessage, bootProgress, bootConsoleOutput }) {
|
|
21
|
+
if (!("serviceWorker" in navigator)) {
|
|
22
|
+
console.error("Service Worker is not supported in this browser.");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!navigator.serviceWorker.controller) {
|
|
27
|
+
await registerServiceWorker();
|
|
28
|
+
|
|
29
|
+
bootMessage.textContent = "Waiting for Service Worker to activate...";
|
|
30
|
+
} else {
|
|
31
|
+
console.log("Service Worker already active.");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
navigator.serviceWorker.addEventListener("message", function (event) {
|
|
35
|
+
switch (event.data.type) {
|
|
36
|
+
case "progress": {
|
|
37
|
+
bootMessage.textContent = event.data.step;
|
|
38
|
+
bootProgress.value = event.data.value;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
case "console": {
|
|
42
|
+
bootConsoleOutput.textContent += event.data.message + "\n";
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
default: {
|
|
46
|
+
console.log("Unknown message type:", event.data.type);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return await navigator.serviceWorker.ready;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function init() {
|
|
55
|
+
const bootMessage = document.getElementById("boot-message");
|
|
56
|
+
const bootProgress = document.getElementById("boot-progress");
|
|
57
|
+
const bootConsoleOutput = document.getElementById("boot-console-output");
|
|
58
|
+
const registration = await boot({
|
|
59
|
+
bootMessage,
|
|
60
|
+
bootProgress,
|
|
61
|
+
bootConsoleOutput,
|
|
62
|
+
});
|
|
63
|
+
if (!registration) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
bootMessage.textContent = "Service Worker Ready";
|
|
67
|
+
bootProgress.value = 100;
|
|
68
|
+
|
|
69
|
+
const launchButton = document.getElementById("launch-button");
|
|
70
|
+
launchButton.disabled = false;
|
|
71
|
+
launchButton.addEventListener("click", async function () {
|
|
72
|
+
// Open in a new window
|
|
73
|
+
window.open("/", "_blank");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const rebootButton = document.getElementById("reboot-button");
|
|
77
|
+
rebootButton.disabled = false;
|
|
78
|
+
rebootButton.addEventListener("click", async function () {
|
|
79
|
+
await registration.unregister();
|
|
80
|
+
window.location.reload();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const reloadButton = document.getElementById("reload-button");
|
|
84
|
+
reloadButton.disabled = false;
|
|
85
|
+
reloadButton.addEventListener("click", async function () {
|
|
86
|
+
registration.active.postMessage({ type: "reload-rails" });
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const reloadDebugButton = document.getElementById("reload-debug-button");
|
|
90
|
+
reloadDebugButton.disabled = false;
|
|
91
|
+
reloadDebugButton.addEventListener("click", async function () {
|
|
92
|
+
registration.active.postMessage({ type: "reload-rails", debug: true });
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
init();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import sqlite3InitModule from "@sqlite.org/sqlite-wasm";
|
|
2
|
+
|
|
3
|
+
export const setupSQLiteDatabase = async () => {
|
|
4
|
+
const sqlite3 = await sqlite3InitModule();
|
|
5
|
+
|
|
6
|
+
console.log("Running SQLite3 version", sqlite3.version.libVersion);
|
|
7
|
+
const db =
|
|
8
|
+
"opfs" in sqlite3
|
|
9
|
+
? new sqlite3.oo1.OpfsDb("/railsdb.sqlite3")
|
|
10
|
+
: new sqlite3.oo1.DB("/railsdb.sqlite3", "ct");
|
|
11
|
+
console.log(
|
|
12
|
+
"opfs" in sqlite3
|
|
13
|
+
? `OPFS is available, created persisted database at ${db.filename}`
|
|
14
|
+
: `OPFS is not available, created transient database ${db.filename}`,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
return db;
|
|
18
|
+
};
|