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
@@ -7,6 +7,56 @@ class JSAnalyzerTest < ActiveSupport::TestCase
7
7
  @env.unregister_minifier('application/javascript')
8
8
  end
9
9
 
10
+ test 'file with exports gets marked as module and exports as module' do
11
+ @env.clear_pipeline
12
+ @env.register_preprocessor 'application/javascript', Condenser::JSAnalyzer
13
+
14
+ file 'export.js', <<~JS
15
+ var t = { 'var': () => { return 2; } };
16
+
17
+ export {t as name1};
18
+ JS
19
+
20
+ asset = assert_file 'export.js', 'application/javascript', <<~FILE
21
+ var t = { 'var': () => { return 2; } };
22
+
23
+ export {t as name1};
24
+ FILE
25
+ assert_equal "module", asset.type
26
+
27
+ asset = assert_exported_file 'export.js', 'application/javascript', <<~FILE
28
+ var t = { 'var': () => { return 2; } };
29
+
30
+ export {t as name1};
31
+ FILE
32
+ assert_equal "module", asset.type
33
+ end
34
+
35
+ test 'file with imports gets marked as module and exports as module' do
36
+ @env.clear_pipeline
37
+ @env.register_preprocessor 'application/javascript', Condenser::JSAnalyzer
38
+
39
+ file 'import.js', <<~JS
40
+ import name1 from 'export'
41
+
42
+ name1();
43
+ JS
44
+
45
+ asset = assert_file 'import.js', 'application/javascript', <<~FILE
46
+ import name1 from 'export'
47
+
48
+ name1();
49
+ FILE
50
+ assert_equal "module", asset.type
51
+
52
+ asset = assert_exported_file 'import.js', 'application/javascript', <<~FILE
53
+ import name1 from 'export'
54
+
55
+ name1();
56
+ FILE
57
+ assert_equal "module", asset.type
58
+ end
59
+
10
60
  test 'file with a single export' do
11
61
  file 'name.js', <<~JS
12
62
  var t = { 'var': () => { return 2; } };
@@ -49,7 +99,7 @@ class JSAnalyzerTest < ActiveSupport::TestCase
49
99
  ')': '&#41;',
50
100
  };
51
101
 
