/
builder_processors.rb
170 lines (136 loc) · 4.21 KB
/
builder_processors.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# frozen_string_literal: true
require 'opal/compiler'
require 'opal/erb'
module Opal
module BuilderProcessors
class Processor
def initialize(source, filename, options = {})
source += "\n" unless source.end_with?("\n")
@source, @filename, @options = source, filename, options.dup
@cache = @options.delete(:cache) { Opal.cache }
@requires = []
@required_trees = []
@autoloads = []
end
attr_reader :source, :filename, :options, :requires, :required_trees, :autoloads
def to_s
source.to_s
end
class << self
attr_reader :extensions
def handles(*extensions)
@extensions = extensions
matches = extensions.join('|')
matches = "(#{matches})" unless extensions.size == 1
@match_regexp = Regexp.new "\\.#{matches}#{REGEXP_END}"
::Opal::Builder.register_processor(self, extensions)
nil
end
def match?(other)
other.is_a?(String) && other.match(match_regexp)
end
def match_regexp
@match_regexp || raise(NotImplementedError)
end
end
def mark_as_required(filename)
"Opal.loaded([#{filename.to_s.inspect}]);"
end
end
class JsProcessor < Processor
handles :js
ManualFragment = Struct.new(:line, :column, :code, :source_map_name)
def source_map
@source_map ||= begin
manual_fragments = source.each_line.with_index.map do |line_source, index|
column = line_source.index(/\S/)
line = index + 1
ManualFragment.new(line, column, line_source, nil)
end
::Opal::SourceMap::File.new(manual_fragments, filename, source)
end
end
def source
@source.to_s + mark_as_required(@filename)
end
end
class RubyProcessor < Processor
handles :rb, :opal
def source
compiled.result
end
def source_map
compiled.source_map
end
def compiled
@compiled ||= Opal::Cache.fetch(@cache, cache_key) do
compiler = compiler_for(@source, file: @filename)
compiler.compile
compiler
end
end
def cache_key
[self.class, @filename, @source, @options]
end
def compiler_for(source, options = {})
::Opal::Compiler.new(source, @options.merge(options))
end
def requires
compiled.requires
end
def required_trees
compiled.required_trees
end
def autoloads
compiled.autoloads
end
# Also catch a files with missing extensions and nil.
def self.match?(other)
super || File.extname(other.to_s) == ''
end
end
# This handler is for files named ".opalerb", which ought to
# first get compiled to Ruby code using ERB, then with Opal.
# Unlike below processors, OpalERBProcessor can be used to
# compile templates, which will in turn output HTML. Take
# a look at docs/templates.md to understand this subsystem
# better.
class OpalERBProcessor < RubyProcessor
handles :opalerb
def initialize(*args)
super
@source = prepare(@source, @filename)
end
def requires
['erb'] + super
end
private
def prepare(source, path)
::Opal::ERB::Compiler.new(source, path).prepared_source
end
end
# This handler is for files named ".rb.erb", which ought to
# first get preprocessed via ERB, then via Opal.
class RubyERBProcessor < RubyProcessor
handles :"rb.erb"
def compiled
@compiled ||= begin
@source = ::ERB.new(@source.to_s).result
compiler = compiler_for(@source, file: @filename)
compiler.compile
compiler
end
end
end
# This handler is for files named ".js.erb", which ought to
# first get preprocessed via ERB, then served verbatim as JS.
class ERBProcessor < Processor
handles :erb
def source
result = ::ERB.new(@source.to_s).result
module_name = ::Opal::Compiler.module_name(@filename)
"Opal.modules[#{module_name.inspect}] = function() {#{result}};"
end
end
end
end