bare-module-traverse 2.3.0 → 2.4.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.
Files changed (4) hide show
  1. package/README.md +78 -12
  2. package/index.d.ts +31 -6
  3. package/index.js +256 -145
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # bare-module-traverse
2
2
 
3
- Low-level module graph traversal for Bare. The algorithm is implemented as a generator function that yields either modules to be read, prefixes to be listed, child dependencies to be traversed, or resolved dependencies of the module graph. As a convenience, the main export is a synchronous and asynchronous iterable that relies on modules being read and prefixes being listed by callbacks. For asynchronous iteration, the callbacks may return promises which will be awaited before being passed to the generator.
3
+ Low-level module graph traversal for Bare. The algorithm is implemented as a generator function that yields either modules to be read, modules to be probed for existence, resolutions to be transformed, prefixes to be listed, sets of imports to be resolved, child dependencies to be traversed, or resolved dependencies of the module graph. As a convenience, the main export is a synchronous and asynchronous iterable that relies on modules being read, modules being probed, resolutions being transformed, and prefixes being listed by callbacks. For asynchronous iteration, the callbacks may return promises which will be awaited before being passed to the generator.
4
4
 
5
5
  ```
6
6
  npm i bare-module-traverse
@@ -50,9 +50,9 @@ for await (const dependency of traverse(
50
50
 
51
51
  ## API
52
52
 
53
- #### `const dependencies = traverse(url[, options], readModule[, listPrefix])`
53
+ #### `const dependencies = traverse(url[, options], readModule[, listPrefix[, probeModule[, resolveModule]]])`
54
54
 
55
- Traverse the module graph rooted at `url`, which must be a WHATWG `URL` instance. `readModule` is called with a `URL` instance for every module to be read and must either return the module source, if it exists, or `null`. `listPrefix` is called with a `URL` instance of every prefix to be listed and must yield `URL` instances that have the specified `URL` as a prefix. If not provided, prefixes won't be traversed. If `readModule` returns a promise or `listPrefix` returns a promise generator, synchronous iteration is not supported.
55
+ Traverse the module graph rooted at `url`, which must be a WHATWG `URL` instance. `readModule` is called with a `URL` instance for every module to be read and must either return the module source, if it exists, or `null`. `listPrefix` is called with a `URL` instance of every prefix to be listed and must yield `URL` instances that have the specified `URL` as a prefix. If not provided, prefixes won't be traversed. `probeModule` is called with a `URL` instance to test whether a module exists and must return `true` if it does, `false` if it doesn't, or `undefined` if probing isn't supported; this lets existence be checked without reading the full module source, such as when locating an addon or asset. When it returns `undefined`, existence is instead determined by reading the module, so no source is read twice. If not provided, `probeModule` returns `undefined` and no probing is performed. `resolveModule` is called with a `URL` instance for every resolved, existing module and must return the `URL` to use in its place, applying any post-resolution transform; a file system implementation would canonicalize symlinks here with `realpath` so a module reached through different symlinks dedupes against its real location. If not provided, resolutions are used unchanged. If `readModule`, `probeModule`, or `resolveModule` returns a promise, or `listPrefix` returns a promise generator, synchronous iteration is not supported.
56
56
 
57
57
  Options include:
58
58
 
@@ -165,22 +165,58 @@ The following generator functions implement the traversal algorithm. The yielded
165
165
 
166
166
  **Source module**
167
167
 
168
+ A module to be read. The driver returns its source if it exists, otherwise `null`.
169
+
168
170
  ```js
169
171
  next.value = {
170
172
  module: URL
171
173
  }
172
174
  ```
173
175
 
176
+ **Probed module**
177
+
178
+ A module whose existence is to be tested without reading its full source, such as when locating an addon or asset. The driver returns `true` if it exists, `false` if it doesn't, or `undefined` if probing isn't supported, in which case existence is instead determined by reading the module.
179
+
180
+ ```js
181
+ next.value = {
182
+ probe: URL
183
+ }
184
+ ```
185
+
186
+ **Resolved module**
187
+
188
+ A resolved, existing module whose URL is to be transformed. The driver returns the URL to use in its place, applying any post-resolution transform, such as canonicalizing symlinks with `realpath`, or the URL unchanged.
189
+
190
+ ```js
191
+ next.value = {
192
+ resolution: URL
193
+ }
194
+ ```
195
+
174
196
  **File prefix**
175
197
 
198
+ A prefix to be listed. The driver returns the URLs that have it as a prefix, of which there may be none.
199
+
176
200
  ```js
