bare-module-traverse 2.1.2 → 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.
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
 
@@ -21,6 +23,9 @@ type AliasableExtension =
21
23
  | '.js'
22
24
  | '.cjs'
23
25
  | '.mjs'
26
+ | '.ts'
27
+ | '.cts'
28
+ | '.mts'
24
29
  | '.json'
25
30
  | '.bundle'
26
31
  | '.bare'
@@ -64,7 +69,7 @@ declare namespace traverse {
64
69
  export { type TraverseOptions }
65
70
 
66
71
  export type Traversal = Generator<
67
- { module: URL } | { prefix: URL } | { children: URL } | { dependency: URL },
72
+ { module: URL } | { prefix: URL } | { children: URL; deferred: boolean } | { dependency: URL },
68
73
  boolean,
69
74
  void | URL[] | Buffer | string | null
70
75
  >
@@ -82,7 +87,6 @@ declare namespace traverse {
82
87
  ADDON: number
83
88
  BINARY: number
84
89
  TEXT: number
85
- ASSET: number
86
90
  }
87
91
 
88
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,86 +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
- const isESM =
225
- defaultType === constants.MODULE || (info !== null && info.type === 'module')
208
+ const lexer = { imports: [], exports: [] }
226
209
 
227
- type = isESM ? constants.MODULE : constants.SCRIPT
228
- break
229
- }
230
- case '.cjs':
231
- type = constants.SCRIPT
232
- break
233
- case '.mjs':
234
- type = constants.MODULE
235
- break
236
- case '.json':
237
- type = constants.JSON
238
- break
239
- case '.bundle':
240
- type = constants.BUNDLE
241
- break
242
- case '.bare':
243
- case '.node':
244
- type = constants.ADDON
245
- break
246
- case '.bin':
247
- type = constants.BINARY
248
- break
249
- case '.txt':
250
- type = constants.TEXT
251
- break
252
- }
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)
253
219
  }
254
220
  }
255
221
 
