opal 1.5.1 → 1.6.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitattributes +4 -0
- data/.github/workflows/build.yml +17 -3
- data/HACKING.md +23 -0
- data/README.md +3 -3
- data/UNRELEASED.md +47 -0
- data/benchmark/run.rb +1 -0
- data/docs/compiled_ruby.md +8 -0
- data/docs/compiler.md +1 -1
- data/docs/compiler_directives.md +1 -1
- data/docs/getting_started.md +17 -0
- data/docs/headless_chrome.md +1 -1
- data/docs/index.md +123 -0
- data/docs/jquery.md +5 -5
- data/docs/templates.md +37 -37
- data/docs/unsupported_features.md +0 -4
- data/lib/opal/builder.rb +59 -39
- data/lib/opal/builder_processors.rb +24 -0
- data/lib/opal/builder_scheduler/prefork.rb +262 -0
- data/lib/opal/builder_scheduler/sequential.rb +13 -0
- data/lib/opal/builder_scheduler.rb +29 -0
- data/lib/opal/cache/file_cache.rb +13 -2
- data/lib/opal/cli.rb +36 -19
- data/lib/opal/cli_options.rb +4 -0
- data/lib/opal/cli_runners/chrome.rb +17 -13
- data/lib/opal/cli_runners/chrome_cdp_interface.rb +19 -2
- data/lib/opal/cli_runners/compiler.rb +1 -1
- data/lib/opal/cli_runners/gjs.rb +3 -1
- data/lib/opal/cli_runners/mini_racer.rb +5 -3
- data/lib/opal/cli_runners/nodejs.rb +3 -3
- data/lib/opal/cli_runners/server.rb +13 -28
- data/lib/opal/cli_runners/system_runner.rb +5 -3
- data/lib/opal/cli_runners.rb +7 -6
- data/lib/opal/compiler.rb +25 -2
- data/lib/opal/config.rb +10 -0
- data/lib/opal/eof_content.rb +5 -2
- data/lib/opal/nodes/args/ensure_kwargs_are_kwargs.rb +2 -6
- data/lib/opal/nodes/args/extract_kwarg.rb +3 -4
- data/lib/opal/nodes/args/extract_kwargs.rb +3 -1
- data/lib/opal/nodes/args/extract_kwoptarg.rb +1 -1
- data/lib/opal/nodes/args/extract_kwrestarg.rb +4 -1
- data/lib/opal/nodes/args/extract_optarg.rb +1 -1
- data/lib/opal/nodes/args/extract_post_arg.rb +1 -1
- data/lib/opal/nodes/args/extract_post_optarg.rb +1 -1
- data/lib/opal/nodes/args/extract_restarg.rb +2 -2
- data/lib/opal/nodes/args/initialize_iterarg.rb +1 -1
- data/lib/opal/nodes/args/initialize_shadowarg.rb +1 -1
- data/lib/opal/nodes/args/prepare_post_args.rb +4 -2
- data/lib/opal/nodes/base.rb +14 -3
- data/lib/opal/nodes/call.rb +13 -16
- data/lib/opal/nodes/class.rb +3 -1
- data/lib/opal/nodes/closure.rb +250 -0
- data/lib/opal/nodes/def.rb +7 -11
- data/lib/opal/nodes/definitions.rb +4 -2
- data/lib/opal/nodes/if.rb +12 -2
- data/lib/opal/nodes/iter.rb +11 -17
- data/lib/opal/nodes/logic.rb +15 -63
- data/lib/opal/nodes/module.rb +3 -1
- data/lib/opal/nodes/rescue.rb +23 -15
- data/lib/opal/nodes/scope.rb +7 -1
- data/lib/opal/nodes/top.rb +27 -4
- data/lib/opal/nodes/while.rb +42 -26
- data/lib/opal/nodes.rb +1 -0
- data/lib/opal/os.rb +59 -0
- data/lib/opal/rewriter.rb +2 -0
- data/lib/opal/rewriters/returnable_logic.rb +14 -0
- data/lib/opal/rewriters/thrower_finder.rb +90 -0
- data/lib/opal/simple_server.rb +12 -6
- data/lib/opal/source_map/file.rb +4 -3
- data/lib/opal/source_map/map.rb +9 -1
- data/lib/opal/util.rb +1 -1
- data/lib/opal/version.rb +1 -1
- data/opal/corelib/array.rb +68 -3
- data/opal/corelib/basic_object.rb +1 -0
- data/opal/corelib/comparable.rb +1 -1
- data/opal/corelib/complex.rb +1 -0
- data/opal/corelib/constants.rb +2 -2
- data/opal/corelib/enumerable.rb +4 -2
- data/opal/corelib/enumerator/chain.rb +4 -0
- data/opal/corelib/enumerator/generator.rb +5 -3
- data/opal/corelib/enumerator/lazy.rb +3 -1
- data/opal/corelib/enumerator/yielder.rb +2 -4
- data/opal/corelib/enumerator.rb +3 -1
- data/opal/corelib/error/errno.rb +3 -1
- data/opal/corelib/error.rb +13 -2
- data/opal/corelib/hash.rb +39 -1
- data/opal/corelib/io.rb +1 -1
- data/opal/corelib/kernel.rb +56 -5
- data/opal/corelib/module.rb +60 -4
- data/opal/corelib/proc.rb +8 -5
- data/opal/corelib/rational.rb +1 -0
- data/opal/corelib/regexp.rb +15 -1
- data/opal/corelib/runtime.js +307 -238
- data/opal/corelib/string/encoding.rb +0 -6
- data/opal/corelib/string.rb +28 -7
- data/opal/corelib/time.rb +5 -2
- data/opal/corelib/unsupported.rb +2 -14
- data/opal.gemspec +2 -2
- data/spec/filters/bugs/delegate.rb +11 -0
- data/spec/filters/bugs/kernel.rb +1 -3
- data/spec/filters/bugs/language.rb +3 -23
- data/spec/filters/bugs/method.rb +0 -1
- data/spec/filters/bugs/module.rb +0 -3
- data/spec/filters/bugs/proc.rb +0 -3
- data/spec/filters/bugs/set.rb +4 -16
- data/spec/filters/bugs/unboundmethod.rb +0 -2
- data/spec/filters/unsupported/array.rb +0 -58
- data/spec/filters/unsupported/freeze.rb +8 -192
- data/spec/filters/unsupported/hash.rb +0 -25
- data/spec/filters/unsupported/kernel.rb +0 -1
- data/spec/filters/unsupported/privacy.rb +17 -0
- data/spec/lib/builder_spec.rb +14 -0
- data/spec/lib/cli_runners/server_spec.rb +2 -3
- data/spec/lib/cli_spec.rb +15 -1
- data/spec/lib/compiler_spec.rb +1 -1
- data/spec/opal/core/language/DATA/characters_support_crlf_spec.rb +9 -0
- data/spec/opal/core/language/DATA/multiple___END___crlf_spec.rb +10 -0
- data/spec/opal/core/language/if_spec.rb +13 -0
- data/spec/opal/core/language/safe_navigator_spec.rb +10 -0
- data/spec/opal/core/module_spec.rb +8 -0
- data/spec/ruby_specs +2 -1
- data/stdlib/await.rb +44 -7
- data/stdlib/delegate.rb +427 -6
- data/stdlib/headless_chrome.rb +6 -2
- data/stdlib/nodejs/file.rb +2 -1
- data/stdlib/opal-parser.rb +1 -1
- data/stdlib/opal-platform.rb +1 -1
- data/stdlib/opal-replutils.rb +5 -3
- data/stdlib/promise.rb +3 -0
- data/stdlib/rbconfig.rb +4 -1
- data/stdlib/ruby2_keywords.rb +60 -0
- data/stdlib/set.rb +21 -0
- data/tasks/performance.rake +41 -35
- data/tasks/releasing.rake +1 -0
- data/tasks/testing/mspec_special_calls.rb +1 -0
- data/tasks/testing.rake +13 -12
- data/test/nodejs/test_await.rb +39 -1
- data/test/nodejs/test_file.rb +3 -2
- metadata +37 -22
- data/docs/faq.md +0 -17
- data/lib/opal/rewriters/break_finder.rb +0 -36
- data/spec/opal/core/kernel/freeze_spec.rb +0 -15
@@ -114,6 +114,12 @@ module Opal
|
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
117
|
+
# This handler is for files named ".opalerb", which ought to
|
118
|
+
# first get compiled to Ruby code using ERB, then with Opal.
|
119
|
+
# Unlike below processors, OpalERBProcessor can be used to
|
120
|
+
# compile templates, which will in turn output HTML. Take
|
121
|
+
# a look at docs/templates.md to understand this subsystem
|
122
|
+
# better.
|
117
123
|
class OpalERBProcessor < RubyProcessor
|
118
124
|
handles :opalerb
|
119
125
|
|
@@ -133,6 +139,24 @@ module Opal
|
|
133
139
|
end
|
134
140
|
end
|
135
141
|
|
142
|
+
# This handler is for files named ".rb.erb", which ought to
|
143
|
+
# first get preprocessed via ERB, then via Opal.
|
144
|
+
class RubyERBProcessor < RubyProcessor
|
145
|
+
handles :"rb.erb"
|
146
|
+
|
147
|
+
def compiled
|
148
|
+
@compiled ||= begin
|
149
|
+
@source = ::ERB.new(@source.to_s).result
|
150
|
+
|
151
|
+
compiler = compiler_for(@source, file: @filename)
|
152
|
+
compiler.compile
|
153
|
+
compiler
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# This handler is for files named ".js.erb", which ought to
|
159
|
+
# first get preprocessed via ERB, then served verbatim as JS.
|
136
160
|
class ERBProcessor < Processor
|
137
161
|
handles :erb
|
138
162
|
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'etc'
|
4
|
+
|
5
|
+
module Opal
|
6
|
+
class BuilderScheduler
|
7
|
+
class Prefork < BuilderScheduler
|
8
|
+
# We hook into the process_requires method
|
9
|
+
def process_requires(rel_path, requires, autoloads, options)
|
10
|
+
return if requires.empty?
|
11
|
+
|
12
|
+
if @in_fork
|
13
|
+
io = @in_fork
|
14
|
+
io.send(:new_requires, rel_path, requires, autoloads, options)
|
15
|
+
else
|
16
|
+
prefork_reactor(rel_path, requires, autoloads, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
class ForkSet < Array
|
23
|
+
def initialize(count, &block)
|
24
|
+
super([])
|
25
|
+
|
26
|
+
@count, @block = count, block
|
27
|
+
|
28
|
+
create_fork
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_events(queue_length)
|
32
|
+
# Wait for anything to happen:
|
33
|
+
# - Either any of our workers return some data
|
34
|
+
# - Or any workers become ready to receive data
|
35
|
+
# - But only if we have enough work for them
|
36
|
+
ios = IO.select(
|
37
|
+
map(&:read_io),
|
38
|
+
sample(queue_length).map(&:write_io),
|
39
|
+
[]
|
40
|
+
)
|
41
|
+
return [[], []] unless ios
|
42
|
+
|
43
|
+
events = ios[0].map do |io|
|
44
|
+
io = from_io(io, :read_io)
|
45
|
+
[io, *io.recv]
|
46
|
+
end
|
47
|
+
|
48
|
+
idles = ios[1].map do |io|
|
49
|
+
from_io(io, :write_io)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Progressively create forks, because we may not need all
|
53
|
+
# the workers at the time. The number 6 was picked due to
|
54
|
+
# some trial and error on a Ryzen machine.
|
55
|
+
#
|
56
|
+
# Do note that prefork may happen more than once.
|
57
|
+
create_fork if length < @count && rand(6) == 1
|
58
|
+
|
59
|
+
[events, idles]
|
60
|
+
end
|
61
|
+
|
62
|
+
def create_fork
|
63
|
+
self << Fork.new(self, &@block)
|
64
|
+
end
|
65
|
+
|
66
|
+
def from_io(io, type)
|
67
|
+
find { |i| i.__send__(type) == io }
|
68
|
+
end
|
69
|
+
|
70
|
+
def close
|
71
|
+
each(&:close)
|
72
|
+
end
|
73
|
+
|
74
|
+
def wait
|
75
|
+
each(&:wait)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class Fork
|
80
|
+
def initialize(forkset)
|
81
|
+
@parent_read, @child_write = IO.pipe
|
82
|
+
@child_read, @parent_write = IO.pipe
|
83
|
+
@forkset = forkset
|
84
|
+
@in_fork = false
|
85
|
+
|
86
|
+
@pid = fork do
|
87
|
+
@in_fork = true
|
88
|
+
|
89
|
+
begin
|
90
|
+
@parent_read.close
|
91
|
+
@parent_write.close
|
92
|
+
|
93
|
+
yield(self)
|
94
|
+
rescue => error
|
95
|
+
send(:exception, error)
|
96
|
+
ensure
|
97
|
+
send(:close) unless write_io.closed?
|
98
|
+
@child_write.close
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
@child_read.close
|
103
|
+
@child_write.close
|
104
|
+
end
|
105
|
+
|
106
|
+
def close
|
107
|
+
send(:close)
|
108
|
+
@parent_write.close
|
109
|
+
end
|
110
|
+
|
111
|
+
def goodbye
|
112
|
+
read_io.close unless read_io.closed?
|
113
|
+
end
|
114
|
+
|
115
|
+
def send_message(io, msg)
|
116
|
+
msg = Marshal.dump(msg)
|
117
|
+
io.write([msg.length].pack('Q') + msg)
|
118
|
+
end
|
119
|
+
|
120
|
+
def recv_message(io)
|
121
|
+
length, = *io.read(8).unpack('Q')
|
122
|
+
Marshal.load(io.read(length)) # rubocop:disable Security/MarshalLoad
|
123
|
+
end
|
124
|
+
|
125
|
+
def fork?
|
126
|
+
@in_fork
|
127
|
+
end
|
128
|
+
|
129
|
+
def read_io
|
130
|
+
fork? ? @child_read : @parent_read
|
131
|
+
end
|
132
|
+
|
133
|
+
def write_io
|
134
|
+
fork? ? @child_write : @parent_write
|
135
|
+
end
|
136
|
+
|
137
|
+
def eof?
|
138
|
+
write_io.closed?
|
139
|
+
end
|
140
|
+
|
141
|
+
def send(*msg)
|
142
|
+
send_message(write_io, msg)
|
143
|
+
end
|
144
|
+
|
145
|
+
def recv
|
146
|
+
recv_message(read_io)
|
147
|
+
end
|
148
|
+
|
149
|
+
def wait
|
150
|
+
Process.waitpid(@pid, Process::WNOHANG)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# By default we use 3/4 of CPU threads detected.
|
155
|
+
def fork_count
|
156
|
+
ENV['OPAL_PREFORK_THREADS']&.to_i || (Etc.nprocessors * 3 / 4.0).ceil
|
157
|
+
end
|
158
|
+
|
159
|
+
def prefork
|
160
|
+
@forks = ForkSet.new(fork_count, &method(:fork_entrypoint))
|
161
|
+
end
|
162
|
+
|
163
|
+
def fork_entrypoint(io)
|
164
|
+
# Ensure we can work with our forks async...
|
165
|
+
Fiber.set_scheduler(nil) if Fiber.respond_to? :set_scheduler
|
166
|
+
|
167
|
+
@in_fork = io
|
168
|
+
|
169
|
+
until io.eof?
|
170
|
+
$0 = 'opal/builder: idle'
|
171
|
+
|
172
|
+
type, *args = *io.recv
|
173
|
+
case type
|
174
|
+
when :compile
|
175
|
+
rel_path, req, autoloads, options = *args
|
176
|
+
$0 = "opal/builder: #{req}"
|
177
|
+
begin
|
178
|
+
asset = builder.process_require_threadsafely(req, autoloads, options)
|
179
|
+
io.send(:new_asset, asset)
|
180
|
+
rescue Builder::MissingRequire => error
|
181
|
+
io.send(:missing_require_exception, rel_path, error)
|
182
|
+
end
|
183
|
+
when :close
|
184
|
+
io.goodbye
|
185
|
+
break
|
186
|
+
end
|
187
|
+
end
|
188
|
+
rescue Errno::EPIPE
|
189
|
+
exit!
|
190
|
+
end
|
191
|
+
|
192
|
+
def prefork_reactor(rel_path, requires, autoloads, options)
|
193
|
+
prefork
|
194
|
+
|
195
|
+
first = rel_path
|
196
|
+
queue = requires.map { |i| [rel_path, i, autoloads, options] }
|
197
|
+
|
198
|
+
awaiting = 0
|
199
|
+
built = 0
|
200
|
+
|
201
|
+
$stderr.print "\r\e[K" if $stderr.tty?
|
202
|
+
|
203
|
+
loop do
|
204
|
+
events, idles = @forks.get_events(queue.length)
|
205
|
+
|
206
|
+
idles.each do |io|
|
207
|
+
break if queue.empty?
|
208
|
+
|
209
|
+
rel_path, req, autoloads, options = *queue.pop
|
210
|
+
|
211
|
+
next if builder.already_processed.include?(req)
|
212
|
+
awaiting += 1
|
213
|
+
builder.already_processed << req
|
214
|
+
io.send(:compile, rel_path, req, autoloads, options)
|
215
|
+
end
|
216
|
+
|
217
|
+
events.each do |io, type, *args|
|
218
|
+
case type
|
219
|
+
when :new_requires
|
220
|
+
rel_path, requires, autoloads, options = *args
|
221
|
+
requires.each do |i|
|
222
|
+
queue << [rel_path, i, autoloads, options]
|
223
|
+
end
|
224
|
+
when :new_asset
|
225
|
+
asset, = *args
|
226
|
+
if !asset
|
227
|
+
# Do nothing, we received a nil which is expected.
|
228
|
+
elsif asset.filename == 'corelib/runtime.js'
|
229
|
+
# Opal runtime should go first... the rest can go their way.
|
230
|
+
builder.processed.unshift(asset)
|
231
|
+
else
|
232
|
+
builder.processed << asset
|
233
|
+
end
|
234
|
+
built += 1
|
235
|
+
awaiting -= 1
|
236
|
+
when :missing_require_exception
|
237
|
+
rel_path, error = *args
|
238
|
+
raise error, "A file required by #{rel_path.inspect} wasn't found.\n#{error.message}", error.backtrace
|
239
|
+
when :exception
|
240
|
+
error, = *args
|
241
|
+
raise error
|
242
|
+
when :close
|
243
|
+
io.goodbye
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
if $stderr.tty?
|
248
|
+
percent = (100.0 * built / (awaiting + built)).round(1)
|
249
|
+
str = format("[opal/builder] Building %<first>s... (%<percent>4.3g%%)\r", first: first, percent: percent)
|
250
|
+
$stderr.print str
|
251
|
+
end
|
252
|
+
|
253
|
+
break if awaiting == 0 && queue.empty?
|
254
|
+
end
|
255
|
+
ensure
|
256
|
+
$stderr.print "\r\e[K\r" if $stderr.tty?
|
257
|
+
@forks.close
|
258
|
+
@forks.wait
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opal
|
4
|
+
class BuilderScheduler
|
5
|
+
class Sequential < BuilderScheduler
|
6
|
+
def process_requires(rel_path, requires, autoloads, options)
|
7
|
+
requires.map { |r| builder.process_require(r, autoloads, options) }
|
8
|
+
rescue Builder::MissingRequire => error
|
9
|
+
raise error, "A file required by #{rel_path.inspect} wasn't found.\n#{error.message}", error.backtrace
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'opal/os' unless RUBY_ENGINE == 'opal'
|
4
|
+
|
5
|
+
module Opal
|
6
|
+
class BuilderScheduler
|
7
|
+
def initialize(builder)
|
8
|
+
@builder = builder
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :builder
|
12
|
+
end
|
13
|
+
|
14
|
+
singleton_class.attr_accessor :builder_scheduler
|
15
|
+
|
16
|
+
if RUBY_ENGINE != 'opal'
|
17
|
+
# Windows has a faulty `fork`.
|
18
|
+
if OS.windows? || ENV['OPAL_PREFORK_DISABLE']
|
19
|
+
require 'opal/builder_scheduler/sequential'
|
20
|
+
Opal.builder_scheduler = BuilderScheduler::Sequential
|
21
|
+
else
|
22
|
+
require 'opal/builder_scheduler/prefork'
|
23
|
+
Opal.builder_scheduler = BuilderScheduler::Prefork
|
24
|
+
end
|
25
|
+
else
|
26
|
+
require 'opal/builder_scheduler/sequential'
|
27
|
+
Opal.builder_scheduler = BuilderScheduler::Sequential
|
28
|
+
end
|
29
|
+
end
|
@@ -19,9 +19,20 @@ module Opal
|
|
19
19
|
|
20
20
|
def set(key, data)
|
21
21
|
file = cache_filename_for(key)
|
22
|
-
|
23
22
|
out = Marshal.dump(data)
|
24
|
-
|
23
|
+
|
24
|
+
# Sometimes `Zlib::BufError` gets raised, unsure why, makes no sense, possibly
|
25
|
+
# some race condition (see https://github.com/ruby/zlib/issues/49).
|
26
|
+
# Limit the number of retries to avoid infinite loops.
|
27
|
+
retries = 5
|
28
|
+
begin
|
29
|
+
out = Zlib.gzip(out, level: 9)
|
30
|
+
rescue Zlib::BufError
|
31
|
+
warn "\n[Opal]: Zlib::BufError; retrying (#{retries} retries left)"
|
32
|
+
retries -= 1
|
33
|
+
retry if retries > 0
|
34
|
+
end
|
35
|
+
|
25
36
|
File.binwrite(file, out)
|
26
37
|
end
|
27
38
|
|
data/lib/opal/cli.rb
CHANGED
@@ -48,14 +48,12 @@ module Opal
|
|
48
48
|
|
49
49
|
@requires.unshift('opal') unless options.delete(:skip_opal_require)
|
50
50
|
|
51
|
-
@compiler_options =
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
end.compact.flatten
|
58
|
-
]
|
51
|
+
@compiler_options = compiler_option_names.map do |option|
|
52
|
+
key = option.to_sym
|
53
|
+
next unless options.key? key
|
54
|
+
value = options.delete(key)
|
55
|
+
[key, value]
|
56
|
+
end.compact.to_h
|
59
57
|
|
60
58
|
raise ArgumentError, 'no libraries to compile' if @lib_only && @requires.empty?
|
61
59
|
raise ArgumentError, 'no runnable code provided (evals or file)' if @evals.empty? && @file.nil? && !@lib_only
|
@@ -68,6 +66,16 @@ module Opal
|
|
68
66
|
return debug_source_map if @debug_source_map
|
69
67
|
return run_repl if @repl
|
70
68
|
|
69
|
+
rbrequires.each { |file| require file }
|
70
|
+
|
71
|
+
runner = self.runner
|
72
|
+
|
73
|
+
# Some runners may need to use a dynamic builder, that is,
|
74
|
+
# a builder that will try to build the entire package every time
|
75
|
+
# a page is loaded - for example a Server runner that needs to
|
76
|
+
# rerun if files are changed.
|
77
|
+
builder = proc { create_builder }
|
78
|
+
|
71
79
|
@exit_status = runner.call(
|
72
80
|
options: runner_options,
|
73
81
|
output: output,
|
@@ -90,13 +98,7 @@ module Opal
|
|
90
98
|
|
91
99
|
attr_reader :exit_status
|
92
100
|
|
93
|
-
def builder
|
94
|
-
@builder ||= create_builder
|
95
|
-
end
|
96
|
-
|
97
101
|
def create_builder
|
98
|
-
rbrequires.each(&Kernel.method(:require))
|
99
|
-
|
100
102
|
builder = Opal::Builder.new(
|
101
103
|
stubs: stubs,
|
102
104
|
compiler_options: compiler_options,
|
@@ -113,22 +115,22 @@ module Opal
|
|
113
115
|
gems.each { |gem_name| builder.use_gem gem_name }
|
114
116
|
|
115
117
|
# --require
|
116
|
-
requires.each { |required| builder.build(required) }
|
118
|
+
requires.each { |required| builder.build(required, requirable: true, load: true) }
|
117
119
|
|
118
120
|
# --preload
|
119
121
|
preload.each { |path| builder.build_require(path) }
|
120
122
|
|
121
123
|
# --verbose
|
122
|
-
builder.build_str '$VERBOSE = true', '(flags)' if verbose
|
124
|
+
builder.build_str '$VERBOSE = true', '(flags)', no_export: true if verbose
|
123
125
|
|
124
126
|
# --debug
|
125
|
-
builder.build_str '$DEBUG = true', '(flags)' if debug
|
127
|
+
builder.build_str '$DEBUG = true', '(flags)', no_export: true if debug
|
126
128
|
|
127
129
|
# --eval / stdin / file
|
128
130
|
evals_or_file { |source, filename| builder.build_str(source, filename) }
|
129
131
|
|
130
132
|
# --no-exit
|
131
|
-
builder.build_str '::Kernel.exit', '(exit)' unless no_exit
|
133
|
+
builder.build_str '::Kernel.exit', '(exit)', no_export: true unless no_exit
|
132
134
|
|
133
135
|
builder
|
134
136
|
end
|
@@ -166,6 +168,7 @@ module Opal
|
|
166
168
|
irb_enabled
|
167
169
|
inline_operators
|
168
170
|
enable_source_location
|
171
|
+
enable_file_source_embed
|
169
172
|
use_strict
|
170
173
|
parse_comments
|
171
174
|
esm
|
@@ -181,7 +184,21 @@ module Opal
|
|
181
184
|
if evals.any?
|
182
185
|
yield evals.join("\n"), '-e'
|
183
186
|
elsif file && (filename != '-' || evals.empty?)
|
184
|
-
|
187
|
+
return @content if @content
|
188
|
+
|
189
|
+
if file.tty?
|
190
|
+
save = true
|
191
|
+
else
|
192
|
+
begin
|
193
|
+
file.rewind
|
194
|
+
rescue Errno::ESPIPE
|
195
|
+
save = true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
content = yield(file.read, filename)
|
200
|
+
@content = content if save
|
201
|
+
content
|
185
202
|
end
|
186
203
|
end
|
187
204
|
end
|
data/lib/opal/cli_options.rb
CHANGED
@@ -181,6 +181,10 @@ module Opal
|
|
181
181
|
options[:enable_source_location] = true
|
182
182
|
end
|
183
183
|
|
184
|
+
on('--enable-file-source-embed', 'Embeds file sources to be accessed by applications.') do
|
185
|
+
options[:enable_file_source_embed] = true
|
186
|
+
end
|
187
|
+
|
184
188
|
on('--use-strict', 'Enables JavaScript\'s strict mode (i.e., adds \'use strict\'; statement)') do
|
185
189
|
options[:use_strict] = true
|
186
190
|
end
|
@@ -5,6 +5,7 @@ require 'socket'
|
|
5
5
|
require 'timeout'
|
6
6
|
require 'tmpdir'
|
7
7
|
require 'rbconfig'
|
8
|
+
require 'opal/os'
|
8
9
|
|
9
10
|
module Opal
|
10
11
|
module CliRunners
|
@@ -20,7 +21,7 @@ module Opal
|
|
20
21
|
end
|
21
22
|
|
22
23
|
def initialize(data)
|
23
|
-
builder = data[:builder]
|
24
|
+
builder = data[:builder].call
|
24
25
|
options = data[:options]
|
25
26
|
argv = data[:argv]
|
26
27
|
|
@@ -65,28 +66,32 @@ module Opal
|
|
65
66
|
def prepare_files_in(dir)
|
66
67
|
js = builder.to_s
|
67
68
|
map = builder.source_map.to_json
|
68
|
-
stack = File.
|
69
|
+
stack = File.binread("#{__dir__}/source-map-support-browser.js")
|
70
|
+
|
71
|
+
ext = builder.output_extension
|
72
|
+
module_type = ' type="module"' if builder.esm?
|
69
73
|
|
70
74
|
# Chrome can't handle huge data passed to `addScriptToEvaluateOnLoad`
|
71
75
|
# https://groups.google.com/a/chromium.org/forum/#!topic/chromium-discuss/U5qyeX_ydBo
|
72
76
|
# The only way is to create temporary files and pass them to chrome.
|
73
|
-
File.
|
74
|
-
File.
|
75
|
-
File.
|
77
|
+
File.binwrite("#{dir}/index.#{ext}", js)
|
78
|
+
File.binwrite("#{dir}/source-map-support.js", stack)
|
79
|
+
File.binwrite("#{dir}/index.html", <<~HTML)
|
76
80
|
<html><head>
|
77
81
|
<meta charset='utf-8'>
|
78
82
|
<script src='./source-map-support.js'></script>
|
79
83
|
<script>
|
84
|
+
window.opalheadlesschrome = true;
|
80
85
|
sourceMapSupport.install({
|
81
86
|
retrieveSourceMap: function(path) {
|
82
|
-
return path.endsWith('/index
|
87
|
+
return path.endsWith('/index.#{ext}') ? {
|
83
88
|
url: './index.map', map: #{map.to_json}
|
84
89
|
} : null;
|
85
90
|
}
|
86
91
|
});
|
87
92
|
</script>
|
88
93
|
</head><body>
|
89
|
-
<script src='./index
|
94
|
+
<script src='./index.#{ext}'#{module_type}></script>
|
90
95
|
</body></html>
|
91
96
|
HTML
|
92
97
|
end
|
@@ -111,7 +116,7 @@ module Opal
|
|
111
116
|
raise 'Chrome server can be started only on localhost' if chrome_host != DEFAULT_CHROME_HOST
|
112
117
|
|
113
118
|
# Disable web security with "--disable-web-security" flag to be able to do XMLHttpRequest (see test_openuri.rb)
|
114
|
-
chrome_server_cmd = %{#{
|
119
|
+
chrome_server_cmd = %{#{OS.shellescape(chrome_executable)} \
|
115
120
|
--headless \
|
116
121
|
--disable-web-security \
|
117
122
|
--remote-debugging-port=#{chrome_port} \
|
@@ -119,7 +124,7 @@ module Opal
|
|
119
124
|
|
120
125
|
chrome_pid = Process.spawn(chrome_server_cmd)
|
121
126
|
|
122
|
-
Timeout.timeout(
|
127
|
+
Timeout.timeout(30) do
|
123
128
|
loop do
|
124
129
|
break if chrome_server_running?
|
125
130
|
sleep 0.5
|
@@ -132,7 +137,7 @@ module Opal
|
|
132
137
|
puts 'Make sure that you have it installed and that its version is > 59'
|
133
138
|
exit(1)
|
134
139
|
ensure
|
135
|
-
if
|
140
|
+
if OS.windows? && chrome_pid
|
136
141
|
Process.kill('KILL', chrome_pid) unless system("taskkill /f /t /pid #{chrome_pid} >NUL 2>NUL")
|
137
142
|
elsif chrome_pid
|
138
143
|
Process.kill('HUP', chrome_pid)
|
@@ -149,8 +154,7 @@ module Opal
|
|
149
154
|
|
150
155
|
def chrome_executable
|
151
156
|
ENV['GOOGLE_CHROME_BINARY'] ||
|
152
|
-
|
153
|
-
when /bccwin|cygwin|djgpp|mingw|mswin|wince/
|
157
|
+
if OS.windows?
|
154
158
|
[
|
155
159
|
'C:/Program Files/Google/Chrome Dev/Application/chrome.exe',
|
156
160
|
'C:/Program Files/Google/Chrome/Application/chrome.exe'
|
@@ -158,7 +162,7 @@ module Opal
|
|
158
162
|
next unless File.exist? path
|
159
163
|
return path
|
160
164
|
end
|
161
|
-
|
165
|
+
elsif OS.macos?
|
162
166
|
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
|
163
167
|
else
|
164
168
|
%w[
|
@@ -106,17 +106,34 @@ CDP(options, function(client) {
|
|
106
106
|
else
|
107
107
|
`Page.handleJavaScriptDialog({accept: false})`
|
108
108
|
end
|
109
|
+
elsif `dialog.type` == 'alert' && `dialog.message` == 'opalheadlesschromeexit'
|
110
|
+
# A special case of an alert with a magic string "opalheadlesschromeexit".
|
111
|
+
# This denotes that `Kernel#exit` has been called. We would have rather used
|
112
|
+
# an exception here, but they don't bubble sometimes.
|
113
|
+
%x{
|
114
|
+
Page.handleJavaScriptDialog({accept: true});
|
115
|
+
Runtime.evaluate({ expression: "window.OPAL_EXIT_CODE" }).then(function(output) {
|
116
|
+
client.close();
|
117
|
+
if (typeof(output.result) !== "undefined" && output.result.type === "number") {
|
118
|
+
process.exit(output.result.value);
|
119
|
+
} else {
|
120
|
+
process.exit(0);
|
121
|
+
}
|
122
|
+
});
|
123
|
+
}
|
109
124
|
end
|
110
125
|
}
|
111
126
|
});
|
112
127
|
|
113
128
|
Page.loadEventFired(() => {
|
114
129
|
Runtime.evaluate({ expression: "window.OPAL_EXIT_CODE" }).then(function(output) {
|
115
|
-
client.close();
|
116
|
-
|
117
130
|
if (typeof(output.result) !== "undefined" && output.result.type === "number") {
|
131
|
+
client.close();
|
118
132
|
process.exit(output.result.value);
|
133
|
+
} else if (typeof(output.result) !== "undefined" && output.result.type === "string" && output.result.value === "noexit") {
|
134
|
+
// do nothing, we have headless chrome support enabled and there are most probably async events awaiting
|
119
135
|
} else {
|
136
|
+
client.close();
|
120
137
|
process.exit(0);
|
121
138
|
}
|
122
139
|
})
|
@@ -5,7 +5,7 @@ require 'opal/paths'
|
|
5
5
|
# The compiler runner will just output the compiled JavaScript
|
6
6
|
Opal::CliRunners::Compiler = ->(data) {
|
7
7
|
options = data[:options] || {}
|
8
|
-
builder = data.fetch(:builder)
|
8
|
+
builder = data.fetch(:builder).call
|
9
9
|
map_file = options[:map_file]
|
10
10
|
output = data.fetch(:output)
|
11
11
|
|
data/lib/opal/cli_runners/gjs.rb
CHANGED
@@ -10,10 +10,12 @@ module Opal
|
|
10
10
|
class Gjs
|
11
11
|
def self.call(data)
|
12
12
|
exe = ENV['GJS_PATH'] || 'gjs'
|
13
|
+
builder = data[:builder].call
|
13
14
|
|
14
15
|
opts = Shellwords.shellwords(ENV['GJS_OPTS'] || '')
|
16
|
+
opts.unshift('-m') if builder.esm?
|
15
17
|
|
16
|
-
SystemRunner.call(data) do |tempfile|
|
18
|
+
SystemRunner.call(data.merge(builder: -> { builder })) do |tempfile|
|
17
19
|
[exe, *opts, tempfile.path, *data[:argv]]
|
18
20
|
end
|
19
21
|
rescue Errno::ENOENT
|
@@ -9,11 +9,15 @@ module Opal
|
|
9
9
|
def self.call(data)
|
10
10
|
::MiniRacer::Platform.set_flags! :harmony
|
11
11
|
|
12
|
-
builder = data.fetch(:builder)
|
12
|
+
builder = data.fetch(:builder).call
|
13
13
|
output = data.fetch(:output)
|
14
14
|
# TODO: pass it
|
15
15
|
argv = data.fetch(:argv)
|
16
16
|
|
17
|
+
# MiniRacer doesn't like to fork. Let's build Opal first
|
18
|
+
# in a forked environment.
|
19
|
+
code = builder.to_s + "\n" + builder.source_map.to_data_uri_comment
|
20
|
+
|
17
21
|
v8 = ::MiniRacer::Context.new
|
18
22
|
v8.attach('prompt', ->(_msg = '') { $stdin.gets&.chomp })
|
19
23
|
v8.attach('console.log', ->(i) { output.print(i); output.flush })
|
@@ -22,8 +26,6 @@ module Opal
|
|
22
26
|
v8.attach('opalminiracer.exit', ->(status) { Kernel.exit(status) })
|
23
27
|
v8.attach('opalminiracer.argv', argv)
|
24
28
|
|
25
|
-
code = builder.to_s + "\n" + builder.source_map.to_data_uri_comment
|
26
|
-
|
27
29
|
v8.eval(code)
|
28
30
|
end
|
29
31
|
|