condenser 1.2 → 1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/condenser/asset.rb +69 -35
  3. data/lib/condenser/build_cache.rb +22 -9
  4. data/lib/condenser/context.rb +9 -25
  5. data/lib/condenser/helpers/parse_helpers.rb +8 -1
  6. data/lib/condenser/manifest.rb +3 -1
  7. data/lib/condenser/pipeline.rb +8 -3
  8. data/lib/condenser/processors/babel_processor.rb +9 -15
  9. data/lib/condenser/processors/css_media_combiner_processor.rb +81 -0
  10. data/lib/condenser/processors/js_analyzer.rb +41 -8
  11. data/lib/condenser/processors/node_processor.rb +1 -0
  12. data/lib/condenser/processors/purgecss_processor.rb +6 -4
  13. data/lib/condenser/processors/rollup_processor.rb +38 -36
  14. data/lib/condenser/resolve.rb +27 -6
  15. data/lib/condenser/templating_engine/ejs.rb +1 -1
  16. data/lib/condenser/transformers/dart_sass_transformer.rb +285 -0
  17. data/lib/condenser/transformers/jst_transformer.rb +67 -17
  18. data/lib/condenser/transformers/sass/functions.rb +133 -0
  19. data/lib/condenser/transformers/sass/importer.rb +48 -0
  20. data/lib/condenser/transformers/sass.rb +4 -0
  21. data/lib/condenser/transformers/sass_transformer.rb +124 -281
  22. data/lib/condenser/transformers/svg_transformer/base.rb +26 -0
  23. data/lib/condenser/transformers/svg_transformer/tag.rb +54 -0
  24. data/lib/condenser/transformers/svg_transformer/template.rb +151 -0
  25. data/lib/condenser/transformers/svg_transformer/template_error.rb +2 -0
  26. data/lib/condenser/transformers/svg_transformer/value.rb +13 -0
  27. data/lib/condenser/transformers/svg_transformer/var_generator.rb +10 -0
  28. data/lib/condenser/transformers/svg_transformer.rb +19 -0
  29. data/lib/condenser/version.rb +1 -1
  30. data/lib/condenser.rb +17 -5
  31. data/test/cache_test.rb +157 -18
  32. data/test/dependency_test.rb +51 -2
  33. data/test/manifest_test.rb +34 -0
  34. data/test/minifiers/terser_minifier_test.rb +0 -1
  35. data/test/minifiers/uglify_minifier_test.rb +0 -1
  36. data/test/postprocessors/css_media_combiner_test.rb +107 -0
  37. data/test/postprocessors/purgecss_test.rb +62 -0
  38. data/test/preprocessor/babel_test.rb +703 -298
  39. data/test/preprocessor/js_analyzer_test.rb +35 -2
  40. data/test/processors/rollup_test.rb +50 -20
  41. data/test/resolve_test.rb +18 -9
  42. data/test/server_test.rb +15 -10
  43. data/test/templates/ejs_test.rb +2 -11
  44. data/test/templates/erb_test.rb +0 -5
  45. data/test/test_helper.rb +8 -3
  46. data/test/transformers/dart_scss_test.rb +139 -0
  47. data/test/transformers/jst_test.rb +165 -21
  48. data/test/transformers/scss_test.rb +14 -0
  49. data/test/transformers/svg_test.rb +40 -0
  50. metadata +23 -6
  51. data/lib/condenser/transformers/sass_transformer/importer.rb +0 -50
@@ -7,7 +7,7 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
7
7
 
8
8
  def initialize(dir = nil, options = {})
9
9
  super(dir)
10
- npm_install('rollup', 'rollup-plugin-commonjs', 'rollup-plugin-node-resolve')
10
+ npm_install('rollup', '@rollup/plugin-commonjs', '@rollup/plugin-node-resolve')
11
11
 
12
12
  @options = options.freeze
13
13
  end
@@ -50,7 +50,12 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
50
50
  return '';