52
- var resc = /[<>&\(\)\[\]"']/g;
102
+ var resc = /[<>&\(\)\\[\\]"']/g;
53
103
 
54
104
  JS
55
105
 
@@ -97,16 +147,16 @@ class JSAnalyzerTest < ActiveSupport::TestCase
97
147
  asset = @env.find('name.js')
98
148
  assert_nil asset.exports
99
149
  assert_equal [
150
+ "module-name/path/to/specific/un-exported/file6.js",
100
151
  "module-name1.js",
152
+ "module-name10.js",
101
153
  "module-name2.js",
102
154
  "module-name3.js",
103
155
  "module-name4.js",
104
156
  "module-name5.js",
105
- "module-name/path/to/specific/un-exported/file6.js",
106
157
  "module-name7.js",
107
158
  "module-name8.js",
108
- "module-name9.js",
109
- "module-name10.js"
159
+ "module-name9.js"
110
160
  ], asset.export_dependencies.map(&:filename)
111
161
  end
112
162
 
@@ -248,4 +298,158 @@ class JSAnalyzerTest < ActiveSupport::TestCase
248
298
  assert_equal ['a.js', 'b.js'], asset.export_dependencies.map(&:filename)
249
299
  end
250
300
 
301
+ test "dependency tracking for a export from" do
302
+ file 'c.js', <<~JS
303
+ function c() { return 'ok'; }
304
+
305
+ export {c}
306
+ JS
307
+
308
+ file 'b.js', <<~JS
309
+ export {c} from 'c';
310
+
311
+ JS
312
+
313
+ file 'a.js', <<~JS
314
+ import {c} from 'b'
315
+
316
+ console.log(c());
317
+ JS
318
+
319
+ asset = assert_file 'a.js', 'application/javascript'
320
+ assert_equal ['/a.js', '/b.js', '/c.js'], asset.all_export_dependencies.map { |path| path.delete_prefix(@path) }
321
+ end
322
+
323
+ test 'exporting a nested object' do
324
+ file 't.js', <<~JS
325
+ export default {
326
+ registry: {
327
+ boolean: true,
328
+ integer: 1
329
+ }
330
+ };
331
+ JS
332
+ end
333
+
334
+ test 'regex chars that dont need escaping' do
335
+ file 'a.js', 'this._agsAdmin=/(https?:\/\/[^/]+\/[^/]+)\/admin\/?(\/.*)?$/i'
336
+ asset = assert_file 'a.js', 'application/javascript'
337
+
338
+ file 'b.js', 'function T(e){return e.replaceAll(/[|\\{}()[\]^$+*?.]/g,"\\$&")}'
339
+ asset = assert_file 'b.js', 'application/javascript'
340
+
341
+ file 'c.js', 'f=/(?:LENGTH)?UNIT\[([^\]]+)]]$/i'
342
+ asset = assert_file 'c.js', 'application/javascript'
343
+
344
+ file 'd.js', 'function o(t,e){return t.replaceAll(/([.$?*|{}()[\]\\\\/+\-^])/g,(t=>e?.includes(t)?t:`\\${t}`))}'
345
+ asset = assert_file 'd.js', 'application/javascript'
346
+ end
347
+
348
+ test 'a regex after a function call or for loop' do
349
+ file 't.js', <<~JS
350
+ for(const s in r)/^(request|service)$/i.test(s)&&delete r[s];
351
+ JS
352
+
353
+ asset = assert_file 't.js', 'application/javascript'
354
+ file 'b.js', <<~JS
355
+ e=>Math.round(1e4*e)/1e4;
356
+ JS
357
+
358
+ asset = assert_file 'b.js', 'application/javascript'
359
+ file 'c.js', <<~JS
360
+ {const e=this._timings.entries,t=e.length;let s=0;for(const r of e)s+=r;r=s/t}
361
+ JS
362
+
363
+ asset = assert_file 'c.js', 'application/javascript'
364
+ end
365
+
366
+ test 'file with a single line comment in a argument list before a regex' do
367
+ file 'name.js', <<~JS
368
+ function javascript(hljs) {
369
+
370
+ regex.either(
371
+ // Float32Array, OutT
372
+ /\b[A-Z][a-z]+([A-Z][a-z]*|\d)*/,
373
+ // CSSFactory, CSSFactoryT
374
+ )
375
+
376
+ const USE_STRICT = {
377
+ label: "use_strict",
378
+ className: 'meta',
379
+ relevance: 10,
380
+ begin: /^\s*['"]use (strict|asm)['"]/
381
+ };
382
+
383
+ }
384
+
385
+ module.exports = javascript;
386
+ JS
387
+
388
+ asset = assert_file 'name.js', 'application/javascript'
389
+
390
+
391
+ file 'name.js', <<~JS
392
+ class ScrollDirective {
393
+ constructor (scrollElement, ...args) {
394
+ this._promise = new Promise((resolve, reject) => {
395
+ function step (now) {
396
+
397
+ const elapsed = now - this.start
398
+ // Behavior is (EaseOutCubic)[https://gizma.com/easing/#easeOutCubic]
399
+ // TODO add easing options
400
+ const percent = Math.min(1 - Math.pow(1 - elapsed / this.duration, 3), 1.0)
401
+ scrollElement.scrollTo(this.deltaX * percent + this.startX, this.deltaY * percent + this.startY)
402
+
403
+ id = window.requestAnimationFrame(step.bind(this))
404
+ }
405
+
406
+ })
407
+ }
408
+
409
+ setDirective (targetX, targetY, options={}) {
410
+ // support (targetX, options)
411
+ if (typeof targetY == "object") {
412
+ options = targetY
413
+ targetY = undefined
414
+ }
415
+ // support (options)
416
+ if (typeof targetX == "object") {
417
+ options = targetX
418
+ targetX = undefined
419
+ }
420
+ if (!options.duration) {
421
+ this.duration = Math.max(Math.abs(this.deltaY) / this.speed, Math.abs(this.deltaX) / this.speed)
422
+ if (options.minDuration) {
423
+ this.duration = Math.max(this.duration, options.minDuration)
424
+ }
425
+ }
426
+ return this
427
+ }
428
+
429
+ }
430
+ JS
431
+
432
+ asset = assert_file 'name.js', 'application/javascript'
433
+ end
434
+
435
+ test 'file with a keyword as start of a function name' do
436
+ file 'name.js', <<~JS
437
+ export default class Admin extends User {
438
+
439
+ static aroundActions = ['requireAdmin']
440
+
441
+ async imports () {
442
+ this.display(imports, {
443
+ user: this.application.session.user
444
+ }, { layout })
445
+ }
446
+ }
447
+ JS
448
+
449
+ asset = @env.find('name.js')
450
+ assert asset.exports
451
+ assert asset.has_default_export?
452
+ assert_empty asset.export_dependencies
453
+ end
454
+
251
455
  end
@@ -0,0 +1,358 @@
1
+ require 'test_helper'
2
+
3
+ class RollupDynamicImportTestTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ super
7
+ @env.unregister_minifier('application/javascript')
8
+ end
9
+
10
+ test 'dynamic imports get inlined' do
11
+ file 'main.js', <<~JS
12
+ cube = await import('./math/math');
13
+ bigCube = await import('math/b');
14
+
15
+ console.log( cube( 5 ) ); // 125
16
+ JS
17
+
18
+ file 'math/cube.js', <<~JS
19
+ export default function cube ( x ) {
20
+ return x * x * x;
21
+ }
22
+ JS
23
+ file 'math/math.js', <<~JS
24
+ import cube from './cube';
25
+
26
+ export {cube};
27
+ JS
28
+ file 'math/b.js', <<~JS
29
+ import cube from './cube';
30
+ let b = x;
31
+ export {cube};
32
+ JS
33
+
34
+ assert_exported_file 'main.js', 'application/javascript', <<~FILE
35
+ function cube$1 ( x ) {
36
+ return x * x * x;
37
+ }
38
+
39
+ const math = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
40
+ __proto__: null,
41
+ cube: cube$1
42
+ }, Symbol.toStringTag, { value: 'Module' }));
43
+
44
+ x;
45
+
46
+ const b = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
47
+ __proto__: null,
48
+ cube: cube$1
49
+ }, Symbol.toStringTag, { value: 'Module' }));
50
+
51
+ cube = await Promise.resolve().then(() => math);
52
+ bigCube = await Promise.resolve().then(() => b);
53
+
54
+ console.log( cube( 5 ) ); // 125
55
+ FILE
56
+ end
57
+
58
+ test 'file with dynamic imports' do
59
+ 1.upto(3) do |i|
60
+ if i == 3
61
+ file "module-name/path/to/specific/un-exported/file#{i}.js", "#{i}"
62
+ else
63
+ file "module-name#{i}.js", "#{i}"
64
+ end
65
+ end
66
+
67
+ file 'name.js', <<~JS
68
+ let x = await import("module-name1");
69
+ let y = import("module-name2");
70
+ import("module-name/path/to/specific/un-exported/file3");
71
+ JS
72
+
73
+ asset = @env.find('name.js')
74
+ assert_nil asset.exports
75
+ assert_equal [
76
+ "module-name1.js",
77
+ "module-name2.js",
78
+ "module-name/path/to/specific/un-exported/file3.js"
79
+ ], asset.linked_assets.map(&:filename)
80
+ end
81
+
82
+ test "dynamic imports don't inlined and are exported" do
83
+ @env.unregister_exporter 'application/javascript'
84
+ @env.register_exporter 'application/javascript', Condenser::RollupProcessor.new(@env.npm_path, dynamic_imports: false)
85
+
86
+ file 'main.js', <<~JS
87
+ cube = await import('./math/math');
88
+ bigCube = await import('math/b');
89
+
90
+ console.log( cube( 5 ) ); // 125
91
+ JS
92
+
93
+ file 'math/cube.js', <<~JS
94
+ export default function cube ( x ) {
95
+ return x * x * x;
96
+ }
97
+ JS
98
+ file 'math/math.js', <<~JS
99
+ import cube from './cube';
100
+
101
+ export {cube};
102
+ JS
103
+ file 'math/b.js', <<~JS
104
+ import cube from './cube';
105
+ let b = x;
106
+ export {cube};
107
+ JS
108
+
109
+ assert_exported_file 'main.js', 'application/javascript', <<~FILE
110
+ cube = await import('/#{@env.find('math/math').path}');
111
+ bigCube = await import('/#{@env.find('math/b').path}');
112
+
113
+ console.log( cube( 5 ) ); // 125
114
+ FILE
115
+
116
+ Dir.mktmpdir do |export_dir|
117
+ manifest = Condenser::Manifest.new(@env, File.join(export_dir, 'manifest.json'))
118
+ main = @env['main.js']
119
+ math = @env['math/math.js']
120
+ mathb = @env['math/b.js']
121
+ assets = [main, math, mathb]
122
+
123
+ assets.each do |asset|
124
+ assert !File.exist?("#{export_dir}/#{asset.path}")
125
+ end
126
+
127
+ manifest.compile('main.js')
128
+ assert File.directory?(manifest.dir)
129
+ assert File.file?(manifest.filename)
130
+ assert File.exist?("#{export_dir}/manifest.json")
131
+
132
+ assets.each do |asset|
133
+ assert File.exist?("#{export_dir}/#{asset.path}")
134
+ assert File.exist?("#{export_dir}/#{asset.path}.gz")
135
+ end
136
+
137
+ data = JSON.parse(File.read(manifest.filename))
138
+
139
+ assert data['main.js']
140
+ assert_equal 240, data['main.js']['size']
141
+ assert_equal main.path, data['main.js']['path']
142
+ assert_equal(<<~JS.rstrip, File.read(File.join(export_dir, data['main.js']['path'])).rstrip)
143
+ cube = await import('/#{math.path}');
144
+ bigCube = await import('/#{mathb.path}');
145
+
146
+ console.log( cube( 5 ) ); // 125
147
+ JS
148
+
149
+ assert data['math/math.js']
150
+ assert_equal 62, data['math/math.js']['size']
151
+ assert_equal math.path, data['math/math.js']['path']
152
+ assert_equal(<<~JS.rstrip, File.read(File.join(export_dir, data['math/math.js']['path'])).rstrip)
153
+ function cube ( x ) {
154
+ return x * x * x;
155
+ }
156
+
157
+ export { cube };
158
+ JS
159
+
160
+ assert data['math/b.js']
161
+ assert_equal 66, data['math/b.js']['size']
162
+ assert_equal mathb.path, data['math/b.js']['path']
163
+ assert_equal(<<~JS.rstrip, File.read(File.join(export_dir, data['math/b.js']['path'])).rstrip)
164
+ function cube ( x ) {
165
+ return x * x * x;
166
+ }
167
+
168
+ x;
169
+
170
+ export { cube };
171
+ JS
172
+ end
173
+ end
174
+
175
+ test "dynamic imports with a prefix" do
176
+ @env.unregister_exporter 'application/javascript'
177
+ @env.register_exporter 'application/javascript', Condenser::RollupProcessor.new(@env.npm_path, prefix: "/assets", dynamic_imports: false)
178
+
179
+ file 'main.js', <<~JS
180
+ cube = await import('./math/math');
181
+ bigCube = await import('math/b');
182
+
183
+ console.log( cube( 5 ) ); // 125
184
+ JS
185
+
186
+ file 'math/cube.js', <<~JS
187
+ export default function cube ( x ) {
188
+ return x * x * x;
189
+ }
190
+ JS
191
+ file 'math/math.js', <<~JS
192
+ import cube from './cube';
193
+
194
+ export {cube};
195
+ JS
196
+ file 'math/b.js', <<~JS
197
+ import cube from './cube';
198
+ let b = x;
199
+ export {cube};
200
+ JS
201
+
202
+ assert_exported_file 'main.js', 'application/javascript', <<~FILE
203
+ cube = await import('/assets/#{@env.find('math/math').path}');
204
+ bigCube = await import('/assets/#{@env.find('math/b').path}');
205
+
206
+ console.log( cube( 5 ) ); // 125
207
+ FILE
208
+
209
+ Dir.mktmpdir do |export_dir|
210
+ manifest = Condenser::Manifest.new(@env, File.join(export_dir, 'manifest.json'))
211
+ main = @env['main.js']
212
+ math = @env['math/math.js']
213
+ mathb = @env['math/b.js']
214
+ assets = [main, math, mathb]
215
+
216
+ assets.each do |asset|
217
+ assert !File.exist?("#{export_dir}/#{asset.path}")
218
+ end
219
+
220
+ manifest.compile('main.js')
221
+ assert File.directory?(manifest.dir)
222
+ assert File.file?(manifest.filename)
223
+ assert File.exist?("#{export_dir}/manifest.json")
224
+
225
+ assets.each do |asset|
226
+ assert File.exist?("#{export_dir}/#{asset.path}")
227
+ assert File.exist?("#{export_dir}/#{asset.path}.gz")
228
+ end
229
+
230
+ data = JSON.parse(File.read(manifest.filename))
231
+
232
+ assert data['main.js']
233
+ assert_equal 254, data['main.js']['size']
234
+ assert_equal main.path, data['main.js']['path']
235
+ assert_equal(<<~JS.rstrip, File.read(File.join(export_dir, data['main.js']['path'])).rstrip)
236
+ cube = await import('/assets/#{math.path}');
237
+ bigCube = await import('/assets/#{mathb.path}');
238
+
239
+ console.log( cube( 5 ) ); // 125
240
+ JS
241
+
242
+ assert data['math/math.js']
243
+ assert_equal 62, data['math/math.js']['size']
244
+ assert_equal math.path, data['math/math.js']['path']
245
+ assert_equal(<<~JS.rstrip, File.read(File.join(export_dir, data['math/math.js']['path'])).rstrip)
246
+ function cube ( x ) {
247
+ return x * x * x;
248
+ }
249
+
250
+ export { cube };
251
+ JS
252
+
253
+ assert data['math/b.js']
254
+ assert_equal 66, data['math/b.js']['size']
255
+ assert_equal mathb.path, data['math/b.js']['path']
256
+ assert_equal(<<~JS.rstrip, File.read(File.join(export_dir, data['math/b.js']['path'])).rstrip)
257
+ function cube ( x ) {
258
+ return x * x * x;
259
+ }
260
+
261
+ x;
262
+
263
+ export { cube };
264
+ JS
265
+ end
266
+ end
267
+
268
+ test "cyclical dynamic imports don't inlined and are exported" do
269
+ @env.unregister_exporter 'application/javascript'
270
+ @env.register_exporter 'application/javascript', Condenser::RollupProcessor.new(@env.npm_path, dynamic_imports: false)
271
+
272
+ file 'main.js', <<~JS
273
+ const cube = await import('./math/math');
274
+
275
+ console.log( cube( 5 ) ); // 125
276
+ JS
277
+
278
+ file 'math/cube.js', <<~JS
279
+ const math = await import('./math');
280
+
281
+ export default function cube ( x ) {
282
+ return math.number(x) * x * x;
283
+ }
284
+ JS
285
+ file 'math/math.js', <<~JS
286
+ import cube from './cube';
287
+
288
+ function number (x) { return x; }
289
+
290
+ export {cube, number};
291
+ JS
292
+
293
+ assert_exported_file 'main.js', 'application/javascript', <<~FILE
294
+ const cube = await import('/#{@env.find('/math/math').export.path}');
295
+
296
+ console.log( cube( 5 ) ); // 125
297
+ FILE
298
+
299
+ Dir.mktmpdir do |export_dir|
300
+ manifest = Condenser::Manifest.new(@env, File.join(export_dir, 'manifest.json'))
301
+ main = @env['main.js']
302
+ math = @env['math/math.js']
303
+ cube = @env['math/cube.js']
304
+ assets = [main, math]
305
+
306
+ assets.each do |asset|
307
+ assert !File.exist?("#{export_dir}/#{asset.path}")
308
+ end
309
+
310
+ manifest.compile('main.js')
311
+ assert File.directory?(manifest.dir)
312
+ assert File.file?(manifest.filename)
313
+ assert File.exist?("#{export_dir}/manifest.json")
314
+
315
+ assets.each do |asset|
316
+ assert File.exist?("#{export_dir}/#{asset.path}")
317
+ assert File.exist?("#{export_dir}/#{asset.path}.gz")
318
+ end
319
+
320
+ data = JSON.parse(File.read(manifest.filename))
321
+
322
+ assert_equal ["main.js", "math/math.js"], data.keys
323
+
324
+ assert data['main.js']
325
+ assert_equal 143, data['main.js']['size']
326
+ assert_equal main.path, data['main.js']['path']
327
+ assert_equal(<<~JS.rstrip, File.read(File.join(export_dir, data['main.js']['path'])).rstrip)
328
+ const cube = await import('/#{math.path}');
329
+
330
+ console.log( cube( 5 ) ); // 125
331
+ JS
332
+
333
+ assert data['math/math.js']
334
+ assert_equal 336, data['math/math.js']['size']
335
+ assert_equal math.path, data['math/math.js']['path']
336
+ assert_equal(<<~JS.rstrip, File.read(File.join(export_dir, data['math/math.js']['path'])).rstrip)
337
+ const math = await Promise.resolve().then(() => entry);
338
+
339
+ function cube ( x ) {
340
+ return math.number(x) * x * x;
341
+ }
342
+
343
+ function number (x) { return x; }
344
+
345
+ const entry = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
346
+ __proto__: null,
347
+ cube,
348
+ number
349
+ }, Symbol.toStringTag, { value: 'Module' }));
350
+
351
+ export { cube, number };
352
+ JS
353
+ end
354
+ end
355
+
356
+ #TODO: add test for inilne / not inline URLs
357
+
358
+ end