opal-rails 2.0.3 → 3.0.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/.github/workflows/build.yml +39 -4
- data/.gitignore +5 -0
- data/Appraisals +20 -21
- data/CHANGELOG.md +36 -0
- data/Gemfile +2 -2
- data/PORTING.md +140 -0
- data/README.md +151 -88
- data/Rakefile +16 -2
- data/app/helpers/opal_helper.rb +9 -28
- data/bin/sandbox +3 -2
- data/bin/sandbox-setup +2 -0
- data/gemfiles/{rails_7_0_opal_1_0.gemfile → rails_7_0_opal_1_8.gemfile} +2 -2
- data/gemfiles/{rails_7_0_opal_1_3.gemfile → rails_7_0_opal_master.gemfile} +2 -2
- data/gemfiles/{rails_6_0_opal_1_0.gemfile → rails_8_0_opal_1_8.gemfile} +3 -3
- data/gemfiles/rails_8_0_opal_master.gemfile +10 -0
- data/gemfiles/{rails_6_1_opal_1_0.gemfile → rails_8_1_opal_1_8.gemfile} +3 -3
- data/gemfiles/rails_8_1_opal_master.gemfile +10 -0
- data/lib/generators/opal/assets/assets_generator.rb +11 -1
- data/lib/generators/opal/assets/templates/{javascript.js.rb → asset.rb.tt} +3 -6
- data/lib/generators/opal/install/install_generator.rb +160 -9
- data/lib/generators/opal/install/templates/{application.js.rb → application.rb} +4 -4
- data/lib/generators/opal/install/templates/dev.tt +8 -0
- data/lib/generators/opal/install/templates/{initializer.rb → initializer.rb.tt} +8 -0
- data/lib/opal/rails/builder_runner.rb +126 -0
- data/lib/opal/rails/engine.rb +11 -9
- data/lib/opal/rails/entrypoints_resolver.rb +81 -0
- data/lib/opal/rails/errors.rb +11 -0
- data/lib/opal/rails/file_watcher.rb +58 -0
- data/lib/opal/rails/haml6_filter.rb +6 -6
- data/lib/opal/rails/haml_filter.rb +3 -6
- data/lib/opal/rails/legacy_upgrade_warning.rb +95 -0
- data/lib/opal/rails/outputs_manifest.rb +82 -0
- data/lib/opal/rails/path_setup.rb +28 -0
- data/lib/opal/rails/task_hooks.rb +49 -0
- data/lib/opal/rails/version.rb +1 -1
- data/lib/opal/rails/watch_runner.rb +173 -0
- data/lib/opal/rails.rb +7 -0
- data/lib/tasks/opal.rake +48 -0
- data/opal-rails.gemspec +23 -14
- data/spec/end_to_end/full_lifecycle_spec.rb +377 -0
- data/spec/helpers/opal_helper_spec.rb +27 -34
- data/spec/integration/source_map_spec.rb +6 -8
- data/spec/opal/assets_generator_spec.rb +31 -0
- data/spec/opal/install_generator_spec.rb +140 -0
- data/spec/opal/rails/build_task_spec.rb +116 -0
- data/spec/opal/rails/builder_runner_spec.rb +151 -0
- data/spec/opal/rails/clobber_task_spec.rb +55 -0
- data/spec/opal/rails/entrypoints_resolver_spec.rb +50 -0
- data/spec/opal/rails/haml_filter_spec.rb +41 -0
- data/spec/opal/rails/legacy_upgrade_warning_spec.rb +94 -0
- data/spec/opal/rails/outputs_manifest_spec.rb +44 -0
- data/spec/opal/rails/path_setup_spec.rb +71 -0
- data/spec/opal/rails/task_hooks_spec.rb +61 -0
- data/spec/opal/rails/watch_runner_spec.rb +283 -0
- data/spec/opal/rails/watch_task_spec.rb +23 -0
- data/spec/spec_helper.rb +16 -7
- data/spec/support/browser_support_spec.rb +36 -0
- data/spec/support/capybara.rb +61 -0
- data/spec/support/reset_assets_cache.rb +9 -1
- data/spec/support/test_app.rb +23 -2
- data/test_apps/app/application_controller.rb +23 -32
- data/test_apps/app/assets/builds/.keep +0 -0
- data/test_apps/app/assets/config/manifest.js +3 -1
- data/test_apps/app/assets/images/.keep +0 -0
- data/test_apps/app/opal/application.rb +5 -0
- data/test_apps/app/{assets/javascripts/source_map_example.js.rb → opal/source_map_example.rb} +1 -2
- data/test_apps/app/opal/with_assignments.js.rb +8 -0
- data/test_apps/rails.rb +19 -5
- metadata +196 -50
- data/gemfiles/rails_6_0_opal_1_1.gemfile +0 -9
- data/gemfiles/rails_6_0_opal_1_3.gemfile +0 -10
- data/gemfiles/rails_6_0_opal_1_7.gemfile +0 -10
- data/gemfiles/rails_6_1_opal_1_1.gemfile +0 -9
- data/gemfiles/rails_6_1_opal_1_3.gemfile +0 -10
- data/gemfiles/rails_6_1_opal_1_7.gemfile +0 -10
- data/gemfiles/rails_7_0_opal_1_7.gemfile +0 -10
- data/lib/opal/rails/haml5_filter.rb +0 -28
- data/test_apps/app/assets/javascripts/application.js.rb +0 -7
- data/test_apps/app/assets/javascripts/bar.rb +0 -3
- data/test_apps/app/assets/javascripts/foo.js.rb +0 -3
|
@@ -1,14 +1,165 @@
|
|
|
1
1
|
class Opal::InstallGenerator < Rails::Generators::Base
|
|
2
2
|
source_root File.expand_path('templates', __dir__)
|
|
3
3
|
|
|
4
|
-
def
|
|
5
|
-
|
|
6
|
-
template
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
def create_opal_files
|
|
5
|
+
create_application_entrypoint
|
|
6
|
+
template 'initializer.rb.tt', 'config/initializers/opal.rb'
|
|
7
|
+
empty_directory 'app/assets/builds'
|
|
8
|
+
create_file 'app/assets/builds/.keep' unless destination_file_exist?('app/assets/builds/.keep')
|
|
9
|
+
ensure_manifest_build_links
|
|
10
|
+
ensure_test_asset_debug
|
|
11
|
+
ensure_gitignore_entries
|
|
12
|
+
ensure_procfile_dev
|
|
13
|
+
ensure_bin_dev
|
|
14
|
+
ensure_javascript_include_tag
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def create_application_entrypoint
|
|
20
|
+
return unless generate_application_entrypoint?
|
|
21
|
+
|
|
22
|
+
template 'application.rb', application_entrypoint_path
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def opal_source_path
|
|
26
|
+
destination_directory_exist?('app/assets/opal') ? 'app/assets/opal' : 'app/opal'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def application_entrypoint_path
|
|
30
|
+
File.join(opal_source_path, 'application.rb')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def generate_application_entrypoint?
|
|
34
|
+
return false if destination_file_exist?(application_entrypoint_path)
|
|
35
|
+
|
|
36
|
+
existing_top_level_entrypoints.empty?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def application_entrypoint_available?
|
|
40
|
+
generate_application_entrypoint? || destination_file_exist?(application_entrypoint_path)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def entrypoints_literal
|
|
44
|
+
return ':all' if bulk_entrypoints_layout?
|
|
45
|
+
|
|
46
|
+
"{ '#{application_logical_name}' => 'application.rb' }"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def application_logical_name
|
|
50
|
+
application_asset_name_reserved? ? 'opal' : 'application'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def application_asset_name_reserved?
|
|
54
|
+
reserved_application_asset_paths.any? { |path| destination_file_exist?(path) }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def reserved_application_asset_paths
|
|
58
|
+
%w[
|
|
59
|
+
app/javascript/application.js
|
|
60
|
+
app/javascript/application.mjs
|
|
61
|
+
app/javascript/application.ts
|
|
62
|
+
app/javascript/application.tsx
|
|
63
|
+
app/assets/javascripts/application.js
|
|
64
|
+
app/assets/javascripts/application.js.erb
|
|
65
|
+
app/assets/builds/application.js
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def bulk_entrypoints_layout?
|
|
70
|
+
return false if existing_top_level_entrypoints.empty?
|
|
71
|
+
|
|
72
|
+
existing_top_level_entrypoints.length > 1 || existing_top_level_entrypoints.first != 'application.rb'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def existing_top_level_entrypoints
|
|
76
|
+
@existing_top_level_entrypoints ||= begin
|
|
77
|
+
source_root_path = destination_path(opal_source_path)
|
|
78
|
+
if File.directory?(source_root_path)
|
|
79
|
+
Dir.children(source_root_path)
|
|
80
|
+
.select { |entry| entry.end_with?('.rb') && File.file?(File.join(source_root_path, entry)) }
|
|
81
|
+
.sort
|
|
82
|
+
else
|
|
83
|
+
[]
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def ensure_gitignore_entries
|
|
89
|
+
return unless destination_file_exist?('.gitignore')
|
|
90
|
+
|
|
91
|
+
append_unless_present('.gitignore', "/app/assets/builds/*\n")
|
|
92
|
+
append_unless_present('.gitignore', "!/app/assets/builds/.keep\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def ensure_manifest_build_links
|
|
96
|
+
manifest_path = 'app/assets/config/manifest.js'
|
|
97
|
+
return unless destination_file_exist?(manifest_path)
|
|
98
|
+
|
|
99
|
+
append_unless_present(manifest_path, "//= link_directory ../builds .js\n")
|
|
100
|
+
append_unless_present(manifest_path, "//= link_directory ../builds .map\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def ensure_test_asset_debug
|
|
104
|
+
test_env_path = 'config/environments/test.rb'
|
|
105
|
+
return unless destination_file_exist?(test_env_path)
|
|
106
|
+
return unless destination_file_exist?('app/assets/config/manifest.js')
|
|
107
|
+
|
|
108
|
+
test_env_contents = File.read(destination_path(test_env_path))
|
|
109
|
+
return if test_env_contents.match?(/config\.assets\.debug\s*=/)
|
|
110
|
+
|
|
111
|
+
insert_into_file test_env_path, "\n config.assets.debug = true", before: /\nend\s*\z/
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def ensure_procfile_dev
|
|
115
|
+
if destination_file_exist?('Procfile.dev')
|
|
116
|
+
append_unless_present('Procfile.dev', "opal: bin/rails opal:watch\n")
|
|
117
|
+
else
|
|
118
|
+
create_file 'Procfile.dev', "web: bin/rails server\nopal: bin/rails opal:watch\n"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def ensure_bin_dev
|
|
123
|
+
if destination_file_exist?('bin/dev')
|
|
124
|
+
# Replace bin/dev if it doesn't use Procfile.dev -- Rails 8.1+
|
|
125
|
+
# generates a bin/dev that just execs "bin/rails server" directly.
|
|
126
|
+
bin_dev_content = File.read(destination_path('bin/dev'))
|
|
127
|
+
return if bin_dev_content.include?('Procfile.dev')
|
|
128
|
+
|
|
129
|
+
remove_file 'bin/dev'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
empty_directory 'bin'
|
|
133
|
+
template 'dev', 'bin/dev'
|
|
134
|
+
chmod 'bin/dev', 0o755
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def ensure_javascript_include_tag
|
|
138
|
+
layout_path = 'app/views/layouts/application.html.erb'
|
|
139
|
+
return unless destination_file_exist?(layout_path)
|
|
140
|
+
return unless application_entrypoint_available?
|
|
141
|
+
|
|
142
|
+
layout_contents = File.read(destination_path(layout_path))
|
|
143
|
+
return if layout_contents.match?(/javascript_include_tag\s+["']#{Regexp.escape(application_logical_name)}["']/)
|
|
144
|
+
|
|
145
|
+
tag = %(<%= javascript_include_tag "#{application_logical_name}", "data-turbo-track": "reload" %>)
|
|
146
|
+
|
|
147
|
+
insert_into_file layout_path, " #{tag}\n", before: %r{\n *</head>}
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def append_unless_present(path, contents)
|
|
151
|
+
append_to_file(path, contents) unless File.read(destination_path(path)).include?(contents.strip)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def destination_directory_exist?(relative_path)
|
|
155
|
+
File.directory?(destination_path(relative_path))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def destination_file_exist?(relative_path)
|
|
159
|
+
File.exist?(destination_path(relative_path))
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def destination_path(relative_path)
|
|
163
|
+
File.join(destination_root, relative_path)
|
|
13
164
|
end
|
|
14
165
|
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
require
|
|
1
|
+
require 'opal'
|
|
2
2
|
|
|
3
|
-
# Uncomment the following to print
|
|
3
|
+
# Uncomment the following to print a hello-world message with Opal:
|
|
4
4
|
#
|
|
5
|
-
# puts
|
|
5
|
+
# puts 'hello world!'
|
|
6
6
|
#
|
|
7
7
|
# The following will append a hello-world to your <body> element:
|
|
8
8
|
#
|
|
9
|
-
# require
|
|
9
|
+
# require 'native'
|
|
10
10
|
# $$[:document].addEventListener :DOMContentLoaded do
|
|
11
11
|
# $$[:document][:body][:innerHTML] = '<h2>Hello World!</h2>'
|
|
12
12
|
# end
|
|
@@ -19,4 +19,12 @@ Rails.application.configure do
|
|
|
19
19
|
# - :ivars # only instance variables
|
|
20
20
|
#
|
|
21
21
|
config.opal.assigns_in_templates = false
|
|
22
|
+
|
|
23
|
+
# Build-oriented Opal asset configuration
|
|
24
|
+
config.opal.source_path = Rails.root.join('<%= opal_source_path %>')
|
|
25
|
+
config.opal.entrypoints_path = config.opal.source_path
|
|
26
|
+
config.opal.build_path = Rails.root.join('app/assets/builds')
|
|
27
|
+
config.opal.entrypoints = <%= entrypoints_literal %>
|
|
28
|
+
config.opal.append_paths = []
|
|
29
|
+
config.opal.use_gems = []
|
|
22
30
|
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
module Opal
|
|
7
|
+
module Rails
|
|
8
|
+
class BuilderRunner
|
|
9
|
+
def initialize(config:, builder_class: Opal::Builder)
|
|
10
|
+
@config = config
|
|
11
|
+
@builder_class = builder_class
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def build(entrypoints:)
|
|
15
|
+
with_opal_config do
|
|
16
|
+
outputs = []
|
|
17
|
+
dependencies = {}
|
|
18
|
+
|
|
19
|
+
FileUtils.mkdir_p(build_path)
|
|
20
|
+
|
|
21
|
+
entrypoints.each_pair do |logical_name, relative_source_file|
|
|
22
|
+
builder = build_entrypoint(relative_source_file)
|
|
23
|
+
js_output = "#{logical_name}.js"
|
|
24
|
+
|
|
25
|
+
write_output(js_output, compiled_source_for(builder, js_output))
|
|
26
|
+
outputs << js_output
|
|
27
|
+
|
|
28
|
+
if source_map_enabled?
|
|
29
|
+
map_output = "#{js_output}.map"
|
|
30
|
+
write_output(map_output, builder.source_map.to_json)
|
|
31
|
+
outputs << map_output
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
dependencies[logical_name] = builder.dependent_files
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
{
|
|
38
|
+
outputs: outputs,
|
|
39
|
+
dependencies: dependencies
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
attr_reader :builder_class, :config
|
|
47
|
+
|
|
48
|
+
def build_entrypoint(relative_source_file)
|
|
49
|
+
builder = builder_class.new(
|
|
50
|
+
compiler_options: Opal::Config.compiler_options,
|
|
51
|
+
missing_require_severity: Opal::Config.missing_require_severity
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
builder.append_paths(*builder_paths)
|
|
55
|
+
Array(config.use_gems).each { |gem_name| builder.use_gem(gem_name) }
|
|
56
|
+
builder.build(relative_source_file)
|
|
57
|
+
builder
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def builder_paths
|
|
61
|
+
[
|
|
62
|
+
config.entrypoints_path,
|
|
63
|
+
config.source_path,
|
|
64
|
+
*Array(config.append_paths)
|
|
65
|
+
].compact.map { |path| Pathname(path).expand_path.to_s }.uniq
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def build_path
|
|
69
|
+
Pathname(config.build_path).expand_path
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def source_map_enabled?
|
|
73
|
+
value = config[:source_map_enabled]
|
|
74
|
+
value.nil? ? Opal::Config.source_map_enabled : value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def compiled_source_for(builder, js_output)
|
|
78
|
+
source = builder.to_s
|
|
79
|
+
return source unless source_map_enabled?
|
|
80
|
+
|
|
81
|
+
[source, "//# sourceMappingURL=#{File.basename(js_output)}.map", nil].join("\n")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def write_output(relative_path, contents)
|
|
85
|
+
output_path = build_path.join(relative_path)
|
|
86
|
+
FileUtils.mkdir_p(output_path.dirname)
|
|
87
|
+
output_path.write(contents)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def with_opal_config
|
|
91
|
+
original_config = snapshot_opal_config
|
|
92
|
+
apply_app_config_to_opal!
|
|
93
|
+
yield
|
|
94
|
+
ensure
|
|
95
|
+
restore_opal_config(original_config)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def snapshot_opal_config
|
|
99
|
+
Opal::Config.config.each_with_object({}) do |(key, value), snapshot|
|
|
100
|
+
snapshot[key] = begin
|
|
101
|
+
value.dup
|
|
102
|
+
rescue TypeError
|
|
103
|
+
value
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def apply_app_config_to_opal!
|
|
109
|
+
config.each_pair do |key, value|
|
|
110
|
+
setter = "#{key}="
|
|
111
|
+
Opal::Config.public_send(setter, value) if Opal::Config.respond_to?(setter)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def restore_opal_config(snapshot)
|
|
116
|
+
return unless snapshot
|
|
117
|
+
|
|
118
|
+
Opal::Config.reset!
|
|
119
|
+
snapshot.each_pair do |key, value|
|
|
120
|
+
setter = "#{key}="
|
|
121
|
+
Opal::Config.public_send(setter, value) if Opal::Config.respond_to?(setter)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/opal/rails/engine.rb
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
require 'rails'
|
|
2
|
-
require 'opal/sprockets'
|
|
3
|
-
require 'sprockets/railtie'
|
|
4
2
|
|
|
5
3
|
module Opal
|
|
6
4
|
module Rails
|
|
@@ -11,13 +9,17 @@ module Opal
|
|
|
11
9
|
|
|
12
10
|
config.opal.dynamic_require_severity = :ignore
|
|
13
11
|
config.opal.assigns_in_templates = true
|
|
12
|
+
config.opal.entrypoints = { 'application' => 'application.rb' }
|
|
13
|
+
config.opal.append_paths = []
|
|
14
|
+
config.opal.use_gems = []
|
|
15
|
+
config.opal.suppress_legacy_upgrade_warning = false
|
|
14
16
|
|
|
15
17
|
def (config.opal).assign_locals_in_templates?
|
|
16
|
-
|
|
18
|
+
[true, :locals].include?(assigns_in_templates)
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def (config.opal).assign_instance_variables_in_templates?
|
|
20
|
-
|
|
22
|
+
[true, :ivars].include?(assigns_in_templates)
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
# Cache eager_load_paths now, otherwise the assets dir is added
|
|
@@ -25,11 +27,8 @@ module Opal
|
|
|
25
27
|
config.eager_load_paths
|
|
26
28
|
|
|
27
29
|
config.before_initialize do |app|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
initializer 'opal.append_assets_path', after: :append_assets_path, group: :all do |app|
|
|
32
|
-
app.config.assets.paths.unshift(*Opal.paths)
|
|
30
|
+
Opal::Rails::LegacyUpgradeWarning.warn_if_needed(app)
|
|
31
|
+
Opal::Rails::PathSetup.apply!(app)
|
|
33
32
|
end
|
|
34
33
|
|
|
35
34
|
config.after_initialize do |app|
|
|
@@ -43,6 +42,9 @@ module Opal
|
|
|
43
42
|
end
|
|
44
43
|
end
|
|
45
44
|
|
|
45
|
+
rake_tasks do
|
|
46
|
+
load File.expand_path('../../tasks/opal.rake', __dir__)
|
|
47
|
+
end
|
|
46
48
|
end
|
|
47
49
|
end
|
|
48
50
|
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module Opal
|
|
6
|
+
module Rails
|
|
7
|
+
class EntrypointsResolver
|
|
8
|
+
def initialize(entrypoints_path:, entrypoints:)
|
|
9
|
+
@entrypoints_path = Pathname(entrypoints_path).expand_path
|
|
10
|
+
@entrypoints = entrypoints
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def resolve
|
|
14
|
+
case entrypoints
|
|
15
|
+
when Hash
|
|
16
|
+
resolve_hash
|
|
17
|
+
when :all
|
|
18
|
+
resolve_all
|
|
19
|
+
else
|
|
20
|
+
raise InvalidEntrypointsConfigError,
|
|
21
|
+
"config.opal.entrypoints must be a Hash or :all, got #{entrypoints.inspect}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :entrypoints_path, :entrypoints
|
|
28
|
+
|
|
29
|
+
def resolve_hash
|
|
30
|
+
resolved = {}
|
|
31
|
+
|
|
32
|
+
unless entrypoints_path.directory?
|
|
33
|
+
raise MissingEntrypointError,
|
|
34
|
+
"Opal entrypoints_path #{entrypoints_path} does not exist"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
entrypoints.each_pair do |logical_name, relative_source_file|
|
|
38
|
+
logical_name = logical_name.to_s
|
|
39
|
+
relative_source_file = relative_source_file.to_s
|
|
40
|
+
absolute_source_file = entrypoints_path.join(relative_source_file)
|
|
41
|
+
|
|
42
|
+
unless absolute_source_file.file?
|
|
43
|
+
raise MissingEntrypointError,
|
|
44
|
+
"configured Opal entrypoint #{relative_source_file.inspect} was not found under #{entrypoints_path}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if resolved.key?(logical_name)
|
|
48
|
+
raise DuplicateEntrypointError,
|
|
49
|
+
"duplicate Opal logical entrypoint #{logical_name.inspect}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
resolved[logical_name] = relative_source_file
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
resolved
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def resolve_all
|
|
59
|
+
unless entrypoints_path.directory?
|
|
60
|
+
raise MissingEntrypointError,
|
|
61
|
+
"Opal entrypoints_path #{entrypoints_path} does not exist"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
entrypoints_path.children
|
|
65
|
+
.select(&:file?)
|
|
66
|
+
.select { |path| path.extname == '.rb' }
|
|
67
|
+
.sort_by { |path| path.basename.to_s }
|
|
68
|
+
.each_with_object({}) do |path, resolved|
|
|
69
|
+
logical_name = path.basename('.rb').to_s
|
|
70
|
+
|
|
71
|
+
if resolved.key?(logical_name)
|
|
72
|
+
raise DuplicateEntrypointError,
|
|
73
|
+
"duplicate Opal logical entrypoint #{logical_name.inspect}"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
resolved[logical_name] = path.basename.to_s
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'pathname'
|
|
4
|
+
|
|
5
|
+
module Opal
|
|
6
|
+
module Rails
|
|
7
|
+
class FileWatcher
|
|
8
|
+
def initialize(files:, extra_directories: [], &callback)
|
|
9
|
+
@files = normalize_paths(files)
|
|
10
|
+
@extra_directories = normalize_paths(extra_directories)
|
|
11
|
+
@callback = callback
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def start
|
|
15
|
+
require 'listen'
|
|
16
|
+
|
|
17
|
+
@listener = Listen.to(*directories) do |modified, added, removed|
|
|
18
|
+
callback.call(
|
|
19
|
+
modified: normalize_paths(modified),
|
|
20
|
+
added: normalize_paths(added),
|
|
21
|
+
removed: normalize_paths(removed)
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
@listener.start
|
|
25
|
+
rescue LoadError
|
|
26
|
+
raise Error, 'opal:watch requires the listen gem'
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop
|
|
30
|
+
@listener&.stop
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
attr_reader :callback, :extra_directories, :files
|
|
36
|
+
|
|
37
|
+
def directories
|
|
38
|
+
dirs = files.map { |file| File.directory?(file) ? file : File.dirname(file) }
|
|
39
|
+
collapse_directories((dirs + extra_directories).uniq.sort)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def collapse_directories(directories)
|
|
43
|
+
previous_dir = nil
|
|
44
|
+
|
|
45
|
+
directories.each_with_object([]) do |dir, collapsed|
|
|
46
|
+
next if previous_dir && dir.start_with?(previous_dir + File::SEPARATOR)
|
|
47
|
+
|
|
48
|
+
collapsed << dir
|
|
49
|
+
previous_dir = dir
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def normalize_paths(paths)
|
|
54
|
+
Array(paths).map { |path| Pathname(path).expand_path.to_s }.uniq.sort
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
1
|
+
require 'haml'
|
|
2
|
+
require 'haml/filters'
|
|
3
|
+
require 'haml/filters/base'
|
|
4
4
|
|
|
5
5
|
module Haml
|
|
6
6
|
class Filters
|
|
7
7
|
class Opal < Base
|
|
8
8
|
def mime_type
|
|
9
|
+
::Opal::Config.esm ? 'module' : 'text/javascript'
|
|
9
10
|
end
|
|
10
|
-
|
|
11
|
+
|
|
11
12
|
def compile(node)
|
|
12
13
|
template = [:multi]
|
|
13
14
|
template << [:static, "<script type='#{mime_type}'>\n"]
|
|
14
|
-
template << [:static, ::Opal.compile(node.value[:text])
|
|
15
|
+
template << [:static, ::Opal.compile(node.value[:text])]
|
|
15
16
|
template << [:static, "\n</script>"]
|
|
16
17
|
template
|
|
17
18
|
end
|
|
18
19
|
end
|
|
19
|
-
::Opal::Config.esm ? 'module' : 'text/javascript'
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
+
require 'opal'
|
|
1
2
|
require 'haml'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
raise LoadError, 'opal-rails requires Haml 6 or newer for the :opal filter' if Haml::VERSION.to_i < 6
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
require 'opal/rails/haml5_filter'
|
|
7
|
-
else
|
|
8
|
-
require 'opal/rails/haml6_filter'
|
|
9
|
-
end
|
|
6
|
+
require 'opal/rails/haml6_filter'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
module Opal
|
|
2
|
+
module Rails
|
|
3
|
+
module LegacyUpgradeWarning
|
|
4
|
+
module_function
|
|
5
|
+
|
|
6
|
+
LEGACY_OPAL_CONFIG_KEYS = %w[
|
|
7
|
+
method_missing_enabled
|
|
8
|
+
const_missing_enabled
|
|
9
|
+
arity_check_enabled
|
|
10
|
+
freezing_stubs_enabled
|
|
11
|
+
missing_require_severity
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
BUILD_CONFIG_KEYS = %w[
|
|
15
|
+
source_path
|
|
16
|
+
entrypoints_path
|
|
17
|
+
build_path
|
|
18
|
+
entrypoints
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
def warn_if_needed(app, output: $stderr)
|
|
22
|
+
return false if suppress_warning?(app)
|
|
23
|
+
|
|
24
|
+
warning = warning_for(app.root)
|
|
25
|
+
return false unless warning
|
|
26
|
+
|
|
27
|
+
output.puts(warning)
|
|
28
|
+
true
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def warning_for(root)
|
|
32
|
+
signals = legacy_signals(root)
|
|
33
|
+
return if signals.empty?
|
|
34
|
+
|
|
35
|
+
<<~WARNING
|
|
36
|
+
WARNING: opal-rails detected a likely 2.x application layout that has not been ported to the 3.x build pipeline yet.
|
|
37
|
+
|
|
38
|
+
This app may fail after upgrade with missing `app/opal` entrypoints or missing `application.js` assets.
|
|
39
|
+
Pin `opal-rails` to the 2.0 series until the app can be ported, or follow `PORTING.md` before continuing.
|
|
40
|
+
If you have already ported the app and want to silence this check, set `config.opal.suppress_legacy_upgrade_warning = true`.
|
|
41
|
+
|
|
42
|
+
Detected legacy signals:
|
|
43
|
+
#{signals.map { |signal| "- #{signal}" }.join("\n")}
|
|
44
|
+
WARNING
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def suppress_warning?(app)
|
|
48
|
+
return false unless app.respond_to?(:config)
|
|
49
|
+
|
|
50
|
+
app.config.respond_to?(:opal) && app.config.opal.suppress_legacy_upgrade_warning
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def legacy_signals(root)
|
|
54
|
+
root = Pathname(root)
|
|
55
|
+
signals = []
|
|
56
|
+
|
|
57
|
+
initializer = root.join('config/initializers/opal.rb')
|
|
58
|
+
initializer_body = initializer.exist? ? initializer.read : nil
|
|
59
|
+
|
|
60
|
+
if legacy_initializer?(initializer_body)
|
|
61
|
+
signals << 'config/initializers/opal.rb still uses legacy 2.x runtime settings'
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
legacy_entrypoints(root).each do |entrypoint|
|
|
65
|
+
signals << "legacy Opal asset entrypoint present at #{relative_to_root(entrypoint, root)}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
manifest = root.join('app/assets/config/manifest.js')
|
|
69
|
+
if manifest.exist? && manifest.read.match?(%r{link_(?:tree|directory)\s+\.\./javascript\b})
|
|
70
|
+
signals << 'app/assets/config/manifest.js still links the legacy javascript asset tree'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
return [] unless initializer_body && signals.length >= 2
|
|
74
|
+
return signals if root.join('app/opal').directory?
|
|
75
|
+
|
|
76
|
+
signals << 'expected 3.x source root app/opal is missing'
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def legacy_initializer?(initializer_body)
|
|
80
|
+
return false unless initializer_body
|
|
81
|
+
return false if BUILD_CONFIG_KEYS.any? { |key| initializer_body.include?("config.opal.#{key}") }
|
|
82
|
+
|
|
83
|
+
LEGACY_OPAL_CONFIG_KEYS.any? { |key| initializer_body.include?("config.opal.#{key}") }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def legacy_entrypoints(root)
|
|
87
|
+
Dir[root.join('app/assets/{javascript,javascripts}/**/*.js.rb').to_s].sort.map { |path| Pathname(path) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def relative_to_root(path, root)
|
|
91
|
+
path.relative_path_from(root).to_s
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|