51
51
  } catch(e) {
52
52
  if (e.name === "SyntaxError") {
53
- if (e.message.startsWith('Unexpected token { in JSON at position ')) {
53
+ if (e.message.startsWith('Unexpected non-whitespace character after JSON at position ')) {
54
+ var pos = parseInt(e.message.slice(59));
55
+ emitMessages(buffer.slice(0,pos));
56
+ return emitMessages(buffer.slice(pos));
57
+ } else if (e.message.startsWith('Unexpected token { in JSON at position ')) {
58
+ // This can be removed, once dropping support for node <= v18
54
59
  var pos = parseInt(e.message.slice(39));
55
60
  emitMessages(buffer.slice(0,pos));
56
61
  return emitMessages(buffer.slice(pos));
@@ -70,8 +75,8 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
70
75
  });
71
76
 
72
77
  const rollup = require("#{npm_module_path('rollup')}");
73
- const commonjs = require("#{npm_module_path('rollup-plugin-commonjs')}");
74
- const nodeResolve = require("#{npm_module_path('rollup-plugin-node-resolve')}");
78
+ const commonjs = require("#{npm_module_path('@rollup/plugin-commonjs')}");
79
+ const nodeResolve = require("#{npm_module_path('@rollup/plugin-node-resolve')}").nodeResolve;
75
80
  var rid = 0;
76
81
  var renderStack = {};
77
82
  var nodeResolver = null;
@@ -95,9 +100,8 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
95
100
  mainFields: ['module', 'main'],
96
101
  // modulesOnly: true,
97
102
  // preferBuiltins: false,
98
- customResolveOptions: {
99
- moduleDirectory: '#{npm_module_path}'
100
- }
103
+ moduleDirectories: [],
104
+ modulePaths: ['#{npm_module_path}']
101
105
  });
102
106
  }
103
107
 
@@ -105,9 +109,9 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
105
109
  inputOptions.plugins = [];