256
- const lexer = { imports: [] }
257
-
258
- if (type === constants.SCRIPT || type === constants.MODULE) {
259
- yield* exports.imports(url, source, imports, artifacts, lexer, visited, opts)
260
- } else if (type === constants.ADDON) {
261
- yield* exports.addons(url, artifacts, visited, opts)
262
- }
263
-
264
222
  yield {
265
223
  dependency: {
266
224
  url: exports.alias(url, opts),
267
225
  source,
226
+ type,
268
227
  imports: compressImportsMap(imports),
269
228
  lexer
270
229
  }
@@ -276,22 +235,31 @@ exports.module = function* (url, source, attributes, artifacts, visited, opts =
276
235
  exports.package = function* (url, source, artifacts, visited, opts = {}) {
277
236
  if (visited.has(url.href)) return false
278
237
 
238
+ visited.add(url.href)
239
+
279
240
  if (source === null) {
280
241
  source = yield { module: url }
281
242
 
282
243
  if (source === null) return false
283
244
  }
284
245
 
285
- visited.add(url.href)
286
-
287
246
  const info = JSON.parse(source)
288
247
 
289
248
  if (info) {
290
- 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
+ }
291
258
 
292
259
  if (info.assets) {
293
260
  yield {
294
- children: exports.assets(info.assets, url, artifacts, visited, opts)
261
+ children: exports.assets(info.assets, url, artifacts, visited, opts),
262
+ deferred: false
295
263
  }
296
264
  }
297
265
 
@@ -323,7 +291,8 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
323
291
 
324
292
  if (specifier === '#package') {
325
293
  yield {
326
- children: exports.package(url, null, artifacts, visited, opts)
294
+ children: exports.package(url, null, artifacts, visited, opts),
295
+ deferred: false
327
296
  }
328
297
  } else if (
329
298
  url.protocol !== builtinProtocol &&
@@ -331,7 +300,8 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
331
300
  url.protocol !== deferredProtocol
332
301
  ) {
333
302
  yield {
334
- children: exports.module(url, null, {}, artifacts, visited, opts)
303
+ children: exports.module(url, null, {}, artifacts, visited, opts),
304
+ deferred: false
335
305
  }
336
306
  }
337
307
  } else {
@@ -340,12 +310,24 @@ exports.preresolved = function* (url, source, resolutions, artifacts, visited, o
340
310
  }
341
311
  }
342
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
+
343
324
  yield {
344
325
  dependency: {
345
326
  url: exports.alias(url, opts),
346
327
  source,
328
+ type,
347
329
  imports: compressImportsMap(imports),
348
- lexer: { imports: [] }
330
+ lexer
349
331
  }
350
332
  }
351
333
 
@@ -365,7 +347,11 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
365
347
 
366
348
  const queue = []
367
349
 
368
- 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) {
369
355
  let specifier = entry.specifier
370
356
  let condition = 'default'
371
357
 
@@ -433,7 +419,11 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
433
419
 
434
420
  for (const url of yield { prefix }) {
435
421
  yield {
436
- 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
437
427
  }
438
428
 
439
429
  addURL(artifacts.assets, url)
@@ -455,7 +445,8 @@ exports.imports = function* (parentURL, source, imports, artifacts, lexer, visit
455
445
  }
456
446
 
457
447
  yield {
458
- children: exports.module(url, source, attributes, artifacts, visited, opts)
448
+ children: exports.module(url, source, attributes, artifacts, visited, opts),
449
+ deferred: false
459
450
  }
460
451
 
461
452
  resolved = yielded = true
@@ -508,7 +499,8 @@ exports.addons = function* (parentURL, artifacts, visited, opts = {}) {
508
499
 
509
500
  for (const url of yield { prefix }) {
510
501
  yield {
511
- children: exports.module(url, null, {}, artifacts, visited, opts)
502
+ children: exports.module(url, null, {}, artifacts, visited, opts),
503
+ deferred: false
512
504
  }
513
505
 
514
506
  addURL(artifacts.addons, url)
@@ -532,7 +524,11 @@ exports.assets = function* (patterns, parentURL, artifacts, visited, opts = {})
532
524
  addURL(artifacts.assets, url)
533
525
 
534
526
  yield {
535
- 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
536
532
  }
537
533
 
538
534
  yielded = true
@@ -605,6 +601,70 @@ exports.patternMatches = function* patternMatches(pattern, parentURL, matches, o
605
601
  return matches
606
602
  }
607
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
+
608
668
  function addURL(collection, url) {
609
669
  if (Array.isArray(collection)) {
610
670
  let lo = 0
@@ -24,7 +24,7 @@ module.exports = function (entry, parentURL, opts = {}) {
24
24
  if (entry.type & lex.constants.ASSET) {
25
25
  conditions = conditions.map((conditions) => ['asset', ...conditions])
26
26
  } else {
27
- extensions = ['.js', '.cjs', '.mjs', '.json', '.bare', '.node']
27
+ extensions = ['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.json', '.bare', '.node']
28
28
 
29
29
  if (entry.type & lex.constants.REQUIRE) {
30
30
  conditions = conditions.map((conditions) => ['require', ...conditions])
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-module-traverse",
3
- "version": "2.1.2",
3
+ "version": "2.3.0",
4
4
  "description": "Low-level module graph traversal for Bare",
5
5
  "exports": {
6
6
  ".": {
@@ -55,7 +55,7 @@
55
55
  "homepage": "https://github.com/holepunchto/bare-module-traverse#readme",
56
56
  "dependencies": {
57
57
  "bare-addon-resolve": "^1.5.0",
58
- "bare-module-lexer": "^1.4.0",
58
+ "bare-module-lexer": "^1.6.0",
59
59
  "bare-module-resolve": "^1.7.0"
60
60
  },
61
61
  "devDependencies": {