177
201
  next.value = {
178
202
  prefix: URL
179
203
  }
180
204
  ```
181
205
 
206
+ **Import set**
207
+
208
+ A set of independent imports to resolve. Each generator must be driven to completion before the parent generator is resumed, since the parent's resolved imports aren't complete until they are. The generators yield the same values as any other and may themselves yield dependency subgraphs to be traversed. A driver may drive them one at a time or, as their resolutions are independent, concurrently.
209
+
210
+ ```js
211
+ next.value = {
212
+ links: [Generator]
213
+ }
214
+ ```
215
+
182
216
  **Dependency subgraph**
183
217
 
218
+ A child subgraph to be traversed by driving its generator as the parent is driven. If `deferred` is `true`, it must be traversed only once all non-deferred subgraphs have been, ensuring, for example, that a module reached both as an import and as an asset is claimed by the import traversal first.
219
+
184
220
  ```js
185
221
  next.value = {
186
222
  children: Generator,
@@ -190,6 +226,8 @@ next.value = {
190
226
 
191
227
  **Dependency node**
192
228
 
229
+ A fully resolved node of the module graph and the traversal's output. This is what the iterable forms yield to the caller.
230
+
193
231
  ```js
194
232
  next.value = {
195
233
  dependency: {
@@ -211,14 +249,16 @@ next.value = {
211
249
  }
212
250
  ```
213
251
 
214
- To drive the generator functions, a loop like the following can be used:
252
+ To drive the generator functions, a recursive routine like the following can be used:
215
253
 
216
254
  ```js
217
- const queue = [traverse.module(url, source, artifacts, visited)]
255
+ const artifacts = { addons: [], assets: [] }
256
+ const visited = new Set()
218
257
 
219
- while (queue.length > 0) {
220
- const generator = queue.pop()
258
+ const queue = [traverse.module(url, null, {}, artifacts, visited)]
259
+ const deferred = []
221
260
 
261
+ function drive(generator) {
222
262
  let next = generator.next()
223
263
 
224
264
  while (next.done !== true) {
@@ -229,22 +269,46 @@ while (queue.length > 0) {
229
269
  let source
230
270
 
231
271
  next = generator.next(source)
272
+ } else if (value.probe) {
273
+ // Test whether `value.probe` exists, returning `true`, `false`, or
274
+ // `undefined` if probing isn't supported
275
+ let exists
276
+
277
+ next = generator.next(exists)
278
+ } else if (value.resolution) {
279
+ // Transform `value.resolution`, e.g. canonicalize it with `realpath`, or
280
+ // pass it through unchanged
281
+ let resolution = value.resolution
282
+
283
+ next = generator.next(resolution)
232
284
  } else if (value.prefix) {
233
285
  // List the modules that have `value.prefix` as a prefix
234
286
  let modules
235
287
 
236
288
  next = generator.next(modules)
289
+ } else if (value.links) {
290
+ // Drive each import to completion before resuming; their resolutions are
291
+ // independent, so a concurrent driver may instead drive them in parallel
292
+ for (const link of value.links) drive(link)
293
+
294
+ next = generator.next()
295
+ } else if (value.children) {
296
+ // Defer the subgraph if requested, otherwise traverse it next
297
+ if (value.deferred) deferred.push(value.children)
298
+ else queue.push(value.children)
299
+
300
+ next = generator.next()
237
301
  } else {
238
- if (value.children) {
239
- queue.push(value.children)
240
- } else {
241
- const dependency = value.dependency
242
- }
302
+ const dependency = value.dependency
243
303
 
244
304
  next = generator.next()
245
305
  }
246
306
  }
247
307
  }
308
+
309
+ while (queue.length > 0 || deferred.length > 0) {
310
+ drive(queue.length > 0 ? queue.pop() : deferred.shift())
311
+ }
248
312
  ```
249
313
 
250
314
  Options are the same as `traverse()` for all functions.
@@ -260,6 +324,8 @@ Options are the same as `traverse()` for all functions.
260
324
 
261
325
  #### `const generator = traverse.imports(parentURL, source, imports, artifacts, visited[, options])`
262
326
 
327
+ #### `const generator = traverse.link(entry, specifier, condition, parentURL, imports, artifacts, visited[, options])`
328
+
263
329
  #### `const generator = traverse.addons(parentURL, artifacts, visited[, options])`
264
330
 
265
331
  #### `const generator = traverse.assets(patterns, parentURL, artifacts, visited[, options])`
package/index.d.ts CHANGED
@@ -42,36 +42,50 @@ interface TraverseOptions extends ResolveOptions {
42
42
  declare function traverse(
43
43
  entry: URL,
44
44
  readModule: (url: URL) => Buffer | string | null,
45
- listPrefix?: (url: URL) => Iterable<URL>
45
+ listPrefix?: (url: URL) => Iterable<URL>,
46
+ probeModule?: (url: URL) => boolean | undefined,
47
+ resolveModule?: (url: URL) => URL
46
48
  ): Iterable<Dependency>
47
49
 
48
50
  declare function traverse(
49
51
  entry: URL,
50
52
  readModule: (url: URL) => Promise<Buffer | string | null>,
51
- listPrefix?: (url: URL) => AsyncIterable<URL>
53
+ listPrefix?: (url: URL) => AsyncIterable<URL>,
54
+ probeModule?: (url: URL) => Promise<boolean | undefined>,
55
+ resolveModule?: (url: URL) => Promise<URL>
52
56
  ): AsyncIterable<Dependency>
53
57
 
54
58
  declare function traverse(
55
59
  entry: URL,
56
60
  opts: TraverseOptions,
57
61
  readModule: (url: URL) => Buffer | string | null,
58
- listPrefix?: (url: URL) => Iterable<URL>
62
+ listPrefix?: (url: URL) => Iterable<URL>,
63
+ probeModule?: (url: URL) => boolean | undefined,
64
+ resolveModule?: (url: URL) => URL
59
65
  ): Iterable<Dependency>
60
66
 
61
67
  declare function traverse(
62
68
  entry: URL,
63
69
  opts: TraverseOptions,
64
70
  readModule: (url: URL) => Promise<Buffer | string | null>,
65
- listPrefix?: (url: URL) => AsyncIterable<URL>
71
+ listPrefix?: (url: URL) => AsyncIterable<URL>,
72
+ probeModule?: (url: URL) => Promise<boolean | undefined>,
73
+ resolveModule?: (url: URL) => Promise<URL>
66
74
  ): AsyncIterable<Dependency>
67
75
 
68
76
  declare namespace traverse {
69
77
  export { type TraverseOptions }
70
78
 
71
79
  export type Traversal = Generator<
72
- { module: URL } | { prefix: URL } | { children: URL; deferred: boolean } | { dependency: URL },
80
+ | { module: URL }
81
+ | { probe: URL }
82
+ | { resolution: URL }
83
+ | { prefix: URL }
84
+ | { links: Traversal[] }
85
+ | { children: Traversal; deferred: boolean }
86
+ | { dependency: Dependency },
73
87
  boolean,
74
- void | URL[] | Buffer | string | null
88
+ void | URL | URL[] | Buffer | string | boolean | null
75
89
  >
76
90
 
77
91
  export interface Artifacts {
@@ -124,6 +138,17 @@ declare namespace traverse {
124
138
  opts?: TraverseOptions
125
139
  ): Traversal
126
140
 
141
+ export function link(
142
+ entry: Import,
143
+ specifier: string,
144
+ condition: string,
145
+ parentURL: URL,
146
+ imports: ImportsMap,
147
+ artifacts: Artifacts,
148
+ visited: Set<string>,
149
+ opts?: TraverseOptions
150
+ ): Traversal
151
+
127
152
  export function prebuilds(
128
153
  packageURL: URL,
129
154
  artifacts: Artifacts,
package/index.js CHANGED
@@ -13,13 +13,33 @@ const constants = {
13
13
  TEXT: 7
14
14
  }
15
15
 
16
- module.exports = exports = function traverse(entry, opts, readModule, listPrefix) {
16
+ function defaultResolveModule(url) {
17
+ return url
18
+ }
19
+
20
+ function defaultProbeModule() {
21
+ return undefined
22
+ }
23
+
24
+ module.exports = exports = function traverse(
25
+ entry,
26
+ opts,
27
+ readModule,
28
+ listPrefix,
29
+ probeModule,
30
+ resolveModule
31
+ ) {
17
32
  if (typeof opts === 'function') {
33
+ resolveModule = probeModule
34
+ probeModule = listPrefix
18
35
  listPrefix = readModule
19
36
  readModule = opts
20
37
  opts = {}
21
38
  }
22
39
 
40
+ if (typeof resolveModule !== 'function') resolveModule = defaultResolveModule
41
+ if (typeof probeModule !== 'function') probeModule = defaultProbeModule
42
+
23
43
  return {
24
44
  *[Symbol.iterator]() {
25
45
  const artifacts = { addons: [], assets: [] }
@@ -27,15 +47,9 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
27
47
  const visited = opts.visited || new Set()
28
48
 
29
49
  const queue = [exports.module(entry, null, {}, artifacts, visited, opts)]
30
-
31
- // Asset traversal is deferred until the module graph has been fully
32
- // drained so that a module imported both as an asset and as a regular
33
- // module is typed by the regular import
34
50
  const deferred = []
35
51
 
36
- while (queue.length > 0 || deferred.length > 0) {
37
- const generator = queue.length > 0 ? queue.pop() : deferred.shift()
38
-
52
+ function* drive(generator) {
39
53
  let next = generator.next()
40
54
 
41
55
  while (next.done !== true) {
@@ -43,6 +57,10 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
43
57
 
44
58
  if (value.module) {
45
59
  next = generator.next(readModule(value.module))
60
+ } else if (value.probe) {
61
+ next = generator.next(probeModule(value.probe))
62
+ } else if (value.resolution) {
63
+ next = generator.next(resolveModule(value.resolution))
46
64
  } else if (value.prefix) {
47
65
  const result = []
48
66
 
@@ -57,17 +75,27 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
57
75
  }
58
76
 
59
77
  next = generator.next(result)
78
+ } else if (value.links) {
79
+ for (const link of value.links) yield* drive(link)
80
+
81
+ next = generator.next()
82
+ } else if (value.children) {
83
+ if (value.deferred) deferred.push(value.children)
84
+ else queue.push(value.children)
85
+
86
+ next = generator.next()
60
87
  } else {
61
- if (value.children) {
62
- if (value.deferred) deferred.push(value.children)
63
- else queue.push(value.children)
64
- } else yield value.dependency
88
+ yield value.dependency
65
89
 
66
90
  next = generator.next()
67
91
  }
68
92
  }
69
93
  }
70
94
 
95
+ while (queue.length > 0 || deferred.length > 0) {
96
+ yield* drive(queue.length > 0 ? queue.pop() : deferred.shift())
97
+ }
98
+
71
99
  return artifacts
72
100
  },
73
101
 
@@ -77,15 +105,9 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
77
105
  const visited = opts.visited || new Set()
78
106
 
79
107
  const queue = [exports.module(entry, null, {}, artifacts, visited, opts)]
80
-
81
- // Asset traversal is deferred until the module graph has been fully
82
- // drained so that a module imported both as an asset and as a regular
83
- // module is typed by the regular import.
84
108
  const deferred = []
85
109
 
86
- while (queue.length > 0 || deferred.length > 0) {
87
- const generator = queue.length > 0 ? queue.pop() : deferred.shift()
88
-
110
+ async function* drive(generator) {
89
111
  let next = generator.next()
90
112
 
91
113
  while (next.done !== true) {
@@ -93,6 +115,10 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
93
115
 
94
116
  if (value.module) {
95
117
  next = generator.next(await readModule(value.module))
118
+ } else if (value.probe) {
119
+ next = generator.next(await probeModule(value.probe))
120
+ } else if (value.resolution) {
121
+ next = generator.next(await resolveModule(value.resolution))
96
122
  } else if (value.prefix) {
97
123
  const result = []
98
124
 
@@ -107,17 +133,27 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
107
133
  }
108
134
 
109
135
  next = generator.next(result)
136
+ } else if (value.links) {
137
+ for (const link of value.links) yield* drive(link)
138
+
139
+ next = generator.next()
140
+ } else if (value.children) {
141
+ if (value.deferred) deferred.push(value.children)
142
+ else queue.push(value.children)
143
+
144
+ next = generator.next()
110
145
  } else {
111
- if (value.children) {
112
- if (value.deferred) deferred.push(value.children)
113
- else queue.push(value.children)
114
- } else yield value.dependency
146
+ yield value.dependency
115
147
 
116
148
  next = generator.next()
117
149
  }
118
150
  }
119
151
  }
120
152
 
153
+ while (queue.length > 0 || deferred.length > 0) {
154
+ yield* drive(queue.length > 0 ? queue.pop() : deferred.shift())
155
+ }
156
+
121
157
  return artifacts
122
158
  }
123
159
  }
@@ -147,16 +183,28 @@ exports.alias = function alias(url, opts = {}) {
147
183
  }
148
184
 
149
185
  exports.module = function* (url, source, attributes, artifacts, visited, opts = {}) {
150
- const { resolutions = null, asset = false } = opts
186
+ const { resolutions = null, asset = false, probed } = opts
151
187
 
152
188
  if (visited.has(url.href)) return false
153
189
 
154
190
  visited.add(url.href)
155
191
 
192
+ if (probed !== undefined) opts = { ...opts, probed: undefined }
193
+
194
+ attributes = attributes || {}
195
+
196
+ const artifact = asset === true || moduleType(url, attributes, null, opts) === constants.ADDON
197
+
156
198
  if (source === null) {
199
+ const exists = probed !== undefined ? probed : artifact ? yield { probe: url } : undefined
200
+
201
+ if (exists === false) {
202
+ throw errors.MODULE_NOT_FOUND(`Cannot find module '${url.href}'`, url.href)
203
+ }
204
+
157
205
  source = yield { module: url }
158
206
 
159
- if (source === null) {
207
+ if (exists !== true && source === null) {
160
208
  throw errors.MODULE_NOT_FOUND(`Cannot find module '${url.href}'`, url.href)
161
209
  }
162
210
  }
@@ -167,8 +215,6 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
167
215
  }
168
216
  }
169
217
 
170
- attributes = attributes || {}
171
-
172
218
  const imports = {}
173
219
 
174
220
  let info = null
@@ -207,10 +253,6 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
207
253
 
208
254
  const lexer = { imports: [], exports: [] }
209
255
 
210
- // Assets are opaque; their contents are never interpreted, so we only recurse
211
- // into modules that are actually imported as modules. An asset that happens
212
- // to be a script, module, or addon is not lexed and its imports and addons
213
- // are not followed.
214
256
  if (asset === false) {
215
257
  if (type === constants.SCRIPT || type === constants.MODULE) {
216
258
  yield* exports.imports(url, source, imports, artifacts, lexer, visited, opts)
@@ -281,10 +323,10 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
281
323
  if (typeof imports !== 'object' || imports === null) return false
282
324
 
283
325
  for (const [specifier, entry] of Object.entries(imports)) {
284
- const stack = [entry]
326
+ const stack = [{ entry, asset: false }]
285
327
 
286
328
  while (stack.length > 0) {
287
- const entry = stack.pop()
329
+ const { entry, asset } = stack.pop()
288
330
 
289
331
  if (typeof entry === 'string') {
290
332
  const url = new URL(entry)
@@ -294,6 +336,20 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
294
336
  children: exports.package(url, null, artifacts, visited, opts),
295
337
  deferred: false
296
338
  }
339
+ } else if (asset) {
340
+ addURL(artifacts.assets, url)
341
+
342
+ const source = yield { module: url }
343
+
344
+ if (source !== null) {
345
+ yield {
346
+ children: exports.module(url, source, {}, artifacts, visited, {
347
+ ...opts,
348
+ asset: true
349
+ }),
350
+ deferred: true
351
+ }
352
+ }
297
353
  } else if (
298
354
  url.protocol !== builtinProtocol &&
299
355
  url.protocol !== linkedProtocol &&
@@ -305,14 +361,13 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
305
361
  }
306
362
  }
307
363
  } else {
308
- stack.unshift(...Object.values(entry))
364
+ for (const [condition, child] of Object.entries(entry)) {
365
+ stack.push({ entry: child, asset: asset || condition === 'asset' })
366
+ }
309
367
  }
310
368
  }
311
369
  }
312
370
 
313
- // The type is derived from the extension and default type alone; unlike
314
- // `module`, the preresolved path does not read the package scope, so any
315
- // `package.json` type declaration is not consulted here.
316
371
  const type = moduleType(url, {}, null, opts)
317
372
 
318
373
  const lexer = { imports: [], exports: [] }
@@ -335,22 +390,12 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
335
390
  }
336
391
 
337
392
  exports.imports = function* (parentURL, source, imports, artifacts, lexer, visited, opts = {}) {
338
- const {
339
- resolve = exports.resolve.default,
340
- builtinProtocol = 'builtin:',
341
- linkedProtocol = 'linked:',
342
- deferredProtocol = 'deferred:',
343
- matchedConditions = []
344
- } = opts
345
-
346
- let yielded = false
347
-
348
- const queue = []
349
-
350
393
  const lexed = lex(source)
351
394
 
352
395
  lexer.exports = lexed.exports
353
396
 
397
+ const links = []
398
+
354
399
  for (const entry of lexed.imports) {
355
400
  let specifier = entry.specifier
356
401
  let condition = 'default'
@@ -360,131 +405,187 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
360
405
  condition = 'addon'
361
406
  } else if (entry.type & lex.constants.ASSET) {
362
407
  condition = 'asset'
363
- }
364
-
365
- if (entry.attributes.imports) {
366
- const specifier = entry.attributes.imports
367
-
368
- queue.push({
369
- entry: {
370
- type: 0,
371
- specifier,
372
- names: [],
373
- attributes: {},
374
- position: [0, 0, 0]
375
- },
376
- specifier,
377
- condition: 'default'
378
- })
408
+ } else if (entry.type & lex.constants.REQUIRE) {
409
+ condition = 'require'
410
+ } else if (entry.type & lex.constants.IMPORT) {
411
+ condition = 'import'
379
412
  }
380
413
 
381
414
  lexer.imports.push(entry)
382
415
 
383
- queue.push({ entry, specifier, condition })
416
+ links.push(
417
+ exports.link(entry, specifier, condition, parentURL, imports, artifacts, visited, opts)
418
+ )
419
+ }
420
+
421
+ yield { links }
422
+ }
423
+
424
+ exports.link = function* (
425
+ entry,
426
+ specifier,
427
+ condition,
428
+ parentURL,
429
+ imports,
430
+ artifacts,
431
+ visited,
432
+ opts = {}
433
+ ) {
434
+ if (entry.attributes.imports) {
435
+ const specifier = entry.attributes.imports
436
+
437
+ yield* resolveImport(
438
+ { type: 0, specifier, names: [], attributes: {}, position: [0, 0, 0] },
439
+ specifier,
440
+ 'default',
441
+ parentURL,
442
+ imports,
443
+ artifacts,
444
+ visited,
445
+ opts
446
+ )
384
447
  }
385
448
 
386
- while (queue.length > 0) {
387
- const { entry, specifier, condition } = queue.shift()
449
+ yield* resolveImport(entry, specifier, condition, parentURL, imports, artifacts, visited, opts)
450
+ }
388
451
 
389
- matchedConditions.push(condition)
452
+ function* resolveImport(entry, specifier, condition, parentURL, imports, artifacts, visited, opts) {
453
+ const {
454
+ resolve = exports.resolve.default,
455
+ builtinProtocol = 'builtin:',
456
+ linkedProtocol = 'linked:',
457
+ deferredProtocol = 'deferred:'
458
+ } = opts
390
459
 
391
- const resolver = resolve(entry, parentURL, { ...opts, matchedConditions })
392
- const candidates = []
460
+ const matchedConditions = []
393
461
 
394
- let next = resolver.next()
395
- let resolutions = 0
462
+ opts = { ...opts, matchedConditions }
396
463
 
397
- while (next.done !== true) {
398
- const value = next.value
464
+ matchedConditions.push(condition)
399
465
 
400
- if (value.package) {
401
- next = resolver.next(JSON.parse(yield { module: value.package }))
402
- } else {
403
- const url = value.resolution
466
+ const resolver = resolve(entry, parentURL, opts)
467
+ const candidates = []
404
468
 
405
- candidates.push(url)
469
+ let next = resolver.next()
470
+ let resolutions = 0
406
471
 
407
- let resolved = false
472
+ while (next.done !== true) {
473
+ const value = next.value
408
474
 
409
- if (
410
- url.protocol === builtinProtocol ||
411
- url.protocol === linkedProtocol ||
412
- url.protocol === deferredProtocol
413
- ) {
414
- addResolution(imports, specifier, matchedConditions, url)
475
+ if (value.package) {
476
+ next = resolver.next(JSON.parse(yield { module: value.package }))
477
+ } else {
478
+ const url = value.resolution
415
479
 
416
- resolved = yielded = true
417
- } else if (condition === 'asset') {
418
- const prefix = url
480
+ candidates.push(url)
419
481
 
420
- for (const url of yield { prefix }) {
421
- yield {
422
- children: exports.module(url, null, {}, artifacts, visited, {
423
- ...opts,
424
- asset: true
425
- }),
426
- deferred: true
427
- }
482
+ let resolved = false
483
+ let resolution = url
484
+
485
+ if (
486
+ url.protocol === builtinProtocol ||
487
+ url.protocol === linkedProtocol ||
488
+ url.protocol === deferredProtocol
489
+ ) {
490
+ addResolution(imports, specifier, matchedConditions, url)
428
491
 
429
- addURL(artifacts.assets, url)
492
+ resolved = true
493
+ } else if (condition === 'asset') {
494
+ const prefix = url
430
495
 
431
- resolved = yielded = true
496
+ for (const url of yield { prefix }) {
497
+ const resolution = yield* postresolve(url)
498
+
499
+ yield {
500
+ children: exports.module(resolution, null, {}, artifacts, visited, {
501
+ ...opts,
502
+ asset: true
503
+ }),
504
+ deferred: true
432
505
  }
433
506
 
434
- if (resolved) addResolution(imports, specifier, matchedConditions, url)
435
- } else {
436
- const source = yield { module: url }
507
+ addURL(artifacts.assets, resolution)
437
508
 
438
- if (source !== null) {
439
- addResolution(imports, specifier, matchedConditions, exports.alias(url, opts))
509
+ resolved = true
510
+ }
440
511
 
441
- let attributes = entry.attributes
512
+ if (resolved) addResolution(imports, specifier, matchedConditions, url)
513
+ } else if (condition === 'addon') {
514
+ let exists = yield { probe: url }
515
+ let source = null
442
516
 
443
- if (attributes.imports) {
444
- attributes = { ...attributes, imports: imports[attributes.imports].default }
445
- }
517
+ if (exists === undefined) {
518
+ source = yield { module: url }
519
+ exists = source !== null
520
+ }
446
521
 
447
- yield {
448
- children: exports.module(url, source, attributes, artifacts, visited, opts),
449
- deferred: false
450
- }
522
+ if (exists) {
523
+ resolution = yield* postresolve(url)
451
524
 
452
- resolved = yielded = true
525
+ addResolution(imports, specifier, matchedConditions, resolution)
526
+
527
+ yield {
528
+ children: exports.module(resolution, source, {}, artifacts, visited, {
529
+ ...opts,
530
+ probed: true
531
+ }),
532
+ deferred: false
453
533
  }
534
+
535
+ resolved = true
454
536
  }
537
+ } else {
538
+ const source = yield { module: url }
455
539
 
456
- if (resolved) {
457
- if (condition === 'addon') addURL(artifacts.addons, url)
540
+ if (source !== null) {
541
+ resolution = yield* postresolve(url)
458
542
 
459
- resolutions++
460
- }
543
+ addResolution(imports, specifier, matchedConditions, exports.alias(resolution, opts))
461
544
 
462
- next = resolver.next(resolved)
463
- }
464
- }
545
+ let attributes = entry.attributes
465
546
 
466
- matchedConditions.pop()
547
+ if (attributes.imports) {
548
+ attributes = { ...attributes, imports: imports[attributes.imports].default }
549
+ }
467
550
 
468
- if (resolutions === 0) {
469
- let message = `Cannot find ${condition === 'default' ? 'module' : condition} '${specifier}' imported from '${parentURL.href}'`
551
+ yield {
552
+ children: exports.module(resolution, source, attributes, artifacts, visited, opts),
553
+ deferred: false
554
+ }
470
555
 
471
- if (candidates.length > 0) {
472
- message += '\nCandidates:'
473
- message += '\n' + candidates.map((url) => '- ' + url.href).join('\n')
556
+ resolved = true
557
+ }
474
558
  }
475
559
 
476
- switch (condition) {
477
- case 'addon':
478
- throw errors.ADDON_NOT_FOUND(message, specifier, parentURL, candidates)
479
- case 'asset':
480
- throw errors.ASSET_NOT_FOUND(message, specifier, parentURL, candidates)
481
- default:
482
- throw errors.MODULE_NOT_FOUND(message, specifier, parentURL, candidates)
560
+ if (resolved) {
561
+ if (condition === 'addon') addURL(artifacts.addons, resolution)
562
+
563
+ resolutions++
483
564
  }
565
+
566
+ next = resolver.next(resolved)
484
567
  }
485
568
  }
486
569
 
487
- return yielded
570
+ matchedConditions.pop()
571
+
572
+ if (resolutions === 0) {
573
+ let message = `Cannot find ${condition === 'addon' || condition === 'asset' ? condition : 'module'} '${specifier}' imported from '${parentURL.href}'`
574
+
575
+ if (candidates.length > 0) {
576
+ message += '\nCandidates:'
577
+ message += '\n' + candidates.map((url) => '- ' + url.href).join('\n')
578
+ }
579
+
580
+ switch (condition) {
581
+ case 'addon':
582
+ throw errors.ADDON_NOT_FOUND(message, specifier, parentURL, candidates)
583
+ case 'asset':
584
+ throw errors.ASSET_NOT_FOUND(message, specifier, parentURL, candidates)
585
+ default:
586
+ throw errors.MODULE_NOT_FOUND(message, specifier, parentURL, candidates)
587
+ }
588
+ }
488
589
  }
489
590
 
490
591
  const ADDON_EXTENSION = /\.(bare|node)$/
@@ -498,12 +599,14 @@ exports.addons = function* (parentURL, artifacts, visited, opts = {}) {
498
599
  prefix.pathname = prefix.pathname.replace(ADDON_EXTENSION, '') + '/'
499
600
 
500
601
  for (const url of yield { prefix }) {
602
+ const resolution = yield* postresolve(url)
603
+
501
604
  yield {
502
- children: exports.module(url, null, {}, artifacts, visited, opts),
605
+ children: exports.module(resolution, null, {}, artifacts, visited, opts),
503
606
  deferred: false
504
607
  }
505
608
 
506
- addURL(artifacts.addons, url)
609
+ addURL(artifacts.addons, resolution)
507
610
 
508
611
  yielded = true
509
612
  }
@@ -521,10 +624,12 @@ exports.assets = function* (patterns, parentURL, artifacts, visited, opts = {})
521
624
  const source = yield { module: url }
522
625
 
523
626
  if (source !== null) {
524
- addURL(artifacts.assets, url)
627
+ const resolution = yield* postresolve(url)
628
+
629
+ addURL(artifacts.assets, resolution)
525
630
 
526
631
  yield {
527
- children: exports.module(url, source, {}, artifacts, visited, {
632
+ children: exports.module(resolution, source, {}, artifacts, visited, {
528
633
  ...opts,
529
634
  asset: true
530
635
  }),
@@ -601,10 +706,10 @@ exports.patternMatches = function* patternMatches(pattern, parentURL, matches, o
601
706
  return matches
602
707
  }
603
708
 
604
- // Determine the module type from an explicit type attribute, if given, and
605
- // otherwise from the module's extension. For the ambiguous `.js` and `.ts`
606
- // extensions the nearest `package.json`, passed as `info`, and the default type
607
- // break the tie. Returns `0` when the type cannot be determined.
709
+ function* postresolve(url) {
710
+ return (yield { resolution: url }) || url
711
+ }
712
+
608
713
  function moduleType(url, attributes, info, opts = {}) {
609
714
  const { defaultType = constants.SCRIPT, aliases = null } = opts
610
715
 
@@ -631,7 +736,7 @@ function moduleType(url, attributes, info, opts = {}) {
631
736
 
632
737
  const match = url.pathname.match(/\.[a-z]+$/)
633
738
 
634
- if (match === null) return 0
739
+ if (match === null) return defaultType
635
740
 
636
741
  let [extension] = match
637
742
 
@@ -662,7 +767,7 @@ function moduleType(url, attributes, info, opts = {}) {
662
767
  return constants.TEXT
663
768
  }
664
769
 
665
- return 0
770
+ return defaultType
666
771
  }
667
772
 
668
773
  function addURL(collection, url) {
@@ -765,6 +870,12 @@ function compressImportsMapEntry(resolved) {
765
870
  if (entry[0] === 'default') primary = entry[1]
766
871
  }
767
872
 
873
+ if (entries.length === 0) return resolved
874
+
875
+ const [, first] = entries[0]
876
+
877
+ if (entries.every(([, resolved]) => resolved === first)) return first
878
+
768
879
  entries = entries.filter(
769
880
  ([condition, resolved]) => condition === 'default' || resolved !== primary
770
881
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-module-traverse",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "description": "Low-level module graph traversal for Bare",
5
5
  "exports": {
6
6
  ".": {