bare-module-traverse 2.2.0 → 2.3.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 +28 -2
  2. package/index.d.ts +4 -3
  3. package/index.js +159 -102
  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)]
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
+ const deferred = []
70
85
 
71
- while (queue.length > 0) {
72
- const generator = queue.pop()
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
189
-
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]
206
+ const type = moduleType(url, attributes, info, opts)
221
207
 
222
- switch (extension) {
223
- case '.js':
224
- case '.ts': {
225
- const isESM =
226
- defaultType === constants.MODULE || (info !== null && info.type === 'module')
208
+ const lexer = { imports: [], exports: [] }
227
209
 
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
 
@@ -436,7 +419,11 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
436
419
 
437
420
  for (const url of yield { prefix }) {
438
421
  yield {
439
- children: exports.module(url, null, {}, artifacts, visited, opts)
422
+ children: exports.module(url, null, {}, artifacts, visited, {
423
+ ...opts,
424
+ asset: true
425
+ }),
426
+ deferred: true
440
427
  }
441
428
 
442
429
  addURL(artifacts.assets, url)
@@ -458,7 +445,8 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
458
445
  }
459
446
 
460
447
  yield {
461
- children: exports.module(url, source, attributes, artifacts, visited, opts)
448
+ children: exports.module(url, source, attributes, artifacts, visited, opts),
449
+ deferred: false
462
450
  }
463
451
 
464
452
  resolved = yielded = true
@@ -511,7 +499,8 @@ exports.addons = function* (parentURL, artifacts, visited, opts = {}) {
511
499
 
512
500
  for (const url of yield { prefix }) {
513
501
  yield {
514
- children: exports.module(url, null, {}, artifacts, visited, opts)
502
+ children: exports.module(url, null, {}, artifacts, visited, opts),
503
+ deferred: false
515
504
  }
516
505
 
517
506
  addURL(artifacts.addons, url)
@@ -535,7 +524,11 @@ exports.assets = function* (patterns, parentURL, artifacts, visited, opts = {})
535
524
  addURL(artifacts.assets, url)
536
525
 
537
526
  yield {
538
- children: exports.module(url, source, {}, artifacts, visited, opts)
527
+ children: exports.module(url, source, {}, artifacts, visited, {
528
+ ...opts,
529
+ asset: true
530
+ }),
531
+ deferred: true
539
532
  }
540
533
 
541
534
  yielded = true
@@ -608,6 +601,70 @@ exports.patternMatches = function* patternMatches(pattern, parentURL, matches, o
608
601
  return matches
609
602
  }
610
603
 
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.
608
+ function moduleType(url, attributes, info, opts = {}) {
609
+ const { defaultType = constants.SCRIPT, aliases = null } = opts
610
+
611
+ if (typeof attributes.type === 'string') {
612
+ switch (attributes.type) {
613
+ case 'script':
614
+ return constants.SCRIPT
615
+ case 'module':
616
+ return constants.MODULE
617
+ case 'json':
618
+ return constants.JSON
619
+ case 'bundle':
620
+ return constants.BUNDLE
621
+ case 'addon':
622
+ return constants.ADDON
623
+ case 'binary':
624
+ return constants.BINARY
625
+ case 'text':
626
+ return constants.TEXT
627
+ }
628
+
629
+ return 0
630
+ }
631
+
632
+ const match = url.pathname.match(/\.[a-z]+$/)
633
+
634
+ if (match === null) return 0
635
+
636
+ let [extension] = match
637
+
638
+ if (aliases !== null && extension in aliases) extension = aliases[extension]
639
+
640
+ switch (extension) {
641
+ case '.js':
642
+ case '.ts':
643
+ return defaultType === constants.MODULE || (info !== null && info.type === 'module')
644
+ ? constants.MODULE
645
+ : constants.SCRIPT
646
+ case '.cjs':
647
+ case '.cts':
648
+ return constants.SCRIPT
649
+ case '.mjs':
650
+ case '.mts':
651
+ return constants.MODULE
652
+ case '.json':
653
+ return constants.JSON
654
+ case '.bundle':
655
+ return constants.BUNDLE
656
+ case '.bare':
657
+ case '.node':
658
+ return constants.ADDON
659
+ case '.bin':
660
+ return constants.BINARY
661
+ case '.txt':
662
+ return constants.TEXT
663
+ }
664
+
665
+ return 0
666
+ }
667
+
611
668
  function addURL(collection, url) {
612
669
  if (Array.isArray(collection)) {
613
670
  let lo = 0
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.0",
4
4
  "description": "Low-level module graph traversal for Bare",
5
5
  "exports": {
6
6
  ".": {