bare-module-traverse 2.3.1 → 2.4.1

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 +84 -15
  2. package/index.d.ts +38 -9
  3. package/index.js +257 -161
  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,59 @@ 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`. When `artifact` is `true`, the module is an addon or asset whose contents are loaded lazily and referenced by path. A driver that only needs to locate such artifacts, such as a module loader, may return `null` without reading, having already established existence by probing; a driver that embeds their contents, such as a bundler, reads them as normal.
169
+
170
+ ```js
171
+ next.value = {
172
+ module: URL,
173
+ artifact: boolean
174
+ }
175
+ ```
176
+
177
+ **Probed module**
178
+
179
+ 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.
180
+
168
181
  ```js
169
182
  next.value = {
170
- module: URL
183
+ probe: URL
184
+ }
185
+ ```
186
+
187
+ **Resolved module**
188
+
189
+ 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.
190
+
191
+ ```js
192
+ next.value = {
193
+ resolution: URL
171
194
  }
172
195
  ```
173
196
 
174
197
  **File prefix**
175
198
 
199
+ A prefix to be listed. The driver returns the URLs that have it as a prefix, of which there may be none.
200
+
176
201
  ```js
177
202
  next.value = {
178
203
  prefix: URL
179
204
  }
180
205
  ```
181
206
 
207
+ **Import set**
208
+
209
+ 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.
210
+
211
+ ```js
212
+ next.value = {
213
+ links: [Generator]
214
+ }
215
+ ```
216
+
182
217
  **Dependency subgraph**
183
218
 
219
+ 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.
220
+
184
221
  ```js
