Skip to content

Commit c6ca107

Browse files
committedOct 1, 2014
Merge pull request #584 from opal/source-maps
Source maps 🌐🌍
2 parents 8cdd3ad + 2bd3ed4 commit c6ca107

24 files changed

+554
-548
lines changed
 

‎.spectator-mspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
BASE_DIR_REGEXP: '(?:opal/corelib|stdlib)'
2-
SPEC_DIR_REGEXP: '(?:spec/corelib/(?:core|language)|spec/stdlib/.*?/spec\b)'
2+
SPEC_DIR_REGEXP: '(?:spec/corelib/(?:core|language)|spec/opal|spec/stdlib/.*?/spec\b)'
33
RSPEC_COMMAND: 'bundle exec ./bin/opal-mspec'

‎lib/opal/builder.rb

+18-8
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ def build(path, options = {})
3232
end
3333

3434
def build_str source, filename, options = {}
35-
asset = processor_for(source, filename, options)
36-
requires = preload + asset.requires
35+
path = path_reader.expand(filename).to_s unless stub?(filename)
36+
asset = processor_for(source, filename, path, options)
37+
requires = preload + asset.requires + tree_requires(asset, path)
3738
requires.map { |r| process_require(r, options) }
3839
processed << asset
3940
self
@@ -48,7 +49,7 @@ def to_s
4849
end
4950

5051
def source_map
51-
processed.map(&:source_map).reduce(:+).to_s
52+
processed.map(&:source_map).reduce(:+).as_json.to_json
5253
end
5354

5455
attr_reader :processed
@@ -61,11 +62,18 @@ def source_map
6162

6263
private
6364

64-
def processor_for(source, filename, options)
65-
unless stub?(filename)
66-
full_filename = path_reader.expand(filename).to_s
67-
processor = processors.find { |p| p.match? full_filename }
65+
def tree_requires(asset, path)
66+
asset.required_trees.flat_map do |tree|
67+
base = File.expand_path(File.dirname(path))
68+
expanded = File.expand_path File.join(base, tree, '*.rb')
69+
Dir[expanded].map do |file|
70+
file.gsub(/(\.js)?(\.(?:rb|opal))/, '')[(base.size+1)..-1]
71+
end
6872
end
73+
end
74+
75+
def processor_for(source, filename, path, options)
76+
processor = processors.find { |p| p.match? path }
6977
processor ||= default_processor
7078
return processor.new(source, filename, compiler_options.merge(options))
7179
end
@@ -77,7 +85,9 @@ def process_require(filename, options)
7785

7886
source = stub?(filename) ? '' : path_reader.read(filename)
7987
raise ArgumentError, "can't find file: #{filename.inspect}" if source.nil?
80-
asset = processor_for(source, filename, options.merge(requirable: true))
88+
89+
path = path_reader.expand(filename).to_s unless stub?(filename)
90+
asset = processor_for(source, filename, path, options.merge(requirable: true))
8191
process_requires(asset, options)
8292
processed << asset
8393
end

‎lib/opal/builder_processors.rb

+27-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'opal/compiler'
22
require 'opal/erb'
3+
require 'source_map'
34

45
module Opal
56
module BuilderProcessors
@@ -13,8 +14,9 @@ def self.inherited(processor)
1314
def initialize(source, filename, options = {})
1415
@source, @filename, @options = source, filename, options
1516
@requires = []
17+
@required_trees = []
1618
end
17-
attr_reader :source, :filename, :options, :requires
19+
attr_reader :source, :filename, :options, :requires, :required_trees
1820

1921
def to_s
2022
source.to_s
@@ -29,7 +31,22 @@ def self.match_regexp
2931
end
3032

3133
def source_map
32-
'a map for: '+filename
34+
@source_map ||= begin
35+
mappings = []
36+
source_file = filename+'.js'
37+
line = source.count("\n")
38+
column = source.scan("\n[^\n]*$").size
39+
offset = ::SourceMap::Offset.new(line, column)
40+
mappings << ::SourceMap::Mapping.new(source_file, offset, offset)
41+
42+
# Ensure mappings isn't empty: https://github.com/maccman/sourcemap/issues/11
43+
unless mappings.any?
44+
zero_offset = ::SourceMap::Offset.new(0,0)
45+
mappings = [::SourceMap::Mapping.new(source_file,zero_offset,zero_offset)]
46+
end
47+
48+
::SourceMap::Map.new(mappings)
49+
end
3350
end
3451

3552
def mark_as_required(filename)
@@ -52,6 +69,10 @@ def source
5269
compiled.result
5370
end
5471

