opal-sprockets 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.
- data/.gitignore +2 -0
- data/Gemfile +2 -0
- data/README.md +28 -0
- data/lib/opal/sprockets/environment.rb +23 -0
- data/lib/opal/sprockets/parser.rb +84 -0
- data/lib/opal/sprockets/processor.rb +85 -0
- data/lib/opal/sprockets/server.rb +141 -0
- data/lib/opal/sprockets/source_map_header.rb +22 -0
- data/lib/opal/sprockets/version.rb +5 -0
- data/lib/opal/sprockets.rb +4 -0
- data/lib/opal-sprockets.rb +1 -0
- data/opal-sprockets.gemspec +21 -0
- metadata +104 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# opal-sprocets
|
2
|
+
|
3
|
+
Adds sprockets support for Opal
|
4
|
+
|
5
|
+
## License
|
6
|
+
|
7
|
+
(The MIT License)
|
8
|
+
|
9
|
+
Copyright (C) 2013 by Adam Beynon
|
10
|
+
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
13
|
+
in the Software without restriction, including without limitation the rights
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
16
|
+
furnished to do so, subject to the following conditions:
|
17
|
+
|
18
|
+
The above copyright notice and this permission notice shall be included in
|
19
|
+
all copies or substantial portions of the Software.
|
20
|
+
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
27
|
+
THE SOFTWARE.
|
28
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'sprockets'
|
2
|
+
|
3
|
+
module Opal
|
4
|
+
|
5
|
+
# Environment is a subclass of Sprockets::Environment which already has our opal
|
6
|
+
# load paths loaded. This makes it easy for stand-alone rack apps, or test runners
|
7
|
+
# that have opal load paths ready to use. You can also add an existing gem's lib
|
8
|
+
# directory to our load path to use real gems inside your opal environment.
|
9
|
+
#
|
10
|
+
# If you are running rails, then you just need opal-rails instead, which will
|
11
|
+
# do this for you.
|
12
|
+
class Environment < ::Sprockets::Environment
|
13
|
+
def initialize *args
|
14
|
+
super
|
15
|
+
Opal.paths.each { |p| append_path p }
|
16
|
+
end
|
17
|
+
|
18
|
+
def use_gem gem_name
|
19
|
+
append_path File.join(Gem::Specification.find_by_name(gem_name).gem_dir, 'lib')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'opal/parser'
|
2
|
+
|
3
|
+
module Opal
|
4
|
+
|
5
|
+
# A subclass of Opal::Parser used exclusively by opal-sprockets to handle
|
6
|
+
# require statements automatically. We basically intercept any top level
|
7
|
+
# require statement, remove it from the code generation, and then pass it
|
8
|
+
# off to our sprockets context to handle it. This lets us use the ruby
|
9
|
+
# require syntax instead of having to use sprockets style comments.
|
10
|
+
class SprocketsParser < Parser
|
11
|
+
|
12
|
+
def self.parse source, options = {}
|
13
|
+
self.new.parse source, options
|
14
|
+
end
|
15
|
+
|
16
|
+
# Holds an array of paths which this file 'requires'.
|
17
|
+
# @return Array<String>
|
18
|
+
attr_reader :requires
|
19
|
+
|
20
|
+
def parse source, options = {}
|
21
|
+
@requires = []
|
22
|
+
@dynamic_require_severity = (options[:dynamic_require_severity] || :error)
|
23
|
+
super source, options
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_call sexp, level
|
27
|
+
if sexp[1] == :require
|
28
|
+
return handle_require sexp[2][1]
|
29
|
+
end
|
30
|
+
|
31
|
+
super sexp, level
|
32
|
+
end
|
33
|
+
|
34
|
+
def handle_require(sexp)
|
35
|
+
str = handle_require_sexp sexp
|
36
|
+
@requires << str unless str.nil? if @requires
|
37
|
+
fragment("", sexp)
|
38
|
+
end
|
39
|
+
|
40
|
+
def handle_require_sexp(sexp)
|
41
|
+
type = sexp.shift
|
42
|
+
|
43
|
+
if type == :str
|
44
|
+
return sexp[0]
|
45
|
+
elsif type == :call
|
46
|
+
recv, meth, args = sexp
|
47
|
+
parts = args[1..-1].map { |s| handle_require_sexp s }
|
48
|
+
|
49
|
+
if recv == [:const, :File]
|
50
|
+
if meth == :expand_path
|
51
|
+
return handle_expand_path(*parts)
|
52
|
+
elsif meth == :join
|
53
|
+
return handle_expand_path parts.join("/")
|
54
|
+
elsif meth == :dirname
|
55
|
+
return handle_expand_path parts[0].split("/")[0...-1].join("/")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
case @dynamic_require_severity
|
62
|
+
when :error
|
63
|
+
error "Cannot handle dynamic require"
|
64
|
+
when :warning
|
65
|
+
warning "Cannot handle dynamic require"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def handle_expand_path(path, base = '')
|
70
|
+
"#{base}/#{path}".split("/").inject([]) do |p, part|
|
71
|
+
if part == ''
|
72
|
+
# we had '//', so ignore
|
73
|
+
elsif part == '..'
|
74
|
+
p.pop
|
75
|
+
else
|
76
|
+
p << part
|
77
|
+
end
|
78
|
+
|
79
|
+
p
|
80
|
+
end.join "/"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'sprockets'
|
2
|
+
require 'opal/sprockets/parser'
|
3
|
+
|
4
|
+
module Opal
|
5
|
+
|
6
|
+
# Proccess using Sprockets
|
7
|
+
#
|
8
|
+
# Opal.process('opal-jquery') # => String
|
9
|
+
def self.process asset
|
10
|
+
Environment.new[asset].to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
# The Processor class is used to make ruby files (with rb or opal extensions)
|
14
|
+
# available to any sprockets based server. Processor will then get passed any
|
15
|
+
# ruby source file to build. There are some options you can override globally
|
16
|
+
# which effect how certain ruby features are handled:
|
17
|
+
#
|
18
|
+
# * method_missing_enabled [true by default]
|
19
|
+
# * optimized_operators_enabled [true by default]
|
20
|
+
# * arity_check_enabled [false by default]
|
21
|
+
# * const_missing_enabled [true by default]
|
22
|
+
# * dynamic_require_severity [true by default]
|
23
|
+
# * source_map_enabled [true by default]
|
24
|
+
# * irb_enabled [false by default]
|
25
|
+
#
|
26
|
+
class Processor < Tilt::Template
|
27
|
+
self.default_mime_type = 'application/javascript'
|
28
|
+
|
29
|
+
def self.engine_initialized?
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
attr_accessor :method_missing_enabled
|
35
|
+
attr_accessor :optimized_operators_enabled
|
36
|
+
attr_accessor :arity_check_enabled
|
37
|
+
attr_accessor :const_missing_enabled
|
38
|
+
attr_accessor :dynamic_require_severity
|
39
|
+
attr_accessor :source_map_enabled
|
40
|
+
attr_accessor :irb_enabled
|
41
|
+
end
|
42
|
+
|
43
|
+
self.method_missing_enabled = true
|
44
|
+
self.optimized_operators_enabled = true
|
45
|
+
self.arity_check_enabled = false
|
46
|
+
self.const_missing_enabled = true
|
47
|
+
self.dynamic_require_severity = :error # :error, :warning or :ignore
|
48
|
+
self.source_map_enabled = true
|
49
|
+
self.irb_enabled = false
|
50
|
+
|
51
|
+
def initialize_engine
|
52
|
+
require_template_library 'opal'
|
53
|
+
end
|
54
|
+
|
55
|
+
def prepare
|
56
|
+
end
|
57
|
+
|
58
|
+
def evaluate(context, locals, &block)
|
59
|
+
options = {
|
60
|
+
:method_missing => self.class.method_missing_enabled,
|
61
|
+
:optimized_operators => self.class.optimized_operators_enabled,
|
62
|
+
:arity_check => self.class.arity_check_enabled,
|
63
|
+
:const_missing => self.class.const_missing_enabled,
|
64
|
+
:dynamic_require_severity => self.class.dynamic_require_severity,
|
65
|
+
:source_map_enabled => self.class.source_map_enabled,
|
66
|
+
:irb => self.class.irb_enabled,
|
67
|
+
:file => context.logical_path,
|
68
|
+
:source_file => context.pathname.to_s,
|
69
|
+
}
|
70
|
+
|
71
|
+
parser = Opal::SprocketsParser.new
|
72
|
+
result = parser.parse data, options
|
73
|
+
|
74
|
+
parser.requires.each { |r| context.require_asset r }
|
75
|
+
result
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
Tilt.register 'rb', Opal::Processor
|
81
|
+
Sprockets.register_engine '.rb', Opal::Processor
|
82
|
+
|
83
|
+
Tilt.register 'opal', Opal::Processor
|
84
|
+
Sprockets.register_engine '.opal', Opal::Processor
|
85
|
+
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'rack/showexceptions'
|
2
|
+
require 'opal/source_map'
|
3
|
+
require 'opal/sprockets/source_map_header'
|
4
|
+
require 'opal/sprockets/environment'
|
5
|
+
require 'erb'
|
6
|
+
|
7
|
+
module Opal
|
8
|
+
|
9
|
+
class SourceMapServer
|
10
|
+
def initialize sprockets
|
11
|
+
@sprockets = sprockets
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :sprockets
|
15
|
+
|
16
|
+
attr_writer :prefix
|
17
|
+
|
18
|
+
def prefix
|
19
|
+
@prefix ||= '/__opal_source_maps__'
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<#{self.class}:#{object_id}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
path = env['PATH_INFO'].gsub(/^\/|\.js\.map$/, '')
|
28
|
+
asset = sprockets[path]
|
29
|
+
return [404, {}, []] if asset.nil?
|
30
|
+
|
31
|
+
source = asset.to_s
|
32
|
+
map = Opal::SourceMap.new(source, asset.pathname.to_s)
|
33
|
+
|
34
|
+
return [200, {"Content-Type" => "text/json"}, [map.to_s]]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Server
|
39
|
+
|
40
|
+
attr_accessor :debug, :index_path, :main, :public_dir, :sprockets
|
41
|
+
|
42
|
+
def initialize debug = true
|
43
|
+
@public_dir = '.'
|
44
|
+
@sprockets = Environment.new
|
45
|
+
@debug = debug
|
46
|
+
|
47
|
+
yield self if block_given?
|
48
|
+
create_app
|
49
|
+
end
|
50
|
+
|
51
|
+
def append_path path
|
52
|
+
@sprockets.append_path path
|
53
|
+
end
|
54
|
+
|
55
|
+
def use_gem gem_name
|
56
|
+
@sprockets.use_gem gem_name
|
57
|
+
end
|
58
|
+
|
59
|
+
def create_app
|
60
|
+
server, sprockets = self, @sprockets
|
61
|
+
|
62
|
+
@app = Rack::Builder.app do
|
63
|
+
use Rack::ShowExceptions
|
64
|
+
map('/assets') { run sprockets }
|
65
|
+
map(server.source_maps.prefix) { run server.source_maps }
|
66
|
+
use Index, server
|
67
|
+
run Rack::Directory.new(server.public_dir)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def source_maps
|
72
|
+
@source_maps ||= SourceMapServer.new(@sprockets)
|
73
|
+
end
|
74
|
+
|
75
|
+
def call(env)
|
76
|
+
@app.call env
|
77
|
+
end
|
78
|
+
|
79
|
+
class Index
|
80
|
+
|
81
|
+
def initialize(app, server)
|
82
|
+
@app = app
|
83
|
+
@server = server
|
84
|
+
@index_path = server.index_path
|
85
|
+
end
|
86
|
+
|
87
|
+
def call(env)
|
88
|
+
if %w[/ /index.html].include? env['PATH_INFO']
|
89
|
+
[200, { 'Content-Type' => 'text/html' }, [html]]
|
90
|
+
else
|
91
|
+
@app.call env
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the html content for the root path. Supports ERB
|
96
|
+
def html
|
97
|
+
source = if @index_path
|
98
|
+
raise "index does not exist: #{@index_path}" unless File.exist?(@index_path)
|
99
|
+
File.read @index_path
|
100
|
+
elsif File.exist? 'index.html'
|
101
|
+
File.read 'index.html'
|
102
|
+
elsif File.exist? 'index.html.erb'
|
103
|
+
File.read 'index.html.erb'
|
104
|
+
else
|
105
|
+
SOURCE
|
106
|
+
end
|
107
|
+
|
108
|
+
::ERB.new(source).result binding
|
109
|
+
end
|
110
|
+
|
111
|
+
def javascript_include_tag source
|
112
|
+
if @server.debug
|
113
|
+
assets = @server.sprockets[source].to_a
|
114
|
+
|
115
|
+
raise "Cannot find asset: #{source}" if assets.empty?
|
116
|
+
|
117
|
+
scripts = assets.map do |a|
|
118
|
+
%Q{<script src="/assets/#{ a.logical_path }?body=1"></script>}
|
119
|
+
end
|
120
|
+
|
121
|
+
scripts.join "\n"
|
122
|
+
else
|
123
|
+
"<script src=\"/assets/#{source}.js\"></script>"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
SOURCE = <<-HTML
|
128
|
+
<!DOCTYPE html>
|
129
|
+
<html>
|
130
|
+
<head>
|
131
|
+
<title>Opal Server</title>
|
132
|
+
</head>
|
133
|
+
<body>
|
134
|
+
<%= javascript_include_tag @server.main %>
|
135
|
+
</body>
|
136
|
+
</html>
|
137
|
+
HTML
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'sprockets/server'
|
2
|
+
|
3
|
+
module Sprockets
|
4
|
+
module Server
|
5
|
+
|
6
|
+
# Adds the source map header to all sprocket responses for assets
|
7
|
+
# with a .rb or .opal extension in the extension chain.
|
8
|
+
def headers_with_opal_source_maps(env, asset, length)
|
9
|
+
headers_without_opal_source_maps(env, asset, length).tap do |headers|
|
10
|
+
if asset.pathname.to_s =~ /\.(rb|opal)\b/
|
11
|
+
headers['X-SourceMap'] = '/__opal_source_maps__/'+asset.logical_path + '.map'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Poor man's alias_method_chain :)
|
17
|
+
alias headers_without_opal_source_maps headers
|
18
|
+
alias headers headers_with_opal_source_maps
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'opal/sprockets'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/opal/sprockets/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'opal-sprockets'
|
6
|
+
s.version = Opal::Sprockets::VERSION
|
7
|
+
s.author = 'Adam Beynon'
|
8
|
+
s.email = 'adam.beynon@gmail.com'
|
9
|
+
s.homepage = 'http://opalrb.org'
|
10
|
+
s.summary = 'Sprockets support for opal'
|
11
|
+
s.description = 'Sprockets suppoer for opal.'
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.require_paths = ['lib']
|
16
|
+
|
17
|
+
s.add_dependency 'sprockets'
|
18
|
+
s.add_dependency 'opal', '~> 0.4.0'
|
19
|
+
|
20
|
+
s.add_development_dependency 'rake'
|
21
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: opal-sprockets
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Adam Beynon
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-16 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: sprockets
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: opal
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.4.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.4.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: Sprockets suppoer for opal.
|
63
|
+
email: adam.beynon@gmail.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- .gitignore
|
69
|
+
- Gemfile
|
70
|
+
- README.md
|
71
|
+
- lib/opal-sprockets.rb
|
72
|
+
- lib/opal/sprockets.rb
|
73
|
+
- lib/opal/sprockets/environment.rb
|
74
|
+
- lib/opal/sprockets/parser.rb
|
75
|
+
- lib/opal/sprockets/processor.rb
|
76
|
+
- lib/opal/sprockets/server.rb
|
77
|
+
- lib/opal/sprockets/source_map_header.rb
|
78
|
+
- lib/opal/sprockets/version.rb
|
79
|
+
- opal-sprockets.gemspec
|
80
|
+
homepage: http://opalrb.org
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.8.23
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Sprockets support for opal
|
104
|
+
test_files: []
|