Skip to content

Commit

Permalink
Make sure the Server CLI Runner can pick up changes in sources
Browse files Browse the repository at this point in the history
* We change the API here, now the provided builder is a proc that
  returns a new Builder instance which will always correspond to an
  up-to-date code.
* As a result - now the server runner will supply an up-to-date code
  each refresh.
* Also refactors the Server CLI Runner to use Opal::SimpleServer.
* Ensure files are rewinded as they need to be re-read at each refresh
  • Loading branch information
hmdne authored and elia committed Oct 5, 2022
1 parent 49e72e1 commit ec886e6
Show file tree
Hide file tree
Showing 11 changed files with 55 additions and 61 deletions.
31 changes: 17 additions & 14 deletions lib/opal/cli.rb
Expand Up @@ -48,14 +48,12 @@ def initialize(options = nil)

@requires.unshift('opal') unless options.delete(:skip_opal_require)

@compiler_options = Hash[
*compiler_option_names.map do |option|
key = option.to_sym
next unless options.key? key
value = options.delete(key)
[key, value]
end.compact.flatten
]
@compiler_options = compiler_option_names.map do |option|
key = option.to_sym
next unless options.key? key
value = options.delete(key)
[key, value]
end.compact.to_h

raise ArgumentError, 'no libraries to compile' if @lib_only && @requires.empty?
raise ArgumentError, 'no runnable code provided (evals or file)' if @evals.empty? && @file.nil? && !@lib_only
Expand All @@ -68,6 +66,16 @@ def run
return debug_source_map if @debug_source_map
return run_repl if @repl

rbrequires.each { |file| require file }

runner = self.runner

# Some runners may need to use a dynamic builder, that is,
# a builder that will try to build the entire package every time
# a page is loaded - for example a Server runner that needs to
# rerun if files are changed.
builder = proc { create_builder }

