bare-module-traverse 2.2.0 → 2.3.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 +28 -2
  2. package/index.d.ts +4 -3
  3. package/index.js +170 -103
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -67,10 +67,13 @@ options = {
67
67
  // rewritten to match, so `'.ts': '.js'` yields a `file:///foo.js`
68
68
  // dependency rather than `file:///foo.ts`.
69
69
  },
70
- resolve: resolve.default
70
+ resolve: resolve.default,
71
+ visited: new Set()
71
72
  }
72
73
  ```
73
74
 
75
+ `visited` is a `Set` of already visited module hrefs. If provided, modules whose href is already in the set are skipped and the set is updated in place as traversal proceeds. This allows a single set to be shared across several traversals so that a later traversal, such as one rooted at a dynamically imported module, does not revisit modules already seen by an earlier one.
76
+
74
77
  Options supported by <https://github.com/holepunchto/bare-module-resolve> and <https://github.com/holepunchto/bare-addon-resolve> may also be specified.
75
78
 
76
79
  #### `for (const dependency of dependencies)`
@@ -81,12 +84,16 @@ Synchronously iterate the module graph. Each yielded dependency has the followin
81
84
  dependency = {
82
85
  url: URL,
83
86
  source: 'string' | Buffer, // Source as returned by `readModule()`
87
+ type: constants.SCRIPT, // The detected module type, or `0` if unknown
84
88
  imports: {
85
89
  // See https://github.com/holepunchto/bare-module#imports
86
90
  },
87
91
  lexer: {
88
92
  imports: [
89
93
  // See https://github.com/holepunchto/bare-module-lexer#api
94
+ ],
95
+ exports: [
96
+ // See https://github.com/holepunchto/bare-module-lexer#api
90
97
  ]
91
98
  }
92
99
  }
@@ -96,6 +103,20 @@ dependency = {
96
103
 
97
104
  Asynchronously iterate the module graph. If `readModule` returns a promise or `listPrefix` returns a promise generator, these will be awaited. The same comments as `for (const dependency of dependencies)` apply.
98
105
 
106
+ #### `constants`
107
+
108
+ The module type constants used by the `type` field of each dependency and accepted as the `defaultType` option.
109
+
110
+ | Constant | Description |
111
+ | :------- | :----------------- |
112
+ | `SCRIPT` | A CommonJS module. |
113
+ | `MODULE` | An ES module. |
114
+ | `JSON` | A JSON module. |
115
+ | `BUNDLE` | A bundle module. |
116
+ | `ADDON` | A native addon. |
117
+ | `BINARY` | A binary module. |
118
+ | `TEXT` | A text module. |
119
+
99
120
  ### Resolution
100
121
 
101
122
  Module and addon resolution is configurable by providing a resolver function. A resolver function is a generator function that yields values matching the shapes defined by <https://github.com/holepunchto/bare-module-resolve#algorithm>. Several resolvers are provided out of the box to support the most common use cases.
@@ -162,7 +183,8 @@ next.value = {
162
183
 
163
184
  ```js
164
185
  next.value = {
165
- children: Generator
186
+ children: Generator,
187
+ deferred: boolean
166
188
  }
