condenser 1.2 → 1.4

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 (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);