@exit_status = runner.call(
options: runner_options,
output: output,
Expand All @@ -90,13 +98,7 @@ def run_repl

attr_reader :exit_status

def builder
@builder ||= create_builder
end

def create_builder
rbrequires.each(&Kernel.method(:require))

builder = Opal::Builder.new(
stubs: stubs,
compiler_options: compiler_options,
Expand Down Expand Up @@ -181,6 +183,7 @@ def evals_or_file
if evals.any?
yield evals.join("\n"), '-e'
elsif file && (filename != '-' || evals.empty?)
file.rewind
yield file.read, filename
end
end
Expand Down
13 changes: 7 additions & 6 deletions lib/opal/cli_runners.rb
Expand Up @@ -10,7 +10,8 @@ module Opal
# - `output`: an IO-like object responding to `#write` and `#puts`
# - `argv`: is the arguments vector coming from the CLI that is being
# forwarded to the program
# - `builder`: the current instance of Opal::Builder
# - `builder`: a proc returning a new instance of Opal::Builder so it
# can be re-created and pick up the most up-to-date sources
#
# Runners can be registered using `#register_runner(name, runner)`.
#
Expand All @@ -21,7 +22,7 @@ class RunnerError < StandardError
@register = {}

def self.[](name)
@register[name.to_sym]
@register[name.to_sym]&.call
end

# @private
Expand All @@ -44,18 +45,18 @@ def self.to_h
def self.register_runner(name, runner, path = nil)
autoload runner, path if path

if runner.respond_to?(:call)
self[name] = runner
if runner.respond_to? :call
self[name] = -> { runner }
else
self[name] = ->(data) { const_get(runner).call(data) }
self[name] = -> { const_get(runner) }
end

nil
end

# Alias a runner name
def self.alias_runner(new_name, old_name)
self[new_name.to_sym] = self[old_name.to_sym]
self[new_name.to_sym] = -> { self[old_name.to_sym] }

nil
end
Expand Down
2 changes: 1 addition & 1 deletion lib/opal/cli_runners/chrome.rb
Expand Up @@ -20,7 +20,7 @@ def self.call(data)
end

def initialize(data)
builder = data[:builder]
builder = data[:builder].call
options = data[:options]
argv = data[:argv]

Expand Down
2 changes: 1 addition & 1 deletion lib/opal/cli_runners/compiler.rb
Expand Up @@ -5,7 +5,7 @@
# The compiler runner will just output the compiled JavaScript
Opal::CliRunners::Compiler = ->(data) {
options = data[:options] || {}
builder = data.fetch(:builder)
builder = data.fetch(:builder).call
map_file = options[:map_file]
output = data.fetch(:output)

Expand Down
5 changes: 3 additions & 2 deletions lib/opal/cli_runners/gjs.rb
Expand Up @@ -10,11 +10,12 @@ module CliRunners
class Gjs
def self.call(data)
exe = ENV['GJS_PATH'] || 'gjs'
builder = data[:builder].call

opts = Shellwords.shellwords(ENV['GJS_OPTS'] || '')
opts.unshift('-m') if data[:builder].esm?
opts.unshift('-m') if builder.esm?

SystemRunner.call(data) do |tempfile|
SystemRunner.call(data.merge(builder: -> { builder })) do |tempfile|
[exe, *opts, tempfile.path, *data[:argv]]
end
rescue Errno::ENOENT
Expand Down
2 changes: 1 addition & 1 deletion lib/opal/cli_runners/mini_racer.rb
Expand Up @@ -9,7 +9,7 @@ class MiniRacer
def self.call(data)
::MiniRacer::Platform.set_flags! :harmony

builder = data.fetch(:builder)
builder = data.fetch(:builder).call
output = data.fetch(:output)
# TODO: pass it
argv = data.fetch(:argv)
Expand Down
41 changes: 13 additions & 28 deletions lib/opal/cli_runners/server.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'opal/simple_server'

module Opal
module CliRunners
class Server
Expand All @@ -11,9 +13,8 @@ def self.call(data)

def initialize(data)
options = data[:options] || {}
builder = data[:builder]
@builder = data[:builder]

@code = builder.to_s + "\n" + builder.source_map.to_data_uri_comment
@argv = data[:argv] || []

@output = data[:output] || $stdout
Expand All @@ -25,7 +26,7 @@ def initialize(data)
@static_folder = File.expand_path(@static_folder) if @static_folder
end

attr_reader :output, :port, :server, :static_folder, :code, :argv
attr_reader :output, :port, :server, :static_folder, :builder, :argv

def run
unless argv.empty?
Expand All @@ -35,7 +36,7 @@ def run
require 'rack'
require 'logger'

app = build_app(code)
app = build_app(builder)

@server = Rack::Server.start(
app: app,
Expand All @@ -49,8 +50,8 @@ def exit_status
nil
end

def build_app(source)
app = App.new(source)
def build_app(builder)
app = App.new(builder: builder, main: 'cli-runner')

if static_folder
not_found = [404, {}, []]
Expand All @@ -65,30 +66,14 @@ def build_app(source)
app
end

class App
def initialize(source)
@source = source
class App < SimpleServer
def initialize(options = {})
@builder = options.fetch(:builder)
super
end

BODY = <<-HTML
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<script src="/cli_runner.js"></script>
</head>
</html>
HTML

def call(env)
case env['PATH_INFO']
when '/'
[200, { 'Content-Type' => 'text/html' }, [BODY]]
when '/cli_runner.js'
[200, { 'Content-Type' => 'text/javascript' }, [@source]]
else
[404, {}, ['not found']]
end
def builder(_)
@builder.call
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/opal/cli_runners/system_runner.rb
Expand Up @@ -13,7 +13,7 @@
# @yieldreturn command [Array<String>] the command to be used in the system call
SystemRunner = ->(data, &block) {
options = data[:options] || {}
builder = data.fetch(:builder)
builder = data.fetch(:builder).call
output = data.fetch(:output)

env = options.fetch(:env, {})
Expand Down
11 changes: 8 additions & 3 deletions lib/opal/simple_server.rb
Expand Up @@ -11,6 +11,8 @@
#
# @example (CLI)
# rackup -ropal -ropal/simple_server -b 'Opal.append_path("app"); run Opal::SimpleServer.new'
# ... or use the Server runner ...
# opal -Rserver app.rb
class Opal::SimpleServer
require 'set'
require 'erb'
Expand Down Expand Up @@ -54,13 +56,16 @@ def call_js(path)
]
end

def fetch_asset(path)
def builder(path)
builder = Opal::Builder.new
builder.build(path.gsub(/(\.(?:rb|js|opal))*\z/, ''))
end

def fetch_asset(path)
builder = self.builder(path)
{
data: builder.to_s,
map: builder.source_map,
source: builder.source_for(path),
map: builder.source_map
}
end

Expand Down
5 changes: 2 additions & 3 deletions spec/lib/cli_runners/server_spec.rb
Expand Up @@ -15,11 +15,10 @@ def app
expect(options[:Port]).to eq(1234)
end

builder = Opal::Builder.new
builder.build_str("puts 123", "app.rb")
builder = -> { Opal::Builder.new.build_str("puts 123", "app.rb") }
described_class.call(builder: builder, options: {port: 1234})

get '/cli_runner.js'
get '/assets/cli_runner.js'
expect(last_response.body).to include(".$puts(123)")
end
end
2 changes: 1 addition & 1 deletion spec/lib/cli_spec.rb
Expand Up @@ -138,7 +138,7 @@
let(:options) { {:gems => [gem_name], :evals => ['']} }

it "adds the gem's lib paths to Opal.path" do
builder = cli.builder
builder = cli.create_builder

spec = Gem::Specification.find_by_name(gem_name)
spec.require_paths.each do |require_path|
Expand Down

0 comments on commit ec886e6

Please sign in to comment.