72+
def source_map
73+
compiled.source_map.map
74+
end
75+
5576
def compiled
5677
@compiled ||= begin
5778
compiler = compiler_for(@source, file: @filename)
@@ -68,6 +89,10 @@ def requires
6889
compiled.requires
6990
end
7091

92+
def required_trees
93+
compiled.required_trees
94+
end
95+
7196
def compiler_class
7297
::Opal::Compiler
7398
end

‎lib/opal/compiler.rb

+6
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,12 @@ def requires
211211
@requires ||= []
212212
end
213213

214+
# An array of trees required in this file
215+
# (typically by calling #require_tree)
216+
def required_trees
217+
@required_trees ||= []
218+
end
219+
214220
# The last sexps in method bodies, for example, need to be returned
215221
# in the compiled javascript. Due to syntax differences between
216222
# javascript any ruby, some sexps need to be handled specially. For

‎lib/opal/nodes/call.rb

+10
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,16 @@ def compile_default?
186186
end
187187
end
188188

189+
add_special :require_tree do
190+
compile_default!
191+
arg = arglist[1]
192+
if arg[0] == :str
193+
dir = File.dirname(compiler.file)
194+
compiler.required_trees << arg[1]
195+
end
196+
push fragment('')
197+
end
198+
189199
add_special :block_given? do
190200
push compiler.handle_block_given_call @sexp
191201
end

‎lib/opal/source_map.rb

+32-15
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,46 @@ def initialize(fragments, file)
1212
end
1313