106
110
  inputOptions.plugins.push({
107
111
  name: 'condenser',
108
- resolveId: function (importee, importer) {
112
+ resolveId: function (importee, importer, options) {
109
113
  if (importee.startsWith('\\0') || (importer && importer.startsWith('\\0'))) {
110
- return;
114
+ return null;
111
115
  }
112
116
 
113
117
  if (!(importer in renderStack)) {
@@ -115,24 +119,15 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
115
119
  }
116
120
 
117
121
  return request('resolve', [importee, importer]).then((value) => {
118
- if (nodeResolver && (value === null || value === undefined)) {
119
- return nodeResolver.resolveId.call(this, importee, importer).then((value) => {
120
- if (!(value === null || value === undefined) && !renderStack[importer].includes(value.id)) {
121
- renderStack[importer].push(value.id);
122
- }
123
- return value;
124
- });
125
- }
126
-
127
- if (!(value === null || value === undefined) && !renderStack[importer].includes(value)) {
128
- renderStack[importer].push(value);
129
- }
130
- return value;
122
+ if (!(value === null || value === undefined) && !renderStack[importer].includes(value)) {
123
+ renderStack[importer].push(value);
124
+ }
125
+ return value;
131
126
  });
132
127
  },
133
128
  load: function(id) {
134
129
  if (id.startsWith('\\0')) {
135
- return;
130
+ return null;
136
131
  }
137
132
 
138
133
  return request('load', [id]).then(function(value) {
@@ -140,17 +135,19 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
140
135
  });
141
136
  }
142
137
  });
138
+
143
139
  inputOptions.plugins.push(nodeResolver);
144
140
  inputOptions.plugins.push(commonjs());
145
141
 
146
- inputOptions.plugins.push({
147
- name: 'nullHanlder',
148
- resolveId: function (importee, importer) {
149
- request('error', ["AssetNotFound", importee, importer, renderStack]).then(function(value) {
150
- process.exit(1);
151
- });
152
- }
153
- });
142
+ // inputOptions.plugins.push({
143
+ // name: 'nullHanlder',
144
+ // resolveId: function (importee, importer, options) {
145
+ // request('log', [importee, importer, options])
146
+ // // request('error', ["AssetNotFound", importee, importer, renderStack]).then(function(value) {
147
+ // // process.exit(1);
148
+ // // });
149
+ // }
150
+ // });
154
151
 
155
152
  const outputOptions = #{JSON.generate(output_options)};
156
153
 
@@ -204,14 +201,20 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
204
201
  @entry
205
202
  elsif importee.start_with?('@babel/runtime') || importee.start_with?('core-js-pure') || importee.start_with?('regenerator-runtime')
206
203
  x = File.join(npm_module_path, importee.gsub(/^\.\//, File.dirname(importer) + '/')).sub('/node_modules/regenerator-runtime', '/node_modules/regenerator-runtime/runtime.js')
207
- x = "#{x}.js" if !x.end_with?('.js')
204
+ x = "#{x}.js" if !x.end_with?('.js', '.mjs')
205
+ File.file?(x) ? x : (x.delete_suffix('.js') + "/index.js")
206
+ elsif npm_module_path && importee&.start_with?(npm_module_path)
207
+ x = importee.end_with?('.js', '.mjs') ? importee : "#{importee}.js"
208
+ x = (x.delete_suffix('.js') + "/index.js") if !File.file?(x)
209
+ x
210
+ elsif importee.start_with?('.') && importer.start_with?(npm_module_path)
211
+ x = File.expand_path(importee, File.dirname(importer))
212
+ x = "#{x}.js" if !x.end_with?('.js', '.mjs')
208
213
  File.file?(x) ? x : (x.delete_suffix('.js') + "/index.js")
209
- elsif npm_module_path && importer.start_with?(npm_module_path)
210
- nil
211
214
  elsif importee.end_with?('*')
212
215
  File.join(File.dirname(importee), '*')
213
216
  else
214
- @environment.find(importee, importer ? File.dirname(@entry == importer ? @input[:source_file] : importer) : nil, accept: @input[:content_types].last)&.source_file
217
+ @environment.find(importee, importer ? (@entry == importer ? @input[:source_file] : importer) : nil, accept: @input[:content_types].last)&.source_file
215
218
  end
216
219
  when 'load'
217
220
  importee = message['args'].first
@@ -261,7 +264,6 @@ class Condenser::RollupProcessor < Condenser::NodeProcessor
261
264
  # when 'get_cache'
262
265
  # io.write(JSON.generate({rid: message['rid'], return: [(@environment.cache.get('rollup') || '{}')] }))
263
266
  end
264
-
265
267
  io.write(JSON.generate({rid: message['rid'], return: ret}))
266
268
  end
267
269
  end
@@ -38,9 +38,7 @@ class Condenser
38
38
  end
39
39
 
40
40
  paths.each do |path|
41
- glob = path
42
- glob = File.join(glob, dirname) if dirname
43
- glob = File.join(glob, basename)
41
+ glob = File.join(*[path, dirname, basename].compact)
44
42
  glob << '.*' unless glob.end_with?('*')
45
43
 
46
44
  Dir.glob(glob).sort.each do |f|
@@ -150,11 +148,34 @@ class Condenser
150
148
  end
151
149
  end
152
150
 
151
+ def has_dir?(path)
152
+ path.count('/') > (path.start_with?('/') ? 1 : 0)
153
+ end
154
+
155
+ def expand_path(path)
156
+ dir = if path.start_with?('/')
157
+ File.expand_path(path)
158
+ else
159
+ File.expand_path("/#{path}").delete_prefix('/')
160
+ end
161
+ dir.empty? ? nil : dir
162
+ end
163
+
153
164
  def decompose_path(path, base=nil)
154
- dirname = path.index('/') ? File.dirname(path) : nil
155
- if base && path&.start_with?('.')
156
- dirname = File.expand_path(dirname, base)
165
+ dirname = if base && path.start_with?('.')
166
+ if has_dir?(base)
167
+ if has_dir?(path)
168
+ expand_path(File.join(File.dirname(base), File.dirname(path)))
169
+ else
170
+ expand_path(File.dirname(base))
171
+ end
172
+ else
173
+ expand_path(File.dirname(path))
174
+ end
175
+ else
176
+ path.index('/') ? File.dirname(path) : nil
157
177
  end
178
+
158
179
 
159
180
  _, star, basename, extensions = path.match(/(([^\.\/]+)(\.[^\/]+)|\*|[^\/]+)$/).to_a
160
181
  if extensions == '.*'
@@ -1,4 +1,4 @@
1
- class Condenser::EjsTemplare
1
+ class Condenser::EjsTemplate
2
2
 
3
3
  def self.setup(environment)
4
4
  require 'ejs' unless defined?(::EJS)
@@ -0,0 +1,285 @@
1
+ class Condenser::DartSassTransformer < Condenser::NodeProcessor
2
+
3
+ @@helper_methods = Set.new
4
+
5
+ ACCEPT = ['text/css', 'text/scss', 'text/sass']
6
+
7
+ attr_accessor :options
8
+
9
+ # Use this function to append Modules that contain functions to expose
10
+ # to dart-sass
11
+ def self.add_helper_methods(module_to_add = nil, &block)
12
+ old_methods = self.instance_methods
13
+
14
+ self.include(module_to_add) if module_to_add
15
+ self.class_eval(&block) if block_given?
16
+
17
+ @@helper_methods.merge(self.instance_methods - old_methods)
18
+ end
19
+
20
+ add_helper_methods(Condenser::Sass::Functions)
21
+
22
+ def self.syntax
23
+ :sass
24
+ end
25
+
26
+ def initialize(dir, options = {})
27
+ super(dir)
28
+ npm_install('sass')
29
+
30
+ @options = options.merge({
31
+ indentedSyntax: self.class.syntax == :sass
32
+ }).freeze
33
+ end
34
+
35
+ def helper_method_signatures
36
+ x = @@helper_methods.map do |name|
37
+ arity = self.method(name).arity
38
+ signature = []
39
+ types = []
40
+ if respond_to?(:"#{name}_signature")
41
+ send(:"#{name}_signature").each do |arg, type|
42
+ signature << arg
43
+ types << type
44
+ end
45
+ elsif arity >= 0
46
+ arity.times.with_index do |a|
47
+ signature << "$arg#{a}"
48
+ types << 'String'
49
+ end
50
+ end
51
+
52
+ ["#{name}(#{signature.join(', ')})", types]
53
+ end
54
+ x
55
+ end
56
+
57
+ def call(environment, input)
58
+ @context = environment.new_context_class#.new(environment)
59
+
60
+ options = {
61
+ verbose: true,
62
+ file: File.join('/', input[:filename])
63
+ }.merge(@options)
64
+ @environment = environment
65
+ @input = input
66
+
67
+ result = exec_runtime(<<-JS)
68
+ const fs = require('fs');
69
+ const sass = require("#{npm_module_path('sass')}");
70
+ const stdin = process.stdin;
71
+ stdin.resume();
72
+ stdin.setEncoding('utf8');
73
+
74
+ // SET STDOUT to sync so that we don't get in an infinte looping waiting
75
+ // for Ruby to response when we haven't even sent the entire request.
76
+ if (process.stdout._handle) process.stdout._handle.setBlocking(true)
77
+
78
+ const source = #{JSON.generate(input[:source])};
79
+ const options = #{JSON.generate(options)};
80
+
81
+ var rid = 0;
82
+ function request(method, ...args) {
83
+ var trid = rid;
84
+ rid += 1;
85
+ console.log(JSON.stringify({ rid: trid, method: method, args: args }) + "\\n");
86
+
87
+
88
+ var readBuffer = '';
89
+ var response = null;
90
+ let chunk = new Buffer(1024);
91
+ let bytesRead;
92
+
93
+ while (response === null) {
94
+ try {
95
+ bytesRead = fs.readSync(stdin.fd, chunk, 0, 1024);
96
+ if (bytesRead === null) { exit(1); }
97
+ readBuffer += chunk.toString('utf8', 0, bytesRead);
98
+ [readBuffer, response] = readResponse(readBuffer);
99
+ } catch (e) {
100
+ if (e.code !== 'EAGAIN') { throw e; }
101
+ }
102
+ }
103
+ return response['return'];
104
+ }
105
+
106
+ function readResponse(buffer) {
107
+ try {
108
+ var message = JSON.parse(buffer);
109
+ return ['', message];
110
+ } catch(e) {
111
+ if (e.name === "SyntaxError") {
112
+ if (e.message.startsWith('Unexpected non-whitespace character after JSON at position ')) {
113
+ let pos = parseInt(e.message.slice(59));
114
+ let [b, r] = readResponse(buffer.slice(0,pos));
115
+ return [b + buffer.slice(pos), r];
116
+ } else if (e.message.startsWith('Unexpected token { in JSON at position ')) {
117
+ // This can be removed, once dropping support for node <= v18
118
+ var pos = parseInt(e.message.slice(39));
119
+ let [b, r] = readResponse(buffer.slice(0,pos));
120
+ return [b + buffer.slice(pos), r];
121
+ } else {
122
+ return [buffer, null];
123
+ }
124
+ } else {
125
+ console.log(JSON.stringify({method: 'error', args: [e.name, e.message]}) + "\\n");
126
+ process.exit(1);
127
+ }
128
+ }
129
+ }
130
+
131
+
132
+ options.importer = function(url, prev) { return request('load', url, prev); };
133
+
134
+ const call_fn = function(name, types, args) {
135
+ const transformedArgs = [];
136
+ request('log', '==================')
137
+ args.forEach((a, i) => {
138
+ if (types[i] === 'Map') {
139
+ // Don't know how to go from SassMap to hash yet
140
+ transformedArgs.push({});
141
+ } else if (types[i] === 'List') {
142
+ // Don't know how to go from SassList to hash yet
143
+ transformedArgs.push([]);
144
+ } else if (!(a instanceof sass.types[types[i]])) {
145
+ throw "$url: Expected a string.";
146
+ } else {
147
+ transformedArgs.push(a.getValue());
148
+ }
149
+
150
+
151
+ // if (types[i] === 'List') { a = a.contents(); }
152
+ });
153
+ request('log', name, transformedArgs, types)
154
+ return new sass.types.String(request('call', name, transformedArgs));
155
+ }
156
+ options.functions = {};
157
+ #{JSON.generate(helper_method_signatures)}.forEach( (f) => {
158
+ let name = f[0].replace(/-/g, '_').replace(/(^[^\\(]+)\\(.*\\)$/, '$1');
159
+ options.functions[f[0]] = (...a) => call_fn(name, f[1], a);
160
+ })
161
+
162
+ try {
163
+ options.data = source;
164
+ const result = sass.renderSync(options);
165
+ request('result', result.css.toString());
166
+ process.exit(0);
167
+ } catch(e) {
168
+ request('error', e.name, e.message);
169
+ process.exit(1);
170
+ }
171
+ JS
172
+
173
+ input[:source] = result
174
+ # input[:map] = map.to_json({})
175
+ input[:linked_assets] += @context.links
176
+ input[:process_dependencies] += @context.dependencies
177
+ end
178
+
179
+ def find(importee, importer = nil)
180
+ # importer ||= @input[:source_file]
181
+ @environment.find(expand_path(importee, importer), nil, accept: ACCEPT)
182
+ end
183
+
184
+ def resolve(importee, importer = nil)
185
+ # importer ||= @input[:source_file]
186
+ @environment.resolve(expand_path(importee, importer), accept: ACCEPT)
187
+ end
188
+
189
+ def expand_path(path, base=nil)
190
+ if path.start_with?('.')
191
+ File.expand_path(path, File.dirname(base)).delete_prefix(File.expand_path('.') + '/')
192
+ else
193
+ File.expand_path(path).delete_prefix(File.expand_path('.') + '/')
194
+ end
195
+ end
196
+
197
+ def exec_runtime(script)
198
+ io = IO.popen([binary, '-e', script], 'r+')
199
+ buffer = ''
200
+ result = nil
201
+
202
+ begin
203
+ while IO.select([io]) && io_read = io.read_nonblock(1_024)
204
+ buffer << io_read
205
+ messages = buffer.split("\n\n")
206
+ buffer = buffer.end_with?("\n\n") ? '' : messages.pop
207
+
208
+ messages.each do |message|
209
+ message = JSON.parse(message)
210
+
211
+ ret = case message['method']
212
+ when 'result'
213
+ result = message['args'][0]
214
+ nil
215
+ when 'load'
216
+ importee = message['args'][0]
217
+ importer = message['args'][1]
218
+
219
+ if importee.end_with?('*')
220
+ @context.depend_on(importee)
221
+ code = ""
222
+ resolve(importee, importer).each do |f, i|
223
+ code << "@import '#{f.filename}';\n"
224
+ end
225
+ { contents: code, map: nil }
226
+ else
227
+ if asset = find(importee)
228
+ @context.depend_on(asset.filename)
229
+ { contents: asset.source, map: asset.sourcemap }
230
+ else
231
+ @context.depend_on(importee)
232
+ nil
233
+ end
234
+ end
235
+ when 'error'
236
+ io.write(JSON.generate({rid: message['rid'], return: nil}))
237
+
238
+ case message['args'][0]
239
+ when 'AssetNotFound'
240
+ error_message = "Could not find import \"#{message['args'][1]}\" for \"#{message['args'][2]}\".\n\n"
241
+ error_message << build_tree(message['args'][3], input, message['args'][2])
242
+ raise exec_runtime_error(error_message)
243
+ else
244
+ raise exec_runtime_error(message['args'][0] + ': ' + message['args'][1])
245
+ end
246
+ when 'call'
247
+ if respond_to?(message['args'][0])
248
+ send(message['args'][0], *message['args'][1])
249
+ else
250
+ puts '!!!'
251
+ end
252
+ end
253
+
254
+ io.write(JSON.generate({rid: message['rid'], return: ret}))
255
+ end
256
+ end
257
+ rescue Errno::EPIPE, EOFError
258
+ end
259
+
260
+ io.close
261
+ if $?.success?
262
+ result
263
+ else
264
+ raise exec_runtime_error(buffer)
265
+ end
266
+ end
267
+
268
+ protected
269
+
270
+ def condenser_context
271
+ @context
272
+ end
273
+
274
+ def condenser_environment
275
+ @environment
276
+ end
277
+
278
+
279
+ end
280
+
281
+ class Condenser::DartScssTransformer < Condenser::DartSassTransformer
282
+ def self.syntax
283
+ :scss
284
+ end
285
+ end
@@ -23,32 +23,32 @@ class Condenser::JstTransformer < Condenser::NodeProcessor
23
23
  const source = #{JSON.generate(input[:source])};
24
24
  const options = #{JSON.generate(opts).gsub(/"@?babel[\/-][^"]+"/) { |m| "require(#{m})"}};
25
25
 
26
- function globalVar(scope, name) {
27
- if (name in scope.globals) {
28
- return true;
29
- } else if (scope.parent === null || scope.parent === undefined) {
30
- return false;
31
- } else {
32
- return globalVar(scope.parent, name);
33
- }
34
- }
35
-
26
+ let scope = [['document', 'window']];
27
+
36
28
  options['plugins'].unshift(function({ types: t }) {
37
29
  return {
38
30
  visitor: {
39
31
  Identifier(path, state) {
40
- if ( path.parent.type == 'MemberExpression' && path.parent.object != path.node) {
32
+ if ( path.parent.type === 'MemberExpression' && path.parent.object !== path.node) {
41
33
  return;
42
34
  }
43
- if ( path.parent.type == 'ImportSpecifier' || path.parent.type == 'ImportDefaultSpecifier' || path.parent.type =='FunctionDeclaration') {
35
+
36
+ if ( path.parent.type === 'ImportSpecifier' ||
37
+ path.parent.type === 'ImportDefaultSpecifier' ||
38
+ path.parent.type === 'FunctionDeclaration' ||
39
+ path.parent.type === 'FunctionExpression' ||
40
+ path.parent.type === 'ArrowFunctionExpression' ||
41
+ path.parent.type === 'SpreadElement' ||
42
+ path.parent.type === 'CatchClause' ) {
43
+ return;
44
+ }
45
+
46
+ if ( path.parent.type === 'ObjectProperty' && path.parent.key === path.node ) {
44
47
  return;
45
48
  }
46
49
 
47
- if (
48
- path.node.name !== 'document' &&
49
- path.node.name !== 'window' &&
50
- !(path.node.name in global) &&
51
- globalVar(path.scope, path.node.name)
50
+ if ( !(path.node.name in global) &&
51
+ !scope.find((s) => s.find(v => v === path.node.name))
52
52
  ) {
53
53
  path.replaceWith(
54
54
  t.memberExpression(t.identifier("locals"), path.node)
@@ -58,6 +58,56 @@ class Condenser::JstTransformer < Condenser::NodeProcessor
58
58
  }
59
59
  };
60
60
  });
61
+
62
+
63
+ options['plugins'].unshift(function({ types: t }) {
64
+ return {
65
+ visitor: {
66
+ "FunctionDeclaration|FunctionExpression|ArrowFunctionExpression": {
67
+ enter(path, state) {
68
+ if (path.node.id) { scope[scope.length-1].push(path.node.id.name); }
69
+ scope.push(path.node.params.map((n) => n.type === 'RestElement' ? n.argument.name : n.name));
70
+ }
71
+ },
72
+ CatchClause: {
73
+ enter(path, state) {
74
+ scope.push([]);
75
+ if (path.node.param.name) { scope[scope.length-1].push(path.node.param.name); }
76
+ }
77
+ },
78
+ Scopable: {
79
+ enter(path, state) {
80
+ if (path.node.type !== 'Program' &&
81
+ path.node.type !== 'CatchClause' &&
82
+ path.parent.type !== 'FunctionDeclaration' &&
83
+ path.parent.type !== 'FunctionExpression' &&
84
+ path.parent.type !== 'ArrowFunctionExpression' &&
85
+ path.parent.type !== 'ExportDefaultDeclaration') {
86
+ scope.push([]);
87
+ }
88
+ },
89
+ exit(path, state) {
90
+ if (path.node.type !== 'Program' &&
91
+ path.parent.type !== 'ExportDefaultDeclaration') {
92
+ scope.pop();
93
+ }
94
+ }
95
+ },
96
+ ImportDeclaration(path, state) {
97
+ path.node.specifiers.forEach((s) => scope[scope.length-1].push(s.local.name));
98
+ },
99
+ ClassDeclaration(path, state) {
100
+ if (path.node.id) {
101
+ scope[scope.length-1].push(path.node.id.name)
102
+ }
103
+ },
104
+ VariableDeclaration(path, state) {
105
+ path.node.declarations.forEach((s) => scope[scope.length-1].push(s.id.name));
106
+ }
107
+ }
108
+ };
109
+ });
110
+
61
111
 
62
112
  try {
63
113
  const result = babel.transform(source, options);