actionview_precompiler 0.2.3 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/lib/actionview_precompiler/ast_parser/jruby.rb +58 -4
- data/lib/actionview_precompiler/ast_parser/prism.rb +155 -0
- data/lib/actionview_precompiler/ast_parser/ripper.rb +206 -0
- data/lib/actionview_precompiler/ast_parser/ruby26.rb +69 -5
- data/lib/actionview_precompiler/ast_parser.rb +25 -4
- data/lib/actionview_precompiler/controller_parser.rb +13 -0
- data/lib/actionview_precompiler/controller_scanner.rb +48 -0
- data/lib/actionview_precompiler/helper_parser.rb +13 -0
- data/lib/actionview_precompiler/helper_scanner.rb +34 -0
- data/lib/actionview_precompiler/precompiler.rb +47 -73
- data/lib/actionview_precompiler/render_parser.rb +144 -42
- data/lib/actionview_precompiler/template_file.rb +22 -0
- data/lib/actionview_precompiler/template_loader.rb +36 -0
- data/lib/actionview_precompiler/template_parser.rb +9 -17
- data/lib/actionview_precompiler/template_scanner.rb +44 -0
- data/lib/actionview_precompiler/version.rb +1 -1
- data/lib/actionview_precompiler.rb +31 -17
- metadata +15 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58e32739b081a1318cdd69ea8bb88286f7921c1028e3081738ecda419d68b25f
|
4
|
+
data.tar.gz: 6309ed0e14d1261ea9f184c8f0522d6cf38b1c3a045a09537f01b8a13841bcae
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4476e8391491ca650bb1dbf3f946160c631a506a9abe7f20760e07ba6e1c94229e443579d7961b17301a6487a819d0c942f1c0261040b6110db45a6d3fda0ca
|
7
|
+
data.tar.gz: d7abd504555763b20cfbbd9afec57a67a1caf032fb1a4e3f571ca9d12f8fc58e72570c2437e42f26ec7e3f6d746cc383a35a90b38dc0758f4a0a3f919a37023f
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# ActionviewPrecompiler
|
2
2
|
|
3
|
-
Provides eager loading of
|
3
|
+
Provides eager loading of Action View templates.
|
4
4
|
|
5
5
|
This optimization aims to improve cold render times and to allow more memory to be shared via CoW on forking web servers.
|
6
6
|
|
@@ -31,7 +31,7 @@ We determine the locals passed to each template by parsing all templates looking
|
|
31
31
|
Right now this assumes every template with the same `virtual_path` takes the same locals (there may be smarter options, we just aren't doing them).
|
32
32
|
A curse/blessing/actually still a curse of this approach is that mis-predicting render calls doesn't cause any issues, it just wastes RAM.
|
33
33
|
|
34
|
-
Templates are half-compiled using standard
|
34
|
+
Templates are half-compiled using standard Action View handlers, so this should work for erb/builder/haml/whatever.
|
35
35
|
Parsing is done using either Ruby 2.6's `RubyVM::AbstractSyntaxTree` or JRuby's parser.
|
36
36
|
|
37
37
|
## Installation
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module ActionviewPrecompiler
|
2
|
-
module
|
2
|
+
module JRubyASTParser
|
3
3
|
class Node
|
4
4
|
def self.wrap(node)
|
5
5
|
if org::jruby::ast::Node === node
|
@@ -25,14 +25,37 @@ module ActionviewPrecompiler
|
|
25
25
|
def string?; org::jruby::ast::StrNode === @node; end
|
26
26
|
def symbol?; org::jruby::ast::SymbolNode === @node; end
|
27
27
|
|
28
|
+
def variable_reference?
|
29
|
+
org::jruby::ast::InstVarNode === @node ||
|
30
|
+
org::jruby::ast::GlobalVarNode === @node ||
|
31
|
+
org::jruby::ast::ClassVarNode === @node
|
32
|
+
end
|
33
|
+
|
34
|
+
def vcall?
|
35
|
+
org::jruby::ast::VCallNode === @node;
|
36
|
+
end
|
37
|
+
|
38
|
+
def call?
|
39
|
+
org::jruby::ast::CallNode === @node;
|
40
|
+
end
|
41
|
+
|
42
|
+
def variable_name
|
43
|
+
self[0][0]
|
44
|
+
end
|
45
|
+
|
46
|
+
def call_method_name
|
47
|
+
self.last.first
|
48
|
+
end
|
49
|
+
|
28
50
|
def argument_nodes
|
29
|
-
@node.args_node.to_a[0...@node.args_node.size].map do |arg|
|
51
|
+
@node.args_node.children.to_a[0...@node.args_node.size].map do |arg|
|
30
52
|
self.class.wrap(arg)
|
31
53
|
end
|
32
54
|
end
|
33
55
|
|
34
56
|
def to_hash
|
35
57
|
@node.pairs.each_with_object({}) do |pair, object|
|
58
|
+
return nil if pair.key == nil
|
36
59
|
object[self.class.wrap(pair.key)] = self.class.wrap(pair.value)
|
37
60
|
end
|
38
61
|
end
|
@@ -41,6 +64,14 @@ module ActionviewPrecompiler
|
|
41
64
|
@node.value
|
42
65
|
end
|
43
66
|
|
67
|
+
def variable_name
|
68
|
+
@node.name.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
def call_method_name
|
72
|
+
@node.name.to_s
|
73
|
+
end
|
74
|
+
|
44
75
|
def to_symbol
|
45
76
|
@node.name
|
46
77
|
end
|
@@ -53,8 +84,17 @@ module ActionviewPrecompiler
|
|
53
84
|
end
|
54
85
|
end
|
55
86
|
|
56
|
-
|
57
|
-
|
87
|
+
extend self
|
88
|
+
|
89
|
+
METHODS_TO_PARSE = %i(render render_to_string layout)
|
90
|
+
|
91
|
+
def parse_render_nodes(code)
|
92
|
+
node = Node.wrap(JRuby.parse(code))
|
93
|
+
|
94
|
+
renders = extract_render_nodes(node)
|
95
|
+
renders.group_by(&:first).collect do |method, nodes|
|
96
|
+
[ method, nodes.collect { |v| v[1] } ]
|
97
|
+
end.to_h
|
58
98
|
end
|
59
99
|
|
60
100
|
def node?(node)
|
@@ -64,5 +104,19 @@ module ActionviewPrecompiler
|
|
64
104
|
def fcall?(node, name)
|
65
105
|
node.fcall_named?(name)
|
66
106
|
end
|
107
|
+
|
108
|
+
def extract_render_nodes(node)
|
109
|
+
return [] unless node?(node)
|
110
|
+
renders = node.children.flat_map { |c| extract_render_nodes(c) }
|
111
|
+
|
112
|
+
method_name = render_call?(node)
|
113
|
+
renders << [method_name, node] if method_name
|
114
|
+
|
115
|
+
renders
|
116
|
+
end
|
117
|
+
|
118
|
+
def render_call?(node)
|
119
|
+
METHODS_TO_PARSE.detect { |m| fcall?(node, m) }
|
120
|
+
end
|
67
121
|
end
|
68
122
|
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "prism"
|
4
|
+
|
5
|
+
module ActionviewPrecompiler
|
6
|
+
module PrismASTParser
|
7
|
+
# This error is raised whenever an assumption we made wasn't met by the AST.
|
8
|
+
class CompilationError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
# Each call object is responsible for holding a list of arguments and should
|
12
|
+
# respond to a single #arguments_node method that returns an array of
|
13
|
+
# arguments.
|
14
|
+
class RenderCall
|
15
|
+
attr_reader :argument_nodes
|
16
|
+
|
17
|
+
def initialize(argument_nodes)
|
18
|
+
@argument_nodes = argument_nodes
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# This class represents a node in the tree that is returned by the parser
|
23
|
+
# that corresponds to an argument to a render call, or a child of one of
|
24
|
+
# those nodes.
|
25
|
+
class RenderNode
|
26
|
+
attr_reader :node
|
27
|
+
|
28
|
+
def initialize(node)
|
29
|
+
@node = node
|
30
|
+
end
|
31
|
+
|
32
|
+
def call?
|
33
|
+
node.is_a?(Prism::CallNode)
|
34
|
+
end
|
35
|
+
|
36
|
+
def hash?
|
37
|
+
node.is_a?(Prism::HashNode) || node.is_a?(Prism::KeywordHashNode)
|
38
|
+
end
|
39
|
+
|
40
|
+
def string?
|
41
|
+
node.is_a?(Prism::StringNode)
|
42
|
+
end
|
43
|
+
|
44
|
+
def symbol?
|
45
|
+
node.is_a?(Prism::SymbolNode)
|
46
|
+
end
|
47
|
+
|
48
|
+
def variable_reference?
|
49
|
+
case node.type
|
50
|
+
when :class_variable_read_node, :instance_variable_read_node,
|
51
|
+
:global_variable_read_node, :local_variable_read_node
|
52
|
+
true
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def vcall?
|
59
|
+
node.is_a?(Prism::CallNode) && node.variable_call?
|
60
|
+
end
|
61
|
+
|
62
|
+
def call_method_name
|
63
|
+
node.message
|
64
|
+
end
|
65
|
+
|
66
|
+
def variable_name
|
67
|
+
case node.type
|
68
|
+
when :class_variable_read_node, :instance_variable_read_node,
|
69
|
+
:global_variable_read_node, :local_variable_read_node
|
70
|
+
node.name.name
|
71
|
+
when :call_node
|
72
|
+
node.message
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Converts the node into a hash where the keys and values are nodes. This
|
77
|
+
# will raise an error if the hash doesn't match the format we expect or
|
78
|
+
# if the hash contains any splats.
|
79
|
+
def to_hash
|
80
|
+
if hash?
|
81
|
+
if node.elements.all? { |assoc| assoc.is_a?(Prism::AssocNode) && assoc.key.is_a?(Prism::SymbolNode) }
|
82
|
+
node.elements.to_h { |assoc| [RenderNode.new(assoc.key), RenderNode.new(assoc.value)] }
|
83
|
+
end
|
84
|
+
else
|
85
|
+
raise CompilationError, "Unexpected node type: #{node.inspect}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Converts the node into a string value. Only handles plain string
|
90
|
+
# content, and will raise an error if the node contains interpolation.
|
91
|
+
def to_string
|
92
|
+
if string?
|
93
|
+
node.unescaped
|
94
|
+
else
|
95
|
+
raise CompilationError, "Unexpected node type: #{node.inspect}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Converts the node into a symbol value. Only handles labels and plain
|
100
|
+
# symbols, and will raise an error if the node contains interpolation.
|
101
|
+
def to_symbol
|
102
|
+
if symbol?
|
103
|
+
node.unescaped.to_sym
|
104
|
+
else
|
105
|
+
raise CompilationError, "Unexpected node type: #{node.inspect}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# This visitor is responsible for visiting the parsed tree and extracting
|
111
|
+
# out the render calls. After visiting the tree, the #render_calls method
|
112
|
+
# will return the hash expected by the #parse_render_nodes method.
|
113
|
+
class RenderVisitor < Prism::Visitor
|
114
|
+
MESSAGE = /\A(render|render_to_string|layout)\z/
|
115
|
+
|
116
|
+
attr_reader :render_calls
|
117
|
+
|
118
|
+
def initialize
|
119
|
+
@render_calls = Hash.new { |hash, key| hash[key] = [] }
|
120
|
+
end
|
121
|
+
|
122
|
+
def visit_call_node(node)
|
123
|
+
if node.name.match?(MESSAGE) && !node.receiver && node.arguments
|
124
|
+
args =
|
125
|
+
node.arguments.arguments.map do |arg|
|
126
|
+
if arg.is_a?(Prism::ParenthesesNode) && arg.body && arg.body.body.length == 1
|
127
|
+
RenderNode.new(arg.body.body.first)
|
128
|
+
else
|
129
|
+
RenderNode.new(arg)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
render_calls[node.name.to_sym] << RenderCall.new(args)
|
134
|
+
end
|
135
|
+
|
136
|
+
super
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Main entrypoint into this AST parser variant. It's responsible for
|
141
|
+
# returning a hash of render calls. The keys are the method names, and the
|
142
|
+
# values are arrays of call objects.
|
143
|
+
def self.parse_render_nodes(code)
|
144
|
+
visitor = RenderVisitor.new
|
145
|
+
result = Prism.parse(code)
|
146
|
+
|
147
|
+
if result.success?
|
148
|
+
result.value.accept(visitor)
|
149
|
+
visitor.render_calls
|
150
|
+
else
|
151
|
+
raise CompilationError, "Unable to parse the template"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ripper"
|
4
|
+
|
5
|
+
module ActionviewPrecompiler
|
6
|
+
module RipperASTParser
|
7
|
+
class Node < ::Array
|
8
|
+
attr_reader :type
|
9
|
+
|
10
|
+
def initialize(type, arr, opts = {})
|
11
|
+
@type = type
|
12
|
+
super(arr)
|
13
|
+
end
|
14
|
+
|
15
|
+
def children
|
16
|
+
to_a
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
typeinfo = type && type != :list ? ':' + type.to_s + ', ' : ''
|
21
|
+
's(' + typeinfo + map(&:inspect).join(", ") + ')'
|
22
|
+
end
|
23
|
+
|
24
|
+
def fcall?
|
25
|
+
type == :command || type == :fcall
|
26
|
+
end
|
27
|
+
|
28
|
+
def fcall_named?(name)
|
29
|
+
fcall? &&
|
30
|
+
self[0].type == :@ident &&
|
31
|
+
self[0][0] == name
|
32
|
+
end
|
33
|
+
|
34
|
+
def argument_nodes
|
35
|
+
raise unless fcall?
|
36
|
+
return [] if self[1].nil?
|
37
|
+
if self[1].last == false || self[1].last.type == :vcall
|
38
|
+
self[1][0...-1]
|
39
|
+
else
|
40
|
+
self[1][0..-1]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def string?
|
45
|
+
type == :string_literal
|
46
|
+
end
|
47
|
+
|
48
|
+
def variable_reference?
|
49
|
+
type == :var_ref
|
50
|
+
end
|
51
|
+
|
52
|
+
def vcall?
|
53
|
+
type == :vcall
|
54
|
+
end
|
55
|
+
|
56
|
+
def call?
|
57
|
+
type == :call
|
58
|
+
end
|
59
|
+
|
60
|
+
def variable_name
|
61
|
+
self[0][0]
|
62
|
+
end
|
63
|
+
|
64
|
+
def call_method_name
|
65
|
+
self.last.first
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_string
|
69
|
+
raise unless string?
|
70
|
+
raise "fixme" unless self[0].type == :string_content
|
71
|
+
raise "fixme" unless self[0][0].type == :@tstring_content
|
72
|
+
self[0][0][0]
|
73
|
+
end
|
74
|
+
|
75
|
+
def hash?
|
76
|
+
type == :bare_assoc_hash || type == :hash
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_hash
|
80
|
+
if type == :bare_assoc_hash
|
81
|
+
hash_from_body(self[0])
|
82
|
+
elsif type == :hash && self[0] == nil
|
83
|
+
{}
|
84
|
+
elsif type == :hash && self[0].type == :assoclist_from_args
|
85
|
+
hash_from_body(self[0][0])
|
86
|
+
else
|
87
|
+
raise "not a hash? #{inspect}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def hash_from_body(body)
|
92
|
+
body.map do |hash_node|
|
93
|
+
return nil if hash_node.type != :assoc_new
|
94
|
+
|
95
|
+
[hash_node[0], hash_node[1]]
|
96
|
+
end.to_h
|
97
|
+
end
|
98
|
+
|
99
|
+
def symbol?
|
100
|
+
type == :@label || type == :symbol_literal
|
101
|
+
end
|
102
|
+
|
103
|
+
def to_symbol
|
104
|
+
if type == :@label && self[0] =~ /\A(.+):\z/
|
105
|
+
$1.to_sym
|
106
|
+
elsif type == :symbol_literal && self[0].type == :symbol && self[0][0].type == :@ident
|
107
|
+
self[0][0][0].to_sym
|
108
|
+
else
|
109
|
+
raise "not a symbol?: #{self.inspect}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class NodeParser < ::Ripper
|
115
|
+
PARSER_EVENTS.each do |event|
|
116
|
+
arity = PARSER_EVENT_TABLE[event]
|
117
|
+
|
118
|
+
if /_new\z/ =~ event.to_s && arity == 0
|
119
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
120
|
+
def on_#{event}(*args)
|
121
|
+
Node.new(:list, args, lineno: lineno(), column: column())
|
122
|
+
end
|
123
|
+
eof
|
124
|
+
elsif /_add(_.+)?\z/ =~ event.to_s
|
125
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
126
|
+
begin; undef on_#{event}; rescue NameError; end
|
127
|
+
def on_#{event}(list, item)
|
128
|
+
list.push(item)
|
129
|
+
list
|
130
|
+
end
|
131
|
+
eof
|
132
|
+
else
|
133
|
+
module_eval(<<-eof, __FILE__, __LINE__ + 1)
|
134
|
+
begin; undef on_#{event}; rescue NameError; end
|
135
|
+
def on_#{event}(*args)
|
136
|
+
Node.new(:#{event}, args, lineno: lineno(), column: column())
|
137
|
+
end
|
138
|
+
eof
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
SCANNER_EVENTS.each do |event|
|
143
|
+
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
144
|
+
def on_#{event}(tok)
|
145
|
+
Node.new(:@#{event}, [tok], lineno: lineno(), column: column())
|
146
|
+
end
|
147
|
+
End
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
class RenderCallParser < NodeParser
|
152
|
+
attr_reader :render_calls
|
153
|
+
|
154
|
+
METHODS_TO_PARSE = %w(render render_to_string layout)
|
155
|
+
|
156
|
+
def initialize(*args)
|
157
|
+
super
|
158
|
+
|
159
|
+
@render_calls = []
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
|
164
|
+
def on_fcall(name, *args)
|
165
|
+
on_render_call(super)
|
166
|
+
end
|
167
|
+
|
168
|
+
def on_command(name, *args)
|
169
|
+
on_render_call(super)
|
170
|
+
end
|
171
|
+
|
172
|
+
def on_render_call(node)
|
173
|
+
METHODS_TO_PARSE.each do |method|
|
174
|
+
if node.fcall_named?(method)
|
175
|
+
@render_calls << [method, node]
|
176
|
+
return node
|
177
|
+
end
|
178
|
+
end
|
179
|
+
node
|
180
|
+
end
|
181
|
+
|
182
|
+
def on_arg_paren(content)
|
183
|
+
content
|
184
|
+
end
|
185
|
+
|
186
|
+
def on_paren(content)
|
187
|
+
if (content.size == 1) && (content.is_a?(Array))
|
188
|
+
content.first
|
189
|
+
else
|
190
|
+
content
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
extend self
|
196
|
+
|
197
|
+
def parse_render_nodes(code)
|
198
|
+
parser = RenderCallParser.new(code)
|
199
|
+
parser.parse
|
200
|
+
|
201
|
+
parser.render_calls.group_by(&:first).collect do |method, nodes|
|
202
|
+
[ method.to_sym, nodes.collect { |v| v[1] } ]
|
203
|
+
end.to_h
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module ActionviewPrecompiler
|
2
|
-
module
|
2
|
+
module Ruby26ASTParser
|
3
3
|
class Node
|
4
4
|
def self.wrap(node)
|
5
5
|
if RubyVM::AbstractSyntaxTree::Node === node
|
@@ -24,7 +24,13 @@ module ActionviewPrecompiler
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def argument_nodes
|
27
|
-
children[1].
|
27
|
+
if children[1].array?
|
28
|
+
children[1].children[0...-1]
|
29
|
+
elsif children[1].block_pass?
|
30
|
+
children[1].children[0].children[0...-1]
|
31
|
+
else
|
32
|
+
raise "can't call argument_nodes on #{inspect}"
|
33
|
+
end
|
28
34
|
end
|
29
35
|
|
30
36
|
def array?
|
@@ -39,12 +45,36 @@ module ActionviewPrecompiler
|
|
39
45
|
type == :HASH
|
40
46
|
end
|
41
47
|
|
48
|
+
def block_pass?
|
49
|
+
type == :BLOCK_PASS
|
50
|
+
end
|
51
|
+
|
42
52
|
def string?
|
43
53
|
type == :STR && String === children[0]
|
44
54
|
end
|
45
55
|
|
56
|
+
def variable_reference?
|
57
|
+
type == :IVAR || type == :GVAR || type == :CVAR
|
58
|
+
end
|
59
|
+
|
60
|
+
def variable_name
|
61
|
+
children[0].to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def vcall?
|
65
|
+
type == :VCALL
|
66
|
+
end
|
67
|
+
|
68
|
+
def call?
|
69
|
+
type == :CALL
|
70
|
+
end
|
71
|
+
|
72
|
+
def call_method_name
|
73
|
+
children[1].to_s
|
74
|
+
end
|
75
|
+
|
46
76
|
def symbol?
|
47
|
-
type == :LIT && Symbol === children[0]
|
77
|
+
(type == :SYM) || (type == :LIT && Symbol === children[0])
|
48
78
|
end
|
49
79
|
|
50
80
|
def to_hash
|
@@ -52,7 +82,9 @@ module ActionviewPrecompiler
|
|
52
82
|
if list.nil?
|
53
83
|
{}
|
54
84
|
else
|
55
|
-
list.children[0..-2].each_slice(2).to_h
|
85
|
+
hash = list.children[0..-2].each_slice(2).to_h
|
86
|
+
return nil if hash.key?(nil)
|
87
|
+
hash
|
56
88
|
end
|
57
89
|
end
|
58
90
|
|
@@ -68,7 +100,7 @@ module ActionviewPrecompiler
|
|
68
100
|
fcall? &&
|
69
101
|
children[0] == name &&
|
70
102
|
children[1] &&
|
71
|
-
children[1].array?
|
103
|
+
(children[1].array? || (children[1].block_pass? && children[1].children[0].array?))
|
72
104
|
end
|
73
105
|
|
74
106
|
private
|
@@ -78,6 +110,18 @@ module ActionviewPrecompiler
|
|
78
110
|
end
|
79
111
|
end
|
80
112
|
|
113
|
+
extend self
|
114
|
+
|
115
|
+
METHODS_TO_PARSE = %i(render render_to_string layout)
|
116
|
+
|
117
|
+
def parse_render_nodes(code)
|
118
|
+
renders = extract_render_nodes(parse(code))
|
119
|
+
|
120
|
+
renders.group_by(&:first).collect do |method, nodes|
|
121
|
+
[ method, nodes.collect { |v| v[1] } ]
|
122
|
+
end.to_h
|
123
|
+
end
|
124
|
+
|
81
125
|
def parse(code)
|
82
126
|
Node.wrap(RubyVM::AbstractSyntaxTree.parse(code))
|
83
127
|
end
|
@@ -89,5 +133,25 @@ module ActionviewPrecompiler
|
|
89
133
|
def fcall?(node, name)
|
90
134
|
node.fcall_named?(name)
|
91
135
|
end
|
136
|
+
|
137
|
+
def extract_render_nodes(root)
|
138
|
+
renders = []
|
139
|
+
queue = [root]
|
140
|
+
|
141
|
+
while node = queue.shift
|
142
|
+
node.children.each do |child|
|
143
|
+
queue << child if node?(child)
|
144
|
+
end
|
145
|
+
|
146
|
+
method_name = render_call?(node)
|
147
|
+
renders << [method_name, node] if method_name
|
148
|
+
end
|
149
|
+
|
150
|
+
renders
|
151
|
+
end
|
152
|
+
|
153
|
+
def render_call?(node)
|
154
|
+
METHODS_TO_PARSE.detect { |m| fcall?(node, m) }
|
155
|
+
end
|
92
156
|
end
|
93
157
|
end
|
@@ -1,5 +1,26 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
module ActionviewPrecompiler
|
2
|
+
parser = ENV["PRECOMPILER_PARSER"]
|
3
|
+
|
4
|
+
begin
|
5
|
+
require "prism"
|
6
|
+
parser ||= "prism"
|
7
|
+
rescue LoadError
|
8
|
+
parser ||= "jruby" if RUBY_ENGINE == 'jruby'
|
9
|
+
parser ||= "rubyvm_ast" if RUBY_ENGINE == 'ruby'
|
10
|
+
end
|
11
|
+
|
12
|
+
case parser
|
13
|
+
when "rubyvm_ast"
|
14
|
+
require "actionview_precompiler/ast_parser/ruby26"
|
15
|
+
ASTParser = Ruby26ASTParser
|
16
|
+
when "jruby"
|
17
|
+
require "actionview_precompiler/ast_parser/jruby"
|
18
|
+
ASTParser = JRubyASTParser
|
19
|
+
when "prism"
|
20
|
+
require "actionview_precompiler/ast_parser/prism"
|
21
|
+
ASTParser = PrismASTParser
|
22
|
+
else
|
23
|
+
require "actionview_precompiler/ast_parser/ripper"
|
24
|
+
ASTParser = RipperASTParser
|
25
|
+
end
|
5
26
|
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ActionviewPrecompiler
|
2
|
+
class ControllerParser
|
3
|
+
def initialize(filename)
|
4
|
+
@filename = filename
|
5
|
+
end
|
6
|
+
|
7
|
+
def render_calls
|
8
|
+
src = File.read(@filename)
|
9
|
+
return [] unless src.include?("render")
|
10
|
+
RenderParser.new(src, from_controller: true).render_calls
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActionviewPrecompiler
|
2
|
+
class ControllerScanner
|
3
|
+
attr_reader :controller_dir
|
4
|
+
|
5
|
+
def initialize(controller_dir)
|
6
|
+
@controller_dir = controller_dir
|
7
|
+
end
|
8
|
+
|
9
|
+
def template_renders
|
10
|
+
template_renders = []
|
11
|
+
|
12
|
+
each_controller do |path, fullpath|
|
13
|
+
parser = ControllerParser.new(fullpath)
|
14
|
+
|
15
|
+
if path =~ /\A(.*)_controller\.rb\z/
|
16
|
+
controller_prefix = $1
|
17
|
+
end
|
18
|
+
|
19
|
+
parser.render_calls.each do |render_call|
|
20
|
+
virtual_path = render_call.virtual_path
|
21
|
+
|
22
|
+
unless virtual_path.include?("/")
|
23
|
+
next unless controller_prefix
|
24
|
+
|
25
|
+
virtual_path = "#{controller_prefix}/#{virtual_path}"
|
26
|
+
end
|
27
|
+
|
28
|
+
locals = render_call.locals_keys.map(&:to_s).sort
|
29
|
+
|
30
|
+
template_renders << [virtual_path, locals]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
template_renders.uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def each_controller
|
40
|
+
Dir["**/*.rb", base: controller_dir].sort.map do |file|
|
41
|
+
fullpath = File.expand_path(file, controller_dir)
|
42
|
+
next if File.directory?(fullpath)
|
43
|
+
|
44
|
+
yield file, fullpath
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|