bare-module-traverse 2.5.8 → 2.6.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.
package/README.md CHANGED
@@ -219,12 +219,16 @@ Options are the same as `traverse()` for all functions.
219
219
 
220
220
  ### `const generator = traverse.package(url, source, artifacts, visited[, options])`
221
221
 
222
- ### `const generator = traverse.preresolved(url, source, resolutions, artifacts, visited[, options])`
222
+ ### `const generator = traverse.preresolved(url, source, attributes, resolutions, artifacts, visited[, options])`
223
223
 
224
224
  ### `const generator = traverse.imports(parentURL, source, imports, artifacts, lexer, visited[, options])`
225
225
 
226
226
  ### `const generator = traverse.link(entry, specifier, condition, parentURL, imports, artifacts, visited[, options])`
227
227
 
228
+ ### `traverse.assertAddonContext(url, scope, context, addonContexts)`
229
+
230
+ Record in `addonContexts` that the package at `scope` loads its addons in `context`, an object of the `host` and the URL of the `wasi` provider, either `null` for the defaults. Throws if it was recorded for another context, or if `url` doesn't load on `context.host`.
231
+
228
232
  ### `const generator = traverse.addons(parentURL, artifacts, visited[, options])`
229
233
 
230
234
  ### `const generator = traverse.assets(patterns, parentURL, artifacts, visited[, options])`
package/index.d.ts CHANGED
@@ -174,9 +174,22 @@ declare namespace traverse {
174
174
  opts?: TraverseOptions
175
175
  ): Traversal
176
176
 