167
189
  ```
168
190
 
@@ -173,12 +195,16 @@ next.value = {
173
195
  dependency: {
174
196
  url: URL,
175
197
  source: 'string' | Buffer,
198
+ type: constants.SCRIPT,
176
199
  imports: {
177
200
  // See https://github.com/holepunchto/bare-module#imports
178
201
  },
179
202
  lexer: {
180
203
  imports: [
181
204
  // See https://github.com/holepunchto/bare-module-lexer#api
205
+ ],
206
+ exports: [
207
+ // See https://github.com/holepunchto/bare-module-lexer#api
182
208
  ]
183
209
  }
184
210
  }
package/index.d.ts CHANGED
@@ -6,14 +6,16 @@ import {
6
6
  type ResolutionsMap
7
7
  } from 'bare-module-resolve'
8
8
  import { type ResolveOptions, type Resolver } from 'bare-addon-resolve'
9
- import { type Import } from 'bare-module-lexer'
9
+ import { type Import, type Export } from 'bare-module-lexer'
10
10
 
11
11
  interface Dependency {
12
12
  url: URL
13
13
  source: string | Buffer
14
+ type: number
14
15
  imports: ImportsMap
15
16
  lexer: {
16
17
  imports: Import[]
18
+ exports: Export[]
17
19
  }
18
20
  }
19
21
 
@@ -67,7 +69,7 @@ declare namespace traverse {
67
69
  export { type TraverseOptions }
68
70
 
69
71
  export type Traversal = Generator<
70
- { module: URL } | { prefix: URL } | { children: URL } | { dependency: URL },
72
+ { module: URL } | { prefix: URL } | { children: URL; deferred: boolean } | { dependency: URL },
71
73
  boolean,
72
74
  void | URL[] | Buffer | string | null
73
75
  >
@@ -85,7 +87,6 @@ declare namespace traverse {
85
87
  ADDON: number
86
88
  BINARY: number
87
89
  TEXT: number
88
- ASSET: number
89
90
  }
90
91
 
91
92
  export function module(
package/index.js CHANGED
@@ -10,8 +10,7 @@ const constants = {
10
10
  BUNDLE: 4,
11
11
  ADDON: 5,
12
12
  BINARY: 6,
13
- TEXT: 7,
14
- ASSET: 8
13
+ TEXT: 7
15
14
  }
16
15
 
17
16
  module.exports = exports = function traverse(entry, opts, readModule, listPrefix) {
@@ -25,10 +24,17 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
25
24
  *[Symbol.iterator]() {
26
25
  const artifacts = { addons: [], assets: [] }
27
26
 
28
- const queue = [exports.module(entry, null, {}, artifacts, new Set(), opts)]
27
+ const visited = opts.visited || new Set()
29
28
 
30
- while (queue.length > 0) {
31
- const generator = queue.pop()
29
+ 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
+ const deferred = []
35
+
36
+ while (queue.length > 0 || deferred.length > 0) {
37
+ const generator = queue.length > 0 ? queue.pop() : deferred.shift()
32
38
 
33
39
  let next = generator.next()
34
40
 
@@ -52,8 +58,10 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
52
58
 
53
59
  next = generator.next(result)
54
60
  } else {
55
- if (value.children) queue.push(value.children)
56
- else yield value.dependency
61
+ if (value.children) {
62
+ if (value.deferred) deferred.push(value.children)
63
+ else queue.push(value.children)
64
+ } else yield value.dependency
57
65
 
58
66
  next = generator.next()
59
67
  }
@@ -66,10 +74,17 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
66
74
  async *[Symbol.asyncIterator]() {
67
75
  const artifacts = { addons: [], assets: [] }
68
76
 
69
- const queue = [exports.module(entry, null, {}, artifacts, new Set(), opts)]
77
+ const visited = opts.visited || new Set()
78
+
79
+ const queue = [exports.module(entry, null, {}, artifacts, visited, opts)]
70
80
 
71
- while (queue.length > 0) {
72
- const generator = queue.pop()
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
+ const deferred = []
85
+
86
+ while (queue.length > 0 || deferred.length > 0) {
87
+ const generator = queue.length > 0 ? queue.pop() : deferred.shift()
73
88
 
74
89
  let next = generator.next()
75
90
 
@@ -93,8 +108,10 @@ module.exports = exports = function traverse(entry, opts, readModule, listPrefix
93
108
 
94
109
  next = generator.next(result)
95
110
  } else {
96
- if (value.children) queue.push(value.children)
97
- else yield value.dependency
111
+ if (value.children) {
112
+ if (value.deferred) deferred.push(value.children)
113
+ else queue.push(value.children)
114
+ } else yield value.dependency
98
115
 
99
116
  next = generator.next()
100
117
  }
@@ -130,10 +147,12 @@ exports.alias = function alias(url, opts = {}) {
130
147
  }
131
148
 
132
149
  exports.module = function* (url, source, attributes, artifacts, visited, opts = {}) {
133
- const { resolutions = null, defaultType = constants.SCRIPT, aliases = null } = opts
150
+ const { resolutions = null, asset = false } = opts
134
151
 
135
152
  if (visited.has(url.href)) return false
136
153
 
154
+ visited.add(url.href)
155
+
137
156
  if (source === null) {
138
157
  source = yield { module: url }
139
158
 
@@ -142,8 +161,6 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
142
161
  }
143
162
  }
144
163
 
145
- visited.add(url.href)
146
-
147
164
  if (resolutions) {
148
165
  if (yield* exports.preresolved(url, source, resolutions, artifacts, visited, opts)) {
149
166
  return true
@@ -165,7 +182,8 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
165
182
  imports['#package'] = packageURL.href
166
183
 
167
184
  yield {
168
- children: exports.package(packageURL, source, artifacts, visited, opts)
185
+ children: exports.package(packageURL, source, artifacts, visited, opts),
186
+ deferred: false
169
187
  }
170
188
 
171
189
  break
@@ -185,89 +203,27 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
185
203
  }
186
204
  }
187
205
 
188
- let type = 0
206
+ const type = moduleType(url, attributes, info, opts)
189
207
 
190
- if (typeof attributes.type === 'string') {
191
- switch (attributes.type) {
192
- case 'script':
193
- type = constants.SCRIPT
194
- break
195
- case 'module':
196
- type = constants.MODULE
197
- break
198
- case 'json':
199
- type = constants.JSON
200
- break
201
- case 'bundle':
202
- type = constants.BUNDLE
203
- break
204
- case 'addon':
205
- type = constants.ADDON
206
- break
207
- case 'binary':
208
- type = constants.BINARY
209
- break
210
- case 'text':
211
- type = constants.TEXT
212
- break
213
- }
214
- } else {
215
- const match = url.pathname.match(/\.[a-z]+$/)
216
-
217
- if (match !== null) {
218
- let [extension] = match
219
-
220
- if (aliases !== null && extension in aliases) extension = aliases[extension]
208
+ const lexer = { imports: [], exports: [] }
221
209
 
222
- switch (extension) {
223
- case '.js':
224
- case '.ts': {
225
- const isESM =
226
- defaultType === constants.MODULE || (info !== null && info.type === 'module')
227
-
228
- type = isESM ? constants.MODULE : constants.SCRIPT
229
- break
230
- }
231
- case '.cjs':
232
- case '.cts':
233
- type = constants.SCRIPT
234
- break
235
- case '.mjs':
236
- case '.mts':
237
- type = constants.MODULE
238
- break
239
- case '.json':
240
- type = constants.JSON
241
- break
242
- case '.bundle':
243
- type = constants.BUNDLE
244
- break
245
- case '.bare':
246
- case '.node':
247
- type = constants.ADDON
248
- break
249
- case '.bin':
250
- type = constants.BINARY
251
- break
252
- case '.txt':
253
- type = constants.TEXT
254
- break
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
+ if (asset === false) {
215
+ if (type === constants.SCRIPT || type === constants.MODULE) {
216
+ yield* exports.imports(url, source, imports, artifacts, lexer, visited, opts)
217
+ } else if (type === constants.ADDON) {
218
+ yield* exports.addons(url, artifacts, visited, opts)
256
219
  }
257
220
  }
258
221
 
259
- const lexer = { imports: [] }
260
-
261
- if (type === constants.SCRIPT || type === constants.MODULE) {
262
- yield* exports.imports(url, source, imports, artifacts, lexer, visited, opts)
263
- } else if (type === constants.ADDON) {
264
- yield* exports.addons(url, artifacts, visited, opts)
265
- }
266
-
267
222
  yield {
268
223
  dependency: {
269
224
  url: exports.alias(url, opts),
270
225
  source,
226
+ type,
271
227
  imports: compressImportsMap(imports),
272
228
  lexer
273
229
  }
@@ -279,22 +235,31 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
279
235
  exports.package = function* (url, source, artifacts, visited, opts = {}) {
280
236
  if (visited.has(url.href)) return false
281
237
 
238
+ visited.add(url.href)
239
+
282
240
  if (source === null) {
283
241
  source = yield { module: url }
284
242
 
285
243
  if (source === null) return false
286
244
  }
287
245
 
288
- visited.add(url.href)
289
-
290
246
  const info = JSON.parse(source)
291
247
 
292
248
  if (info) {
293
- yield { dependency: { url, source, imports: {}, lexer: { imports: [] } } }
249
+ yield {
250
+ dependency: {
251
+ url,
252
+ source,
253
+ type: constants.JSON,
254
+ imports: {},
255
+ lexer: { imports: [], exports: [] }
256
+ }
257
+ }
294
258
 
295
259
  if (info.assets) {
296
260
  yield {
297
- children: exports.assets(info.assets, url, artifacts, visited, opts)
261
+ children: exports.assets(info.assets, url, artifacts, visited, opts),
262
+ deferred: false
298
263
  }
299
264
  }
300
265
 
@@ -326,7 +291,8 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
326
291
 
327
292
  if (specifier === '#package') {
328
293
  yield {
329
- children: exports.package(url, null, artifacts, visited, opts)
294
+ children: exports.package(url, null, artifacts, visited, opts),
295
+ deferred: false
330
296
  }
331
297
  } else if (
332
298
  url.protocol !== builtinProtocol &&
@@ -334,7 +300,8 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
334
300
  url.protocol !== deferredProtocol
335
301
  ) {
336
302
  yield {
337
- children: exports.module(url, null, {}, artifacts, visited, opts)
303
+ children: exports.module(url, null, {}, artifacts, visited, opts),
304
+ deferred: false
338
305
  }
339
306
  }
340
307
  } else {
@@ -343,12 +310,24 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
343
310
  }
344
311
  }
345
312
 
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
+ const type = moduleType(url, {}, null, opts)
317
+
318
+ const lexer = { imports: [], exports: [] }
319
+
320
+ if (type === constants.SCRIPT || type === constants.MODULE) {
321
+ lexer.exports = lex(source).exports
322
+ }
323
+
346
324
  yield {
347
325
  dependency: {
348
326
  url: exports.alias(url, opts),
349
327
  source,
328
+ type,
350
329
  imports: compressImportsMap(imports),
351
- lexer: { imports: [] }
330
+ lexer
352
331
  }
353
332
  }
354
333
 
@@ -368,7 +347,11 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
368
347
 
369
348
  const queue = []
370
349
 
371
- for (const entry of lex(source).imports) {
350
+ const lexed = lex(source)
351
+
352
+ lexer.exports = lexed.exports
353
+
354
+ for (const entry of lexed.imports) {
372
355
  let specifier = entry.specifier
373
356
  let condition = 'default'
374
357
 
@@ -377,6 +360,10 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
377
360
  condition = 'addon'
378
361
  } else if (entry.type & lex.constants.ASSET) {
379
362
  condition = 'asset'
363
+ } else if (entry.type & lex.constants.REQUIRE) {
364
+ condition = 'require'
365
+ } else if (entry.type & lex.constants.IMPORT) {
366
+ condition = 'import'
380
367
  }
381
368
 
382
369
  if (entry.attributes.imports) {
@@ -436,7 +423,11 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
436
423
 
437
424
  for (const url of yield { prefix }) {
438
425
  yield {
439
- children: exports.module(url, null, {}, artifacts, visited, opts)
426
+ children: exports.module(url, null, {}, artifacts, visited, {
427
+ ...opts,
428
+ asset: true
429
+ }),
430
+ deferred: true
440
431
  }
441
432
 
442
433
  addURL(artifacts.assets, url)
@@ -458,7 +449,8 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
458
449
  }
459
450
 
460
451
  yield {
461
- children: exports.module(url, source, attributes, artifacts, visited, opts)
452
+ children: exports.module(url, source, attributes, artifacts, visited, opts),
453
+ deferred: false
462
454
  }
463
455
 
464
456
  resolved = yielded = true
@@ -478,7 +470,7 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
478
470
  matchedConditions.pop()
479
471
 
480
472
  if (resolutions === 0) {
481
- let message = `Cannot find ${condition === 'default' ? 'module' : condition} '${specifier}' imported from '${parentURL.href}'`
473
+ let message = `Cannot find ${condition === 'addon' || condition === 'asset' ? condition : 'module'} '${specifier}' imported from '${parentURL.href}'`
482
474
 
483
475
  if (candidates.length > 0) {
484
476
  message += '\nCandidates:'
@@ -511,7 +503,8 @@ exports.addons = function* (parentURL, artifacts, visited, opts = {}) {
511
503
 
512
504
  for (const url of yield { prefix }) {
513
505
  yield {
514
- children: exports.module(url, null, {}, artifacts, visited, opts)
506
+ children: exports.module(url, null, {}, artifacts, visited, opts),
507
+ deferred: false
515
508
  }
516
509
 
517
510
  addURL(artifacts.addons, url)
@@ -535,7 +528,11 @@ exports.assets = function* (patterns, parentURL, artifacts, visited, opts = {})
535
528
  addURL(artifacts.assets, url)
536
529
 
537
530
  yield {
538
- children: exports.module(url, source, {}, artifacts, visited, opts)
531
+ children: exports.module(url, source, {}, artifacts, visited, {
532
+ ...opts,
533
+ asset: true
534
+ }),
535
+ deferred: true
539
536
  }
540
537
 
541
538
  yielded = true
@@ -608,6 +605,70 @@ exports.patternMatches = function* patternMatches(pattern, parentURL, matches, o
608
605
  return matches
609
606
  }
610
607
 
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.
612
+ function moduleType(url, attributes, info, opts = {}) {
613
+ const { defaultType = constants.SCRIPT, aliases = null } = opts
614
+
615
+ if (typeof attributes.type === 'string') {
616
+ switch (attributes.type) {
617
+ case 'script':
618
+ return constants.SCRIPT
619
+ case 'module':
620
+ return constants.MODULE
621
+ case 'json':
622
+ return constants.JSON
623
+ case 'bundle':
624
+ return constants.BUNDLE
625
+ case 'addon':
626
+ return constants.ADDON
627
+ case 'binary':
628
+ return constants.BINARY
629
+ case 'text':
630
+ return constants.TEXT
631
+ }
632
+
633
+ return 0
634
+ }
635
+
636
+ const match = url.pathname.match(/\.[a-z]+$/)
637
+
638
+ if (match === null) return 0
639
+
640
+ let [extension] = match
641
+
642
+ if (aliases !== null && extension in aliases) extension = aliases[extension]
643
+
644
+ switch (extension) {
645
+ case '.js':
646
+ case '.ts':
647
+ return defaultType === constants.MODULE || (info !== null && info.type === 'module')
648
+ ? constants.MODULE
649
+ : constants.SCRIPT
650
+ case '.cjs':
651
+ case '.cts':
652
+ return constants.SCRIPT
653
+ case '.mjs':
654
+ case '.mts':
655
+ return constants.MODULE
656
+ case '.json':
657
+ return constants.JSON
658
+ case '.bundle':
659
+ return constants.BUNDLE
660
+ case '.bare':
661
+ case '.node':
662
+ return constants.ADDON
663
+ case '.bin':
664
+ return constants.BINARY
665
+ case '.txt':
666
+ return constants.TEXT
667
+ }
668
+
669
+ return 0
670
+ }
671
+
611
672
  function addURL(collection, url) {
612
673
  if (Array.isArray(collection)) {
613
674
  let lo = 0
@@ -708,6 +769,12 @@ function compressImportsMapEntry(resolved) {
708
769
  if (entry[0] === 'default') primary = entry[1]
709
770
  }
710
771
 
772
+ if (entries.length === 0) return resolved
773
+
774
+ const [, first] = entries[0]
775
+
776
+ if (entries.every(([, resolved]) => resolved === first)) return first
777
+
711
778
  entries = entries.filter(
712
779
  ([condition, resolved]) => condition === 'default' || resolved !== primary
713
780
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-module-traverse",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "Low-level module graph traversal for Bare",
5
5
  "exports": {
6
6
  ".": {