185
222
  next.value = {
186
223
  children: Generator,
@@ -190,6 +227,8 @@ next.value = {
190
227
 
191
228
  **Dependency node**
192
229
 
230
+ A fully resolved node of the module graph and the traversal's output. This is what the iterable forms yield to the caller.
231
+
193
232
  ```js
194
233
  next.value = {
195
234
  dependency: {
@@ -211,40 +250,68 @@ next.value = {
211
250
  }
212
251
  ```
213
252
 
214
- To drive the generator functions, a loop like the following can be used:
253
+ To drive the generator functions, a recursive routine like the following can be used:
215
254
 
216
255
  ```js
217
- const queue = [traverse.module(url, source, artifacts, visited)]
256
+ const artifacts = { addons: [], assets: [] }
257
+ const visited = new Set()
218
258
 
219
- while (queue.length > 0) {
220
- const generator = queue.pop()
259
+ const queue = [traverse.module(url, null, {}, artifacts, visited)]
260
+ const deferred = []
221
261
 
262
+ function drive(generator) {
222
263
  let next = generator.next()
223
264
 
224
265
  while (next.done !== true) {
225
266
  const value = next.value
226
267
 
227
268
  if (value.module) {
228
- // Read `value.module` if it exists, otherwise `null`
269
+ // Read `value.module` if it exists, otherwise `null`. When
270
+ // `value.artifact` is `true`, the module is an addon or asset that may be
271
+ // left unread, returning `null`, unless its contents are needed
229
272
  let source
230
273
 
231
274
  next = generator.next(source)
275
+ } else if (value.probe) {
276
+ // Test whether `value.probe` exists, returning `true`, `false`, or
277
+ // `undefined` if probing isn't supported
278
+ let exists
279
+
280
+ next = generator.next(exists)
281
+ } else if (value.resolution) {
282
+ // Transform `value.resolution`, e.g. canonicalize it with `realpath`, or
283
+ // pass it through unchanged
284
+ let resolution = value.resolution
285
+
286
+ next = generator.next(resolution)
232
287
  } else if (value.prefix) {
233
288
  // List the modules that have `value.prefix` as a prefix
234
289
  let modules
235
290
 
236
291
  next = generator.next(modules)
292
+ } else if (value.links) {
293
+ // Drive each import to completion before resuming; their resolutions are
294
+ // independent, so a concurrent driver may instead drive them in parallel
295
+ for (const link of value.links) drive(link)
296
+
297
+ next = generator.next()
298
+ } else if (value.children) {
299
+ // Defer the subgraph if requested, otherwise traverse it next
300
+ if (value.deferred) deferred.push(value.children)
301
+ else queue.push(value.children)
302
+
303
+ next = generator.next()
237
304
  } else {
238
- if (value.children) {
239
- queue.push(value.children)
240
- } else {
241
- const dependency = value.dependency
242
- }
305
+ const dependency = value.dependency
243
306
 
244
307
  next = generator.next()
245
308
  }
246
309
  }
247
310
  }
311
+
312
+ while (queue.length > 0 || deferred.length > 0) {
313
+ drive(queue.length > 0 ? queue.pop() : deferred.shift())
314
+ }
248
315
  ```
249
316
 
250
317
  Options are the same as `traverse()` for all functions.
@@ -258,7 +325,9 @@ Options are the same as `traverse()` for all functions.
258
325
 
259
326
  #### `const generator = traverse.preresolved(url, source, resolutions, artifacts, visited[, options])`
260
327
 
261
- #### `const generator = traverse.imports(parentURL, source, imports, artifacts, visited[, options])`
328
+ #### `const generator = traverse.imports(parentURL, source, imports, artifacts, lexer, visited[, options])`
329
+
330
+ #### `const generator = traverse.link(entry, specifier, condition, parentURL, imports, artifacts, visited[, options])`
262
331
 
263
332
  #### `const generator = traverse.addons(parentURL, artifacts, visited[, options])`
264
333
 
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; artifact: boolean }
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 {
@@ -120,12 +134,27 @@ declare namespace traverse {
120
134
  source: string | Buffer,
121
135
  imports: ImportsMap,
122
136
  artifacts: Artifacts,
137
+ lexer: {
138
+ imports: Import[]
139
+ exports: Export[]
140
+ },
123
141
  visited: Set<string>,
124
142
  opts?: TraverseOptions
125
143
  ): Traversal
126
144
 
127
- export function prebuilds(
128
- packageURL: URL,
145
+ export function link(
146
+ entry: Import,
147
+ specifier: string,
148
+ condition: string,
149
+ parentURL: URL,
150
+ imports: ImportsMap,
151
+ artifacts: Artifacts,
152
+ visited: Set<string>,
153
+ opts?: TraverseOptions
154
+ ): Traversal
155
+
156
+ export function addons(
157
+ parentURL: URL,
129
158
  artifacts: Artifacts,
130
159
  visited: Set<string>,
131
160
  opts?: TraverseOptions
@@ -133,7 +162,7 @@ declare namespace traverse {
133
162
 
134
163
  export function assets(
135
164
  patterns: ConditionalSpecifier,
136
- packageURL: URL,
165
+ parentURL: URL,
137
166
  artifacts: Artifacts,
138
167
  visited: Set<string>,
139
168
  opts?: TraverseOptions
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) {
157
- source = yield { module: url }
199
+ const exists = probed !== undefined ? probed : artifact ? yield { probe: url } : undefined
158
200
 
159
- if (source === null) {
201
+ if (exists === false) {
202
+ throw errors.MODULE_NOT_FOUND(`Cannot find module '${url.href}'`, url.href)
203
+ }
204
+
205
+ source = yield { module: url, artifact }
206
+
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,14 +215,12 @@ 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
175
221
 
176
222
  for (const packageURL of lookupPackageScope(url, opts)) {
177
- const source = yield { module: packageURL }
223
+ const source = yield { module: packageURL, artifact: false }
178
224
 
179
225
  if (source !== null) {
180
226
  info = JSON.parse(source)
@@ -193,7 +239,7 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
193
239
  if (typeof attributes.imports === 'string') {
194
240
  const url = new URL(attributes.imports)
195
241
 
196
- const source = yield { module: url }
242
+ const source = yield { module: url, artifact: false }
197
243
 
198
244
  if (source !== null) {
199
245
  opts = {
@@ -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)
@@ -238,7 +280,7 @@ exports.package = function* (url, source, artifacts, visited, opts = {}) {
238
280
  visited.add(url.href)
239
281
 
240
282
  if (source === null) {
241
- source = yield { module: url }
283
+ source = yield { module: url, artifact: false }
242
284
 
243
285
  if (source === null) return false
244
286
  }
@@ -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,16 @@ 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
+ yield {
343
+ children: exports.module(url, null, {}, artifacts, visited, {
344
+ ...opts,
345
+ asset: true
346
+ }),
347
+ deferred: true
348
+ }
297
349
  } else if (
298
350
  url.protocol !== builtinProtocol &&
299
351
  url.protocol !== linkedProtocol &&
@@ -305,14 +357,13 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
305
357
  }
306
358
  }
307
359
  } else {
308
- stack.unshift(...Object.values(entry))
360
+ for (const [condition, child] of Object.entries(entry)) {
361
+ stack.push({ entry: child, asset: asset || condition === 'asset' })
362
+ }
309
363
  }
310
364
  }
311
365
  }
312
366
 
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
367
  const type = moduleType(url, {}, null, opts)
317
368
 
318
369
  const lexer = { imports: [], exports: [] }
@@ -335,22 +386,12 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
335
386
  }
336
387
 
337
388
  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
389
  const lexed = lex(source)
351
390
 
352
391
  lexer.exports = lexed.exports
353
392
 
393
+ const links = []
394
+
354
395
  for (const entry of lexed.imports) {
355
396
  let specifier = entry.specifier
356
397
  let condition = 'default'
@@ -366,129 +407,184 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
366
407
  condition = 'import'
367
408
  }
368
409
 
369
- if (entry.attributes.imports) {
370
- const specifier = entry.attributes.imports
371
-
372
- queue.push({
373
- entry: {
374
- type: 0,
375
- specifier,
376
- names: [],
377
- attributes: {},
378
- position: [0, 0, 0]
379
- },
380
- specifier,
381
- condition: 'default'
382
- })
383
- }
384
-
385
410
  lexer.imports.push(entry)
386
411
 
387
- queue.push({ entry, specifier, condition })
412
+ links.push(
413
+ exports.link(entry, specifier, condition, parentURL, imports, artifacts, visited, opts)
414
+ )
388
415
  }
389
416
 
390
- while (queue.length > 0) {
391
- const { entry, specifier, condition } = queue.shift()
417
+ yield { links }
418
+ }
419
+
420
+ exports.link = function* (
421
+ entry,
422
+ specifier,
423
+ condition,
424
+ parentURL,
425
+ imports,
426
+ artifacts,
427
+ visited,
428
+ opts = {}
429
+ ) {
430
+ if (entry.attributes.imports) {
431
+ const specifier = entry.attributes.imports
432
+
433
+ yield* resolveImport(
434
+ { type: 0, specifier, names: [], attributes: {}, position: [0, 0, 0] },
435
+ specifier,
436
+ 'default',
437
+ parentURL,
438
+ imports,
439
+ artifacts,
440
+ visited,
441
+ opts
442
+ )
443
+ }
444
+
445
+ yield* resolveImport(entry, specifier, condition, parentURL, imports, artifacts, visited, opts)
446
+ }
447
+
448
+ function* resolveImport(entry, specifier, condition, parentURL, imports, artifacts, visited, opts) {
449
+ const {
450
+ resolve = exports.resolve.default,
451
+ builtinProtocol = 'builtin:',
452
+ linkedProtocol = 'linked:',
453
+ deferredProtocol = 'deferred:'
454
+ } = opts
392
455
 
393
- matchedConditions.push(condition)
456
+ const matchedConditions = []
394
457
 
395
- const resolver = resolve(entry, parentURL, { ...opts, matchedConditions })
396
- const candidates = []
458
+ opts = { ...opts, matchedConditions }
397
459
 
398
- let next = resolver.next()
399
- let resolutions = 0
460
+ matchedConditions.push(condition)
400
461
 
401
- while (next.done !== true) {
402
- const value = next.value
462
+ const resolver = resolve(entry, parentURL, opts)
463
+ const candidates = []
403
464
 
404
- if (value.package) {
405
- next = resolver.next(JSON.parse(yield { module: value.package }))
406
- } else {
407
- const url = value.resolution
465
+ let next = resolver.next()
466
+ let resolutions = 0
408
467
 
409
- candidates.push(url)
468
+ while (next.done !== true) {
469
+ const value = next.value
410
470
 
411
- let resolved = false
471
+ if (value.package) {
472
+ next = resolver.next(JSON.parse(yield { module: value.package, artifact: false }))
473
+ } else {
474
+ const url = value.resolution
412
475
 
413
- if (
414
- url.protocol === builtinProtocol ||
415
- url.protocol === linkedProtocol ||
416
- url.protocol === deferredProtocol
417
- ) {
418
- addResolution(imports, specifier, matchedConditions, url)
419
-
420
- resolved = yielded = true
421
- } else if (condition === 'asset') {
422
- const prefix = url
423
-
424
- for (const url of yield { prefix }) {
425
- yield {
426
- children: exports.module(url, null, {}, artifacts, visited, {
427
- ...opts,
428
- asset: true
429
- }),
430
- deferred: true
431
- }
476
+ candidates.push(url)
477
+
478
+ let resolved = false
479
+ let resolution = url
480
+
481
+ if (
482
+ url.protocol === builtinProtocol ||
483
+ url.protocol === linkedProtocol ||
484
+ url.protocol === deferredProtocol
485
+ ) {
486
+ addResolution(imports, specifier, matchedConditions, url)
432
487
 
433
- addURL(artifacts.assets, url)
488
+ resolved = true
489
+ } else if (condition === 'asset') {
490
+ const prefix = url
434
491
 
435
- resolved = yielded = true
492
+ for (const url of yield { prefix }) {
493
+ const resolution = yield* postresolve(url)
494
+
495
+ yield {
496
+ children: exports.module(resolution, null, {}, artifacts, visited, {
497
+ ...opts,
498
+ asset: true
499
+ }),
500
+ deferred: true
436
501
  }
437
502
 
438
- if (resolved) addResolution(imports, specifier, matchedConditions, url)
439
- } else {
440
- const source = yield { module: url }
503
+ addURL(artifacts.assets, resolution)
441
504
 
442
- if (source !== null) {
443
- addResolution(imports, specifier, matchedConditions, exports.alias(url, opts))
505
+ resolved = true
506
+ }
444
507
 
445
- let attributes = entry.attributes
508
+ if (resolved) addResolution(imports, specifier, matchedConditions, url)
509
+ } else if (
510
+ condition === 'addon' ||
511
+ moduleType(url, entry.attributes, null, opts) === constants.ADDON
512
+ ) {
513
+ let exists = yield { probe: url }
514
+ let source = null
446
515
 
447
- if (attributes.imports) {
448
- attributes = { ...attributes, imports: imports[attributes.imports].default }
449
- }
516
+ if (exists === undefined) {
517
+ source = yield { module: url, artifact: false }
518
+ exists = source !== null
519
+ }
450
520
 
451
- yield {
452
- children: exports.module(url, source, attributes, artifacts, visited, opts),
453
- deferred: false
454
- }
521
+ if (exists) {
522
+ resolution = yield* postresolve(url)
455
523
 
456
- resolved = yielded = true
524
+ addResolution(imports, specifier, matchedConditions, exports.alias(resolution, opts))
525
+
526
+ yield {
527
+ children: exports.module(resolution, source, {}, artifacts, visited, {
528
+ ...opts,
529
+ probed: true
530
+ }),
531
+ deferred: false
457
532
  }
533
+
534
+ resolved = true
458
535
  }
536
+ } else {
537
+ const source = yield { module: url, artifact: false }
459
538
 
460
- if (resolved) {
461
- if (condition === 'addon') addURL(artifacts.addons, url)
539
+ if (source !== null) {
540
+ resolution = yield* postresolve(url)
462
541
 
463
- resolutions++
464
- }
542
+ addResolution(imports, specifier, matchedConditions, exports.alias(resolution, opts))
465
543
 
466
- next = resolver.next(resolved)
467
- }
468
- }
544
+ let attributes = entry.attributes
469
545
 
470
- matchedConditions.pop()
546
+ if (attributes.imports) {
547
+ attributes = { ...attributes, imports: imports[attributes.imports].default }
548
+ }
471
549
 
472
- if (resolutions === 0) {
473
- let message = `Cannot find ${condition === 'addon' || condition === 'asset' ? condition : 'module'} '${specifier}' imported from '${parentURL.href}'`
550
+ yield {
551
+ children: exports.module(resolution, source, attributes, artifacts, visited, opts),
552
+ deferred: false
553
+ }
474
554
 
475
- if (candidates.length > 0) {
476
- message += '\nCandidates:'
477
- message += '\n' + candidates.map((url) => '- ' + url.href).join('\n')
555
+ resolved = true
556
+ }
478
557
  }
479
558
 
480
- switch (condition) {
481
- case 'addon':
482
- throw errors.ADDON_NOT_FOUND(message, specifier, parentURL, candidates)
483
- case 'asset':
484
- throw errors.ASSET_NOT_FOUND(message, specifier, parentURL, candidates)
485
- default:
486
- throw errors.MODULE_NOT_FOUND(message, specifier, parentURL, candidates)
559
+ if (resolved) {
560
+ if (condition === 'addon') addURL(artifacts.addons, resolution)
561
+
562
+ resolutions++
487
563
  }
564
+
565
+ next = resolver.next(resolved)
488
566
  }
489
567
  }
490
568
 
491
- return yielded
569
+ matchedConditions.pop()
570
+
571
+ if (resolutions === 0) {
572
+ let message = `Cannot find ${condition === 'addon' || condition === 'asset' ? condition : 'module'} '${specifier}' imported from '${parentURL.href}'`
573
+
574
+ if (candidates.length > 0) {
575
+ message += '\nCandidates:'
576
+ message += '\n' + candidates.map((url) => '- ' + url.href).join('\n')
577
+ }
578
+
579
+ switch (condition) {
580
+ case 'addon':
581
+ throw errors.ADDON_NOT_FOUND(message, specifier, parentURL, candidates)
582
+ case 'asset':
583
+ throw errors.ASSET_NOT_FOUND(message, specifier, parentURL, candidates)
584
+ default:
585
+ throw errors.MODULE_NOT_FOUND(message, specifier, parentURL, candidates)
586
+ }
587
+ }
492
588
  }
493
589
 
494
590
  const ADDON_EXTENSION = /\.(bare|node)$/
@@ -502,12 +598,14 @@ exports.addons = function* (parentURL, artifacts, visited, opts = {}) {
502
598
  prefix.pathname = prefix.pathname.replace(ADDON_EXTENSION, '') + '/'
503
599
 
504
600
  for (const url of yield { prefix }) {
601
+ const resolution = yield* postresolve(url)
602
+
505
603
  yield {
506
- children: exports.module(url, null, {}, artifacts, visited, opts),
604
+ children: exports.module(resolution, null, {}, artifacts, visited, opts),
507
605
  deferred: false
508
606
  }
509
607
 
510
- addURL(artifacts.addons, url)
608
+ addURL(artifacts.addons, resolution)
511
609
 
512
610
  yielded = true
513
611
  }
@@ -522,21 +620,19 @@ exports.assets = function* (patterns, parentURL, artifacts, visited, opts = {})
522
620
  let yielded = false
523
621
 
524
622
  for (const url of matches) {
525
- const source = yield { module: url }
623
+ const resolution = yield* postresolve(url)
526
624
 
527
- if (source !== null) {
528
- addURL(artifacts.assets, url)
529
-
530
- yield {
531
- children: exports.module(url, source, {}, artifacts, visited, {
532
- ...opts,
533
- asset: true
534
- }),
535
- deferred: true
536
- }
625
+ addURL(artifacts.assets, resolution)
537
626
 
538
- yielded = true
627
+ yield {
628
+ children: exports.module(resolution, null, {}, artifacts, visited, {
629
+ ...opts,
630
+ asset: true
631
+ }),
632
+ deferred: true
539
633
  }
634
+
635
+ yielded = true
540
636
  }
541
637
 
542
638
  return yielded
@@ -605,10 +701,10 @@ exports.patternMatches = function* patternMatches(pattern, parentURL, matches, o
605
701
  return matches
606
702
  }
607
703
 
608
- // Determine the module type from an explicit type attribute, if given, and
609
- // otherwise from the module's extension. For the ambiguous `.js` and `.ts`
610
- // extensions the nearest `package.json`, passed as `info`, and the default type
611
- // break the tie. Returns `0` when the type cannot be determined.
704
+ function* postresolve(url) {
705
+ return (yield { resolution: url }) || url
706
+ }
707
+
612
708
  function moduleType(url, attributes, info, opts = {}) {
613
709
  const { defaultType = constants.SCRIPT, aliases = null } = opts
614
710
 
@@ -635,7 +731,7 @@ function moduleType(url, attributes, info, opts = {}) {
635
731
 
636
732
  const match = url.pathname.match(/\.[a-z]+$/)
637
733
 
638
- if (match === null) return 0
734
+ if (match === null) return defaultType
639
735
 
640
736
  let [extension] = match
641
737
 
@@ -666,7 +762,7 @@ function moduleType(url, attributes, info, opts = {}) {
666
762
  return constants.TEXT
667
763
  }
668
764
 
669
- return 0
765
+ return defaultType
670
766
  }
671
767
 
672
768
  function addURL(collection, url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-module-traverse",
3
- "version": "2.3.1",
3
+ "version": "2.4.1",
4
4
  "description": "Low-level module graph traversal for Bare",
5
5
  "exports": {
6
6
  ".": {