condenser 1.3 → 1.5

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/condenser/asset.rb +103 -25
  3. data/lib/condenser/build_cache.rb +23 -8
  4. data/lib/condenser/cache/file_store.rb +1 -0
  5. data/lib/condenser/cache/memory_store.rb +1 -0
  6. data/lib/condenser/cache/null_store.rb +1 -0
  7. data/lib/condenser/cache_store.rb +1 -0
  8. data/lib/condenser/context.rb +1 -0
  9. data/lib/condenser/encoding_utils.rb +2 -0
  10. data/lib/condenser/environment.rb +2 -0
  11. data/lib/condenser/errors.rb +2 -0
  12. data/lib/condenser/export.rb +11 -6
  13. data/lib/condenser/helpers/parse_helpers.rb +23 -1
  14. data/lib/condenser/manifest.rb +3 -1
  15. data/lib/condenser/minifiers/sass_minifier.rb +2 -0
  16. data/lib/condenser/minifiers/terser_minifier.rb +2 -0
  17. data/lib/condenser/minifiers/uglify_minifier.rb +2 -0
  18. data/lib/condenser/pipeline.rb +2 -0
  19. data/lib/condenser/processors/babel_processor.rb +19 -16
  20. data/lib/condenser/processors/css_media_combiner_processor.rb +7 -5
  21. data/lib/condenser/processors/js_analyzer.rb +149 -42
  22. data/lib/condenser/processors/node_processor.rb +3 -0
  23. data/lib/condenser/processors/purgecss_processor.rb +2 -0
  24. data/lib/condenser/processors/rollup_processor.rb +289 -136
  25. data/lib/condenser/resolve.rb +41 -10
  26. data/lib/condenser/server.rb +22 -20
  27. data/lib/condenser/templating_engine/ejs.rb +2 -0
  28. data/lib/condenser/templating_engine/erb.rb +2 -0
  29. data/lib/condenser/transformers/dart_sass_transformer.rb +5 -3
  30. data/lib/condenser/transformers/jst_transformer.rb +2 -0
  31. data/lib/condenser/transformers/sass/functions.rb +2 -0
  32. data/lib/condenser/transformers/sass/importer.rb +2 -0
  33. data/lib/condenser/transformers/sass.rb +2 -0
  34. data/lib/condenser/transformers/sass_transformer.rb +2 -0
  35. data/lib/condenser/transformers/svg_transformer/base.rb +2 -0
  36. data/lib/condenser/transformers/svg_transformer/tag.rb +2 -0
  37. data/lib/condenser/transformers/svg_transformer/template.rb +3 -1
  38. data/lib/condenser/transformers/svg_transformer/template_error.rb +2 -0
  39. data/lib/condenser/transformers/svg_transformer/value.rb +2 -0
  40. data/lib/condenser/transformers/svg_transformer/var_generator.rb +2 -0
  41. data/lib/condenser/transformers/svg_transformer.rb +2 -0
  42. data/lib/condenser/utils.rb +2 -0
  43. data/lib/condenser/version.rb +3 -1
  44. data/lib/condenser/writers/brotli_writer.rb +2 -0
  45. data/lib/condenser/writers/file_writer.rb +2 -0
  46. data/lib/condenser/writers/zlib_writer.rb +2 -0
  47. data/lib/condenser.rb +2 -0
  48. data/lib/rake/condensertask.rb +2 -0
  49. data/test/cache_test.rb +115 -20
  50. data/test/dependency_test.rb +51 -2
  51. data/test/manifest_test.rb +17 -2
  52. data/test/postprocessors/css_media_combiner_test.rb +9 -12
  53. data/test/preprocessor/babel_test.rb +876 -349
  54. data/test/preprocessor/js_analyzer_test.rb +208 -4
  55. data/test/processors/rollup/dynamic_import_test.rb +358 -0
  56. data/test/processors/rollup_test.rb +37 -56
  57. data/test/resolve_test.rb +14 -9
  58. data/test/server_test.rb +10 -9
  59. data/test/test_helper.rb +6 -3
  60. data/test/transformers/dart_scss_test.rb +2 -2
  61. data/test/transformers/scss_test.rb +2 -2
  62. metadata +6 -11
  63. data/lib/condenser/minifiers/package-lock.json +0 -25
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Condenser::JSAnalyzer
2
4
 