177
+ interface AddonContext {
178
+ host: string | null
179
+ wasi: string | null
180
+ }
181
+
182
+ export function assertAddonContext(
183
+ url: URL,
184
+ scope: string,
185
+ context: Partial<AddonContext>,
186
+ addonContexts: Map<string, AddonContext>
187
+ ): void
188
+
177
189
  export function preresolved(
178
190
  url: URL,
179
191
  source: string | Buffer,
192
+ attributes: Record<string, string> | null,
180
193
  resolution: ResolutionsMap,
181
194
  artifacts: Artifacts,
182
195
  visited: Set<string>,
package/index.js CHANGED
@@ -192,10 +192,29 @@ exports.alias = function alias(url, opts = {}) {
192
192
  }
193
193
 
194
194
  exports.module = function* (url, source, attributes, artifacts, visited, opts = {}) {
195
- const { resolutions = null, asset = false, probed, types } = opts
196
-
197
195
  attributes = attributes || {}
198
196
 
197
+ if (opts.contexts === undefined) opts = { ...opts, contexts: new Map() }
198
+ if (opts.addonContexts === undefined) opts = { ...opts, addonContexts: new Map() }
199
+
200
+ // The host and WASI attributes set the context that the addons of the module
201
+ // and everything it imports load in. A graph is walked again for each context
202
+ // it is imported under, even where it was visited before.
203
+ if (attributes.host !== undefined || attributes.wasi !== undefined) {
204
+ const { addonHost = null, addonWASI = null } = opts
205
+
206
+ const host = attributes.host === undefined ? addonHost : addonHostForAttribute(attributes.host)
207
+ const wasi = attributes.wasi === undefined ? addonWASI : attributes.wasi
208
+
209
+ if (host !== addonHost || wasi !== addonWASI) {
210
+ opts = { ...opts, addonHost: host, addonWASI: wasi }
211
+
212
+ visited = visitedFor(opts.contexts, contextKey(host, wasi))
213
+ }
214
+ }
215
+
216
+ const { resolutions = null, asset = false, probed, types } = opts
217
+
199
218
  if (visited.has(url.href)) {
200
219
  // An asset is only read for its bytes, so it doesn't care what type the
201
220
  // module is imported as elsewhere.
@@ -214,7 +233,9 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
214
233
  if (opts.prefixes === undefined) opts = { ...opts, prefixes: new Map() }
215
234
  if (opts.types === undefined) opts = { ...opts, types: new Map() }
216
235
 
217
- const artifact = asset === true || moduleType(url, attributes, null, opts) === constants.ADDON
236
+ const artifact =
237
+ asset === true ||
238
+ (moduleType(url, attributes, null, opts) === constants.ADDON && !isWebAssembly(url))
218
239
 
219
240
  if (source === null) {
220
241
  if (url.protocol === 'data:') {
@@ -235,7 +256,9 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
235
256
  }
236
257
 
237
258
  if (resolutions) {
238
- if (yield* exports.preresolved(url, source, resolutions, artifacts, visited, opts)) {
259
+ if (
260
+ yield* exports.preresolved(url, source, attributes, resolutions, artifacts, visited, opts)
261
+ ) {
239
262
  return true
240
263
  }
241
264
  }
@@ -291,14 +314,17 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
291
314
  }
292
315
 
293
316
  yield {
294
- dependency: {
295
- url: exports.alias(url, opts),
296
- source,
297
- type,
298
- naturalType,
299
- imports: compressImportsMap(imports),
300
- lexer
301
- }
317
+ dependency: withWASI(
318
+ {
319
+ url: exports.alias(url, opts),
320
+ source,
321
+ type,
322
+ naturalType,
323
+ imports: compressImportsMap(imports),
324
+ lexer
325
+ },
326
+ opts
327
+ )
302
328
  }
303
329
 
304
330
  return true
@@ -342,7 +368,15 @@ exports.package = function* (url, source, artifacts, visited, opts = {}) {
342
368
  return false
343
369
  }
344
370
 
345
- exports.preresolved = function* (url, source, resolutions, artifacts, visited, opts = {}) {
371
+ exports.preresolved = function* (
372
+ url,
373
+ source,
374
+ attributes,
375
+ resolutions,
376
+ artifacts,
377
+ visited,
378
+ opts = {}
379
+ ) {
346
380
  const {
347
381
  builtinProtocol = 'builtin:',
348
382
  linkedProtocol = 'linked:',
@@ -353,6 +387,17 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
353
387
 
354
388
  if (typeof imports !== 'object' || imports === null) return false
355
389
 
390
+ attributes = attributes || {}
391
+
392
+ const parentURL = url
393
+
394
+ // The resolutions map leaves out the import attributes, so they are read
395
+ // from the source instead.
396
+ const lexed =
397
+ opts.asset !== true && isJavaScript(moduleType(url, attributes, null, opts))
398
+ ? lex(source)
399
+ : null
400
+
356
401
  let info = null
357
402
 
358
403
  for (const [specifier, entry] of Object.entries(imports)) {
@@ -382,7 +427,11 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
382
427
  } else if (url.protocol === deferredProtocol) {
383
428
  continue
384
429
  } else if (url.protocol === builtinProtocol || url.protocol === linkedProtocol) {
385
- if (addon && artifacts !== null) addURL(artifacts.addons, url)
430
+ if (addon) {
431
+ assertImportedAddonContext(url, imports, parentURL, opts)
432
+
433
+ if (artifacts !== null) addURL(artifacts.addons, url)
434
+ }
386
435
  } else if (asset) {
387
436
  if (artifacts !== null) {
388
437
  const expanded = yield* expandAsset(url, artifacts, visited, { ...opts, importType: 0 })
@@ -402,13 +451,24 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
402
451
  }
403
452
  }
404
453
  } else {
405
- if (addon && artifacts !== null) addURL(artifacts.addons, url)
454
+ if (addon) {
455
+ assertImportedAddonContext(url, imports, parentURL, opts)
456
+
457
+ if (artifacts !== null) addURL(artifacts.addons, url)
458
+ }
406
459
 
407
460
  yield {
408
- children: exports.module(url, null, {}, artifacts, visited, {
409
- ...opts,
410
- importType: 0
411
- }),
461
+ children: exports.module(
462
+ url,
463
+ null,
464
+ addon ? { type: 'addon' } : importAttributes(lexed, specifier, imports),
465
+ artifacts,
466
+ visited,
467
+ {
468
+ ...opts,
469
+ importType: 0
470
+ }
471
+ ),
412
472
  deferred: false
413
473
  }
414
474
  }
@@ -424,26 +484,27 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
424
484
  }
425
485
  }
426
486
 
427
- const type = moduleType(url, {}, info, opts)
487
+ const type = moduleType(url, attributes, info, opts)
428
488
 
429
489
  const lexer = { imports: [], exports: [] }
430
490
 
431
- if (opts.asset !== true && (type === constants.SCRIPT || type === constants.MODULE)) {
432
- const lexed = lex(source)
433
-
491
+ if (lexed !== null && isJavaScript(type)) {
434
492
  lexer.imports = lexed.imports
435
493
  lexer.exports = lexed.exports
436
494
  }
437
495
 
438
496
  yield {
439
- dependency: {
440
- url: exports.alias(url, opts),
441
- source,
442
- type,
443
- naturalType: type,
444
- imports: compressImportsMap(imports),
445
- lexer
446
- }
497
+ dependency: withWASI(
498
+ {
499
+ url: exports.alias(url, opts),
500
+ source,
501
+ type,
502
+ naturalType: naturalModuleType(url, attributes, info, type, opts),
503
+ imports: compressImportsMap(imports),
504
+ lexer
505
+ },
506
+ opts
507
+ )
447
508
  }
448
509
 
449
510
  return true
@@ -491,8 +552,10 @@ exports.link = function* (
491
552
  visited,
492
553
  opts = {}
493
554
  ) {
494
- if (entry.attributes.imports) {
495
- const specifier = entry.attributes.imports
555
+ for (const name of ['imports', 'wasi']) {
556
+ const specifier = entry.attributes[name]
557
+
558
+ if (!specifier) continue
496
559
 
497
560
  yield* resolveImport(
498
561
  { type: 0, specifier, names: [], attributes: {}, position: [0, 0, 0] },
@@ -515,7 +578,8 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
515
578
  builtinProtocol = 'builtin:',
516
579
  linkedProtocol = 'linked:',
517
580
  deferredProtocol = 'deferred:',
518
- deferUnresolved = false
581
+ deferUnresolved = false,
582
+ loadable = null
519
583
  } = opts
520
584
 
521
585
  const matchedConditions = []
@@ -524,7 +588,11 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
524
588
 
525
589
  matchedConditions.push(condition)
526
590
 
527
- const resolver = resolve(entry, parentURL, opts)
591
+ const resolver = resolve(
592
+ entry,
593
+ parentURL,
594
+ condition === 'addon' && opts.addonHost ? { ...opts, hosts: [opts.addonHost] } : opts
595
+ )
528
596
  const candidates = []
529
597
 
530
598
  let next = resolver.next()
@@ -548,6 +616,8 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
548
616
  url.protocol === linkedProtocol ||
549
617
  url.protocol === deferredProtocol
550
618
  ) {
619
+ if (condition === 'addon') assertImportedAddonContext(url, imports, parentURL, opts)
620
+
551
621
  addResolution(imports, specifier, matchedConditions, url)
552
622
 
553
623
  resolved = true
@@ -565,7 +635,7 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
565
635
  condition === 'addon' ||
566
636
  moduleType(url, entry.attributes, null, opts) === constants.ADDON
567
637
  ) {
568
- let exists = yield { probe: url }
638
+ let exists = isWebAssembly(url) ? undefined : yield { probe: url }
569
639
  let source = null
570
640
 
571
641
  if (exists === undefined) {
@@ -573,13 +643,15 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
573
643
  exists = source !== null
574
644
  }
575
645
 
576
- if (exists) {
577
- resolution = yield* postresolve(url)
646
+ if (exists) resolution = yield* postresolve(url)
647
+
648
+ if (exists && (loadable === null || loadable(resolution))) {
649
+ assertImportedAddonContext(resolution, imports, parentURL, opts)
578
650
 
579
651
  addResolution(imports, specifier, matchedConditions, exports.alias(resolution, opts))
580
652
 
581
653
  yield {
582
- children: exports.module(resolution, source, {}, artifacts, visited, {
654
+ children: exports.module(resolution, source, { type: 'addon' }, artifacts, visited, {
583
655
  ...opts,
584
656
  probed: true
585
657
  }),
@@ -608,6 +680,10 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
608
680
  attributes = { ...attributes, imports: imports[attributes.imports].default }
609
681
  }
610
682
 
683
+ if (attributes.wasi) {
684
+ attributes = { ...attributes, wasi: imports[attributes.wasi].default }
685
+ }
686
+
611
687
  yield {
612
688
  children: exports.module(resolution, source, attributes, artifacts, visited, {
613
689
  ...opts,
@@ -657,7 +733,109 @@ function* resolveImport(entry, specifier, condition, parentURL, imports, artifac
657
733
  }
658
734
  }
659
735
 
660
- const ADDON_EXTENSION = /\.(bare|node)$/
736
+ const ADDON_EXTENSION = /\.(bare|node|wasm)$/
737
+
738
+ function importAttributes(lexed, specifier, imports) {
739
+ const attributes = {}
740
+
741
+ if (lexed === null) return attributes
742
+
743
+ for (const entry of lexed.imports) {
744
+ if (entry.specifier !== specifier) continue
745
+
746
+ for (const name of ['type', 'host', 'wasi']) {
747
+ if (typeof entry.attributes[name] === 'string') attributes[name] = entry.attributes[name]
748
+ }
749
+ }
750
+
751
+ if (attributes.wasi !== undefined) attributes.wasi = defaultResolution(imports[attributes.wasi])
752
+
753
+ return attributes
754
+ }
755
+
756
+ function defaultResolution(entry) {
757
+ return typeof entry === 'string' ? entry : entry.default
758
+ }
759
+
760
+ // Only hosts that load on any platform may be asked for, so that an import
761
+ // can't pick the native addons of another platform.
762
+ const portableHosts = ['wasi-wasm32']
763
+
764
+ function addonHostForAttribute(host) {
765
+ if (portableHosts.includes(host)) return host
766
+
767
+ throw errors.UNKNOWN_ADDON_HOST(`Addon host '${host}' is not supported`)
768
+ }
769
+
770
+ function contextKey(host, wasi) {
771
+ return JSON.stringify([host, wasi])
772
+ }
773
+
774
+ function visitedFor(contexts, key) {
775
+ let visited = contexts.get(key)
776
+
777
+ if (visited === undefined) contexts.set(key, (visited = new Set()))
778
+
779
+ return visited
780
+ }
781
+
782
+ function withWASI(dependency, opts) {
783
+ const { addonWASI = null } = opts
784
+
785
+ if (addonWASI !== null && dependency.type === constants.ADDON && isWebAssembly(dependency.url)) {
786
+ dependency.wasi = addonWASI
787
+ }
788
+
789
+ return dependency
790
+ }
791
+
792
+ function assertImportedAddonContext(url, imports, parentURL, opts) {
793
+ const { addonHost = null, addonWASI = null, addonContexts } = opts
794
+
795
+ exports.assertAddonContext(
796
+ url,
797
+ imports['#package'] || parentURL.href,
798
+ { host: addonHost, wasi: addonWASI },
799
+ addonContexts
800
+ )
801
+ }
802
+
803
+ exports.assertAddonContext = function assertAddonContext(url, scope, context, addonContexts) {
804
+ if (url.protocol === 'builtin:') return
805
+
806
+ const { host = null } = context
807
+
808
+ if (host !== null && !isWebAssembly(url)) {
809
+ throw errors.ADDON_HOST_INCOMPATIBLE(
810
+ `Addon '${url.href}' is not a WebAssembly addon, but '${host}' was asked for`
811
+ )
812
+ }
813
+
814
+ // Only WebAssembly addons are given WASI capabilities.
815
+ const wasi = isWebAssembly(url) ? context.wasi || null : null
816
+
817
+ const recorded = addonContexts.get(scope)
818
+
819
+ if (recorded === undefined) {
820
+ addonContexts.set(scope, { host, wasi })
821
+ } else if (recorded.host !== host) {
822
+ throw errors.ADDON_HOST_INCOMPATIBLE(
823
+ `Addons of '${scope}' are already resolved for ${recorded.host === null ? 'the default hosts' : `'${recorded.host}'`}`
824
+ )
825
+ } else if (recorded.wasi !== wasi) {
826
+ throw errors.ADDON_WASI_INCOMPATIBLE(
827
+ `Addons of '${scope}' are already given ${recorded.wasi === null ? 'the default WASI capabilities' : `the WASI capabilities of '${recorded.wasi}'`}`
828
+ )
829
+ }
830
+ }
831
+
832
+ function isJavaScript(type) {
833
+ return type === constants.SCRIPT || type === constants.MODULE
834
+ }
835
+
836
+ function isWebAssembly(url) {
837
+ return url.pathname.endsWith('.wasm')
838
+ }
661
839
 
662
840
  exports.addons = function* (parentURL, artifacts, visited, opts = {}) {
663
841
  let yielded = false
@@ -897,9 +1075,7 @@ function moduleType(url, attributes, info, opts = {}) {
897
1075
 
898
1076
  const match = url.pathname.match(/\.[a-z]+$/)
899
1077
 
900
- if (match === null) return defaultType
901
-
902
- let [extension] = match
1078
+ let extension = match === null ? '.js' : match[0]
903
1079
 
904
1080
  if (aliases !== null && extension in aliases) extension = aliases[extension]
905
1081
 
package/lib/errors.js CHANGED
@@ -49,6 +49,18 @@ module.exports = class ModuleTraverseError extends Error {
49
49
  return new ModuleTraverseError(msg, ModuleTraverseError.TYPE_INCOMPATIBLE)
50
50
  }
51
51
 
52
+ static UNKNOWN_ADDON_HOST(msg) {
53
+ return new ModuleTraverseError(msg, ModuleTraverseError.UNKNOWN_ADDON_HOST)
54
+ }
55
+
56
+ static ADDON_HOST_INCOMPATIBLE(msg) {
57
+ return new ModuleTraverseError(msg, ModuleTraverseError.ADDON_HOST_INCOMPATIBLE)
58
+ }
59
+
60
+ static ADDON_WASI_INCOMPATIBLE(msg) {
61
+ return new ModuleTraverseError(msg, ModuleTraverseError.ADDON_WASI_INCOMPATIBLE)
62
+ }
63
+
52
64
  static AMBIGUOUS_MODULE_TYPE(msg) {
53
65
  return new ModuleTraverseError(msg, ModuleTraverseError.AMBIGUOUS_MODULE_TYPE)
54
66
  }
@@ -3,7 +3,7 @@ const resolve = require('../resolve')
3
3
  const runtime = require('../runtime')
4
4
 
5
5
  module.exports = function (entry, parentURL, opts = {}) {
6
- const { linked = false, host = runtime.host, hosts = [host] } = opts
6
+ const { linked = false, host = runtime.host, hosts = [host, 'wasi-wasm32'] } = opts
7
7
 
8
8
  let directories = false
9
9
  let extensions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-module-traverse",
3
- "version": "2.5.8",
3
+ "version": "2.6.0",
4
4
  "description": "Low-level module graph traversal for Bare",
5
5
  "exports": {
6
6
  ".": {