Skip to content

Commit

Permalink
Add opal/simple_server for simple non cached apps
Browse files Browse the repository at this point in the history
The quickest way to get up and running with Opal:

  rackup -ropal -ropal/simple_server -b 'Opal.append_path("app"); run Opal::SimpleServer.new'
  • Loading branch information
elia committed Oct 9, 2016
1 parent be0f68d commit 87caa64
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -53,6 +53,7 @@ Whitespace conventions:
- Added `JS::Error` error class that can be used to catch any JS error.
- Added `Method#source_location` and `Method#comments`.
- Added a deprecation API that can be set to raise on deprecation with: `Opal.raise_on_deprecation = true`
- Added `Opal::SimpleServer` as the quickest way to get up and running with Opal: `rackup -ropal -ropal/simple_server -b 'Opal.append_path("app"); run Opal::SimpleServer.new'`


### Changed
Expand Down
1 change: 1 addition & 0 deletions lib/opal.rb
Expand Up @@ -12,4 +12,5 @@
# in any JavaScript environment.
module Opal
autoload :Server, 'opal/server'
autoload :SimpleServer, 'opal/simple_server'
end
99 changes: 99 additions & 0 deletions lib/opal/simple_server.rb
@@ -0,0 +1,99 @@
require 'opal/deprecations'

# Opal::SimpleServer is a very basic Rack server for Opal assets, it relies on
# Opal::Builder and Ruby corelib/stdlib. It's meant to be used just for local
# development.
#
# For a more complete implementation see opal-sprockets (Rubygems) or
# opal-webpack (NPM).
#
# @example (CLI)
# rackup -ropal -ropal/simple_server -b 'Opal.append_path("app"); run Opal::SimpleServer.new'
class Opal::SimpleServer
require 'set'
require 'erb'

def initialize(options = {})
@prefix = options.fetch(:prefix, 'assets')
@main = options.fetch(:main, 'application')
yield self if block_given?
freeze
end

attr_accessor :main, :index_path

# @deprecated
# It's here for compatibility with Opal::Sprockets::Server
def append_path(path)
Opal.deprecation 'Please use `Opal.append_path(path)` instead.'
Opal.append_path path
end

def call(env)
case env['PATH_INFO']
when %r{\A/#{@prefix}/(.*)\.map\z}
path, cache_invalidator = $1.split('?', 2)
call_map(path)
when %r{\A/#{@prefix}/(.*)\z}
path, cache_invalidator = $1.split('?', 2)
call_asset(path)
else call_index
end
end

def call_asset(path)
asset = fetch_asset(path)
[
200,
{ 'Content-Type' => 'application/javascript',
'X-SourceMap' => "/#{@prefix}/#{path}.map#{cache_invalidator}}" },
[asset[:data]]
]
end

def call_map(path)
asset = fetch_asset(path)
[
200,
{ 'Content-Type' => 'application/json' },
[asset[:map]]
]
end

def fetch_asset(path)
builder = Opal::Builder.new
builder.build(path.gsub(/(\.(?:rb|js|opal))*\z/, ''))
{
data: builder.to_s,
map: builder.source_map.to_json
}
end

def javascript_include_tag(path)
%{<script src="/#{@prefix}/#{path}.js#{cache_invalidator}"></script>}
end

def cache_invalidator
"?#{Time.now.to_i}"
end

def call_index
if @index_path
contents = File.read(@index_path)
html = ERB.new(contents).result binding
else
html = <<-HTML
<!doctype html>
<html>
<head>
<meta charset="utf8">
#{javascript_include_tag(main)}
</head>
<body></body>
</html>
HTML
end
[200, {'Content-Type' => 'text/html'}, [html]]
end
end

45 changes: 45 additions & 0 deletions spec/lib/simple_server_spec.rb
@@ -0,0 +1,45 @@
require 'lib/spec_helper'
require 'rack/test'

describe Opal::SimpleServer do
include Rack::Test::Methods

attr_accessor :app

before do
Opal.append_path "#{__dir__}/fixtures"
self.app = described_class.new(main: 'console')
end

it 'serves opal assets' do
response = get '/assets/console.js'
expect(response.body).to eq(Opal::Builder.build('console').to_s)
end

it 'serves index for all non opal paths' do
%w[/ /foo /foo/bar/baz].each do |path|
response = get path
expect(response.body).to include('<html>')
expect(response.body).to include('<script')
expect(response.body).to include('src="/assets/console.js')
expect(response.headers['Content-type']).to eq('text/html')
end
end

it 'serves the source map for the compiled asset' do
response = get '/assets/console.map'
expect(response.body).to eq(Opal::Builder.build('console').source_map.to_json)
end

it 'takes a :prefix option to set the assets prefix' do
self.app = described_class.new(main: 'opal', prefix: 'foo')
expect(get('/foo/console.js').body).to eq(Opal::Builder.build('console').to_s)
self.app = described_class.new(main: 'opal', prefix: '/foo')
expect(get('/foo/console.js').body).to eq(Opal::Builder.build('console').to_s)
end

it 'takes a :main option to set the main asset' do
self.app = described_class.new(main: 'foo')
expect(get('/').body).to include('src="/assets/foo.js')
end
end

0 comments on commit 87caa64

Please sign in to comment.