3
5
  include Condenser::ParseHelpers
@@ -14,8 +16,10 @@ class Condenser::JSAnalyzer
14
16
  @sourcefile = input[:source_file]
15
17
  @source = input[:source]
16
18
  @stack = [:main]
19
+ @previous = [[]]
17
20
 
18
- input[:export_dependencies] ||= []
21
+ input[:linked_assets] ||= Set.new
22
+ input[:export_dependencies] ||= Set.new
19
23
 
20
24
  scan_until(/\A(\/\/[^\n]*(\n|\z))*/)
21
25
  if matched
@@ -28,6 +32,7 @@ class Condenser::JSAnalyzer
28
32
  end
29
33
 
30
34
  last_postion = nil
35
+ last_stack = nil
31
36
  while !eos?
32
37
  case @stack.last
33
38
 
@@ -35,14 +40,19 @@ class Condenser::JSAnalyzer
35
40
  scan_until(/(\$\{|\`)/)
36
41
  case matched
37
42
  when '`'
38
- @stack.pop
43
+ @stack.pop if pre_match[-1] != "\\" && pre_match[-1] != "\\"
39
44
  when '${'
40
45
  @stack << :tick_statment
41
46
  end
42
47
 
43
48
  when :import
44
- scan_until(/[\"\'\`]/)
45
- input[:export_dependencies] << case matched
49
+ scan_until(/[\"\'\`\(]/)
50
+ dynamic = if matched == "("
51
+ scan_until(/[\"\'\`]/)
52
+ true
53
+ end
54
+
55
+ filename = case matched
46
56
  when "\""
47
57
  double_quoted_value
48
58
  when "'"
@@ -50,66 +60,142 @@ class Condenser::JSAnalyzer
50
60
  when '`'
51
61
  tick_quoted_value
52
62
  end
53
- scan_until(/(;|\n)/)
63
+
64
+ input[:type] = "module"
65
+ if dynamic
66
+ input[:process_dependencies] << filename
67
+ input[:linked_assets] << filename
68
+ else
69
+ input[:export_dependencies] << filename
70
+ end
71
+ scan_until(/(;|\n|\))/)
54
72
  @stack.pop
55
73
 
74
+ when :export
75
+ input[:type] = "module"
76
+ input[:exports] = true;
77
+ input[:default_export] = true if gobble(/\s+default/)
78
+ gobble(/\s+/)
79
+
80
+ if gobble(/\{/)
81
+ @stack << :brackets
82
+ @previous << []
83
+ elsif gobble(/\*/)
84
+ @stack << :export_from
85
+ else
86
+ @stack.pop
87
+ end
88
+
89
+ when :export_from
90
+ if gobble(/\s+from\s+/)
91
+ scan_until(/\"|\'/)
92
+ input[:export_dependencies] << case matched
93
+ when '"'
94
+ double_quoted_value
95
+ when "'"
96
+ single_quoted_value
97
+ end
98
+ end
99
+ @stack.pop
100
+ @stack.pop
101
+
56
102
  else
57
- scan_until(/(\/\/|\/\*|\/|\(|\)|\{|\}|\"|\'|\`|export|import|\z)/)
103
+ scan_until(/(\/\/|\/\*|\/|\(|\)|\{|\}|\"|\'|\`|export(?![[:alnum:]])|import(?![[:alnum:]])|\z)/)
58
104
 
59
105
  case matched
60
106
  when '//'
61
107
  scan_until(/(\n|\z)/)
108
+ @previous.last << :single_line_comment
62
109
  when '/*'
63
110
  scan_until(/\*\//)
111
+ @previous.last << :multi_line_comment
64
112
  when '"'
65
113
  double_quoted_value
114
+ @previous.last << :double_quoted_value
66
115
  when "'"
67
116
  single_quoted_value
117
+ @previous.last << :single_quoted_value
68
118
  when '`'
69
119
  @stack << :tick_value
70
120
  when '/'
71
- if match_index = @source.rindex(/(\w+|\)|\])\s*\//, @index)
72
- match = @source.match(/(\w+|\)|\])\s*\//, match_index)
73
- if match[0].length + match_index != @index
121
+ if pre_match.match(/(\W|\A)(void|typeof|return|export)\z/)
122
+ regex_value
123
+ elsif match_index = @source.rindex(/(\w+|\)|\])(\s*)\//m, @index)
124
+ match = @source.match(/(\w+|\)|\])(\s*)\//, match_index)
125
+
126
+ x = @previous.last.dup
127
+ while match[2]&.index("\n") && [:single_line_comment, :multi_line_comment].include?(x.last)
128
+ case x.last
129
+ when :single_line_comment
130
+ match_index = @source.rindex(/[^\n]*\/\/[^\n]*\s*/m, match.begin(2))
131
+ match = @source.match(/(\w+|\)|\])\s*\//, match_index)
132
+ when :multi_line_comment
133
+ match_index = @source.rindex(/\/\*/m, match.begin(2))
134
+ match = @source.match(/(\w+|\)|\])\s*\//, match_index)
135
+ end
136
+ end
137
+
138
+ if @previous.last.last == :loop && match[1] =~ /\)\z/
139
+ regex_value
140
+ elsif %w(void typeof return export).include?(match[1])
141
+ regex_value
142
+ elsif match[0].length + match_index != @index
143
+ regex_value
144
+ elsif match[1].strip.empty?
74
145
  regex_value
75
146
  end
76
147
  else
77
148
  regex_value
78
149
  end
79
150
  when '('
80
- @stack.push :parenthesis
151
+ @stack << if pre_match =~ /\W*(for|while)\s*\z/
152
+ :loop
153
+ else
154
+ :parenthesis
155
+ end
81
156
  when ')'
82
- raise unexptected_token(")") if @stack.last != :parenthesis
83
- @stack.pop
157
+ raise unexptected_token(")") if @stack.last != :parenthesis && @stack.last != :loop
158
+ @previous.last << @stack.pop
84
159
  when '{'
85
160
  @stack.push :brackets
161
+ @previous << []
86
162
  when '}'
87
163
  case @stack.last
88
- when :brackets, :tick_statment
164
+ when :tick_statment
89
165
  @stack.pop
166
+ when :brackets
167
+ @stack.pop
168
+ @previous.pop
169
+ @previous.last << :brackets
170
+ if @stack.last == :export
171
+ @stack.pop
172
+ @stack << :export_from if peek(/\s+from/i)
173
+ end
90
174
  else
91
175
  raise unexptected_token("}")
92
176
  end
93
177
  when 'export'
94
178
  if @stack.last == :main
95
- input[:exports] = true;
96
- input[:default_export] = true if next_word == 'default'
179
+ @stack << :export
97
180
  end
98
181
  when 'import'
99
- if @stack.last == :main
100
- @stack << :import
101
- end
182
+ @stack << :import
102
183
  else
103
184
  @stack.pop
104
185
  end
105
186
  end
106
187
 
107
- if last_postion == @index
188
+ if last_postion == @index && last_stack == @stack.last
189
+ syntax_error = Condenser::SyntaxError.new("Error parsing JS file with JSAnalyzer")
190
+ syntax_error.instance_variable_set(:@path, @sourcefile)
108
191
  raise Condenser::SyntaxError, "Error parsing JS file with JSAnalyzer"
109
192
  else
110
193
  last_postion = @index
194
+ last_stack = @stack.last
111
195
  end
112
196
  end
197
+
198
+ raise Condenser::SyntaxError, "Unexpected EOF" if !@stack.empty? && @stack.last != :main
113
199
  end
114
200
 
115
201
  def unexptected_token(token)
@@ -119,26 +205,26 @@ class Condenser::JSAnalyzer
119
205
 
120
206
  message = "Unexpected token #{token} #{@sourcefile} #{lineno.to_s.rjust(4)}:#{(@index-start)}"
121
207
  message << "\n#{lineno.to_s.rjust(4)}: " << @source[start..uptop]
122
- message << "\n #{'-'* (@index-1-start)}#{'^'*(@matched.length)}"
208
+ message << "\n #{'-'* ([@index-1-start,1].max)}#{'^'*([@matched.length,1].max)}"
123
209
  message << "\n"
124
- Condenser::SyntaxError.new(message)
210
+
211
+ syntax_error = Condenser::SyntaxError.new(message)
212
+ syntax_error.instance_variable_set(:@path, @sourcefile)
213
+ syntax_error
125
214
  end
126
215
 
127
216
  def double_quoted_value
128
- ret_value = ""
217
+ ret_value = String.new
129
218
 
130
- while scan_until(/[\"\n]/)
131
- if matched == "\n"
219
+ while scan_until(/[\"\n\\]/)
220
+ case matched
221
+ when "\n"
132
222
  raise unexptected_token("\\n")
133
- elsif matched == "\""
134
- if pre_match[-1] != "\\"
135
- ret_value << pre_match
136
- return ret_value
137
- else
138
- ret_value << pre_match << "\\\""
139
- end
140
-
141
-
223
+ when "\\"
224
+ ret_value << pre_match << matched << gobble(1)
225
+ when "\""
226
+ ret_value << pre_match
227
+ return ret_value
142
228
  else
143
229
  ret_value << match
144
230
  end
@@ -146,7 +232,7 @@ class Condenser::JSAnalyzer
146
232
  end
147
233
 
148
234
  def single_quoted_value
149
- ret_value = ""
235
+ ret_value = String.new
150
236
 
151
237
  while scan_until(/[\'\n]/)
152
238
  if matched == "\n"
@@ -161,7 +247,7 @@ class Condenser::JSAnalyzer
161
247
  end
162
248
 
163
249
  def tick_quoted_value
164
- ret_value = ""
250
+ ret_value = String.new
165
251
 
166
252
  while scan_until(/[\`]/)
167
253
  if matched == "\`" && pre_match[-1] != "\\"
@@ -174,16 +260,37 @@ class Condenser::JSAnalyzer
174
260
  end
175
261
 
176
262
  def regex_value
177
- ret_value = ""
263
+ ret_value = String.new
178
264
 
179
- while scan_until(/\//)
180
- if matched == "/" && pre_match[-1] != "\\"
181
- ret_value << pre_match
182
- return ret_value
183
- else
184
- ret_value << pre_match
265
+ regex_stack = ['/']
266
+ while !regex_stack.empty?
267
+
268
+ scan_until(/[\/\[\]]/)
269
+ escaped = pre_match[-1] == "\\" && pre_match[-2] != "\\"
270
+ ret_value << pre_match
271
+ case matched
272
+ when "["
273
+ regex_stack << '[' if !escaped && regex_stack.last != "["
274
+ ret_value << matched
275
+ when "]"
276
+ regex_stack.pop if !escaped && regex_stack.last == "["
277
+ ret_value << matched
278
+ when "/"
279
+ # From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Regular_expressions/Character_class
280
+ #
281
+ # The lexical grammar does a very rough parse of regex literals, so
282
+ # that it does not end the regex literal at a / character which appears
283
+ # within a character class. This means /[/]/ is valid without needing
284
+ # to escape the /.
285
+ if !escaped && regex_stack.last != "[" && regex_stack.pop != "/"
286
+ raise unexptected_token("/")
287
+ else
288
+ ret_value << matched
289
+ end
185
290
  end
186
291
  end
292
+
293
+ ret_value
187
294
  end
188
295
 
189
296
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'tempfile'
2
4
  require 'open3'
3
5
 
@@ -60,6 +62,7 @@ class Condenser
60
62
  lineno = lines[0][/\((\d+):\d+\)$/, 1] if lines[0]
61
63
  lineno ||= 1
62
64
  error.set_backtrace(["#{source_file}:#{lineno}"] + caller)
65
+ error.instance_variable_set(:@path, source_file)
63
66
  error
64
67
  end
65
68
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
2
4
 
3
5
  class Condenser::PurgeCSSProcessor < Condenser::NodeProcessor