1414
def map
15-
@map ||= ::SourceMap.new.tap do |map|
16-
line, column = 1, 0
17-
18-
@fragments.each do |fragment|
19-
if source_line = fragment.line
20-
map.add_mapping(
21-
:generated_line => line,
22-
:generated_col => column,
23-
:source_line => source_line,
24-
:source_col => fragment.column,
25-
:source => file
15+
@map ||= begin
16+
source_file = file+'.rb'
17+
generated_line, generated_column = 1, 0
18+
19+
mappings = @fragments.map do |fragment|
20+
mapping = nil
21+
source_line = fragment.line
22+
source_column = fragment.column
23+
source_code = fragment.code
24+
25+
if source_line and source_column
26+
source_offset = ::SourceMap::Offset.new(source_line, source_column)
27+
generated_offset = ::SourceMap::Offset.new(generated_line, generated_column)
28+
29+
mapping = ::SourceMap::Mapping.new(
30+
source_file,
31+
generated_offset,
32+
source_offset
2633
)
2734
end
2835

29-
new_lines = fragment.code.count "\n"
30-
line += new_lines
36+
new_lines = source_code.count "\n"
37+
generated_line += new_lines
3138

3239
if new_lines > 0
33-
column = fragment.code.size - (fragment.code.rindex("\n") + 1)
40+
generated_column = source_code.size - (source_code.rindex("\n") + 1)
3441
else
35-
column += fragment.code.size
42+
generated_column += source_code.size
3643
end
44+
45+
mapping
3746
end
47+
48+
# Ensure mappings isn't empty: https://github.com/maccman/sourcemap/issues/11
49+
unless mappings.any?
50+
zero_offset = ::SourceMap::Offset.new(0,0)
51+
mappings = [::SourceMap::Mapping.new(source_file,zero_offset,zero_offset)]
52+
end
53+
54+
::SourceMap::Map.new(mappings.compact)
3855
end
3956
end
4057

‎lib/opal/sprockets/processor.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def evaluate(context, locals, &block)
7373
result = builder.build_str(data, path, :prerequired => prerequired)
7474

7575
if self.class.source_map_enabled
76-
register_source_map(context.pathname, result.source_map.to_s)
76+
register_source_map(context.pathname, result.source_map)
7777
"#{result.to_s}\n//# sourceMappingURL=#{context.logical_path}.map\n"
7878
else
7979
result.to_s

‎lib/opal/sprockets/server.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def call(env)
4343
when %r{^(.*)\.rb$}
4444
source = File.read(sprockets.resolve(path_info))
4545
return app_results if source.nil?
46-
return [200, {"Content-Type" => "text/text"}, [source]]
46+
return [200, {"Content-Type" => "text/ruby"}, [source]]
4747
else
4848
app_results
4949
end

‎opal.gemspec

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
1818
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
1919
s.require_paths = ['lib']
2020

21-
s.add_dependency 'source_map'
21+
s.add_dependency 'sourcemap', '~> 0.1.0'
2222
s.add_dependency 'sprockets', '~> 2.12.1'
2323
s.add_dependency 'hike', '~> 1.2'
2424
s.add_dependency 'tilt', '~> 1.4'

‎spec/lib/builder_processors_spec.rb

+5
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,10 @@
1818
expect(processor.to_s).not_to include('Opal.modules[')
1919
end
2020
end
21+
22+
it 'fills required_trees' do
23+
processor = described_class.new('require_tree "./pippo"', '-e')
24+
expect(processor.required_trees).to eq(['./pippo'])
25+
end
2126
end
2227

‎spec/lib/builder_spec.rb

+9
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@
1010
expect(builder.build('opal').to_s).to match('(Opal);')
1111
end
1212

13+
it 'respect #require_tree calls' do
14+
begin
15+
Opal.append_path(File.expand_path('../fixtures/', __FILE__))
16+
expect(builder.build('require_tree_test').to_s).to match('required_file1')
17+
ensure
18+
Opal.instance_variable_set('@paths', nil)
19+
end
20+
end
21+
1322
describe ':stubs' do
1423
let(:options) { {stubs: ['foo']} }
1524

‎spec/lib/compiler_spec.rb

+10
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,16 @@
175175
expect(compiler.requires).to eq([__FILE__])
176176
end
177177
end
178+
179+
describe '#require_tree' do
180+
require 'pathname'
181+
let(:file) { Pathname(__FILE__).join('../fixtures/require_tree_test.rb') }
182+
183+
it 'parses and resolve #require argument' do
184+
compiler = compiler_for(file.read)
185+
expect(compiler.required_trees).to eq(['./required_tree_test'])
186+
end
187+
end
178188
end
179189

180190
def expect_compiled(*args)
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
#=require_tree ./required_tree_test
1+
require_tree './required_tree_test'
22

33
puts 5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#=require_tree ./required_tree_test
2+
3+
puts 5

‎spec/lib/sprockets/environment_spec.rb

+9-1
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@
1212
expect(env[logical_path+'.js'].source).to include('$puts(')
1313
end
1414

15+
describe 'require_tree sprockets directive' do
16+
it 'is still supported' do
17+
source = env['sprockets_require_tree_test'].source
18+
expect(source).to include('required_file1')
19+
expect(source).to include('required_file2')
20+
end
21+
end
22+
1523
describe 'require_tree helper' do
16-
it 'does something' do
24+
it 'is handled by the processor' do
1725
source = env['require_tree_test'].source
1826
expect(source).to include('required_file1')
1927
expect(source).to include('required_file2')

‎stdlib/source_map.rb

+5-63
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,5 @@
1-
require 'json'
2-
3-
require 'source_map/vlq.rb'
4-
require 'source_map/generator.rb'
5-
require 'source_map/parser.rb'
6-
7-
class SourceMap
8-
include SourceMap::Generator
9-
include SourceMap::Parser
10-
11-
# Create a new blank SourceMap
12-
#
13-
# Options may include:
14-
#
15-
# :file => String # See {#file}
16-
# :source_root => String # See {#source_root}
17-
# :generated_output => IO # See {#generated_output}
18-
#
19-
# :sources => Array[String] # See {#sources}
20-
# :names => Array[String] # See {#names}
21-
#
22-
# :version => 3 # Which version of SourceMap to use (only 3 is allowed)
23-
#
24-
def initialize(opts={})
25-
unless (remain = opts.keys - [:generated_output, :file, :source_root, :sources, :names, :version]).empty?
26-
raise ArgumentError, "Unsupported options to SourceMap.new: #{remain.inspect}"
27-
end
28-
self.generated_output = opts[:generated_output]
29-
self.file = opts[:file] || ''
30-
self.source_root = opts[:source_root] || ''
31-
self.version = opts[:version] || 3
32-
self.sources = opts[:sources] || []
33-
self.names = opts[:names] || []
34-
self.mappings = []
35-
raise "version #{opts[:version]} not supported" if version != 3
36-
end
37-
38-
# The name of the file containing the code that this SourceMap describes.
39-
# (default "")
40-
attr_accessor :file
41-
42-
# The URL/directory that contains the original source files.
43-
#
44-
# This is prefixed to the entries in ['sources']
45-
# (default "")
46-
attr_accessor :source_root
47-
48-
# The version of the SourceMap spec we're using.
49-
# (default 3)
50-
attr_accessor :version
51-
52-
# The list of sources (used during parsing/generating)
53-
# These are relative to the source_root.
54-
# (default [])
55-
attr_accessor :sources
56-
57-
# A list of names (used during parsing/generating)
58-
# (default [])
59-
attr_accessor :names
60-
61-
# A list of mapping objects.
62-
attr_accessor :mappings
63-
end
1+
require 'source_map/map'
2+
require 'source_map/mapping'
3+
require 'source_map/offset'
4+
require 'source_map/version'
5+
require 'source_map/vlq'

0 commit comments

Comments
 (0)
Please sign in to comment.