index.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. 'use strict'
  2. // walk the tree of deps starting from the top level list of bundled deps
  3. // Any deps at the top level that are depended on by a bundled dep that
  4. // does not have that dep in its own node_modules folder are considered
  5. // bundled deps as well. This list of names can be passed to npm-packlist
  6. // as the "bundled" argument. Additionally, packageJsonCache is shared so
  7. // packlist doesn't have to re-read files already consumed in this pass
  8. const fs = require('fs')
  9. const path = require('path')
  10. const EE = require('events').EventEmitter
  11. // we don't care about the package bins, but we share a pj cache
  12. // with other modules that DO care about it, so keep it nice.
  13. const normalizePackageBin = require('npm-normalize-package-bin')
  14. class BundleWalker extends EE {
  15. constructor (opt) {
  16. opt = opt || {}
  17. super(opt)
  18. this.path = path.resolve(opt.path || process.cwd())
  19. this.parent = opt.parent || null
  20. if (this.parent) {
  21. this.result = this.parent.result
  22. // only collect results in node_modules folders at the top level
  23. // since the node_modules in a bundled dep is included always
  24. if (!this.parent.parent) {
  25. const base = path.basename(this.path)
  26. const scope = path.basename(path.dirname(this.path))
  27. this.result.add(/^@/.test(scope) ? scope + '/' + base : base)
  28. }
  29. this.root = this.parent.root
  30. this.packageJsonCache = this.parent.packageJsonCache
  31. } else {
  32. this.result = new Set()
  33. this.root = this.path
  34. this.packageJsonCache = opt.packageJsonCache || new Map()
  35. }
  36. this.seen = new Set()
  37. this.didDone = false
  38. this.children = 0
  39. this.node_modules = []
  40. this.package = null
  41. this.bundle = null
  42. }
  43. addListener (ev, fn) {
  44. return this.on(ev, fn)
  45. }
  46. on (ev, fn) {
  47. const ret = super.on(ev, fn)
  48. if (ev === 'done' && this.didDone) {
  49. this.emit('done', this.result)
  50. }
  51. return ret
  52. }
  53. done () {
  54. if (!this.didDone) {
  55. this.didDone = true
  56. if (!this.parent) {
  57. const res = Array.from(this.result)
  58. this.result = res
  59. this.emit('done', res)
  60. } else {
  61. this.emit('done')
  62. }
  63. }
  64. }
  65. start () {
  66. const pj = path.resolve(this.path, 'package.json')
  67. if (this.packageJsonCache.has(pj)) {
  68. this.onPackage(this.packageJsonCache.get(pj))
  69. } else {
  70. this.readPackageJson(pj)
  71. }
  72. return this
  73. }
  74. readPackageJson (pj) {
  75. fs.readFile(pj, (er, data) =>
  76. er ? this.done() : this.onPackageJson(pj, data))
  77. }
  78. onPackageJson (pj, data) {
  79. try {
  80. this.package = normalizePackageBin(JSON.parse(data + ''))
  81. } catch (er) {
  82. return this.done()
  83. }
  84. this.packageJsonCache.set(pj, this.package)
  85. this.onPackage(this.package)
  86. }
  87. allDepsBundled (pkg) {
  88. return Object.keys(pkg.dependencies || {}).concat(
  89. Object.keys(pkg.optionalDependencies || {}))
  90. }
  91. onPackage (pkg) {
  92. // all deps are bundled if we got here as a child.
  93. // otherwise, only bundle bundledDeps
  94. // Get a unique-ified array with a short-lived Set
  95. const bdRaw = this.parent ? this.allDepsBundled(pkg)
  96. : pkg.bundleDependencies || pkg.bundledDependencies || []
  97. const bd = Array.from(new Set(
  98. Array.isArray(bdRaw) ? bdRaw
  99. : bdRaw === true ? this.allDepsBundled(pkg)
  100. : Object.keys(bdRaw)))
  101. if (!bd.length) {
  102. return this.done()
  103. }
  104. this.bundle = bd
  105. this.readModules()
  106. }
  107. readModules () {
  108. readdirNodeModules(this.path + '/node_modules', (er, nm) =>
  109. er ? this.onReaddir([]) : this.onReaddir(nm))
  110. }
  111. onReaddir (nm) {
  112. // keep track of what we have, in case children need it
  113. this.node_modules = nm
  114. this.bundle.forEach(dep => this.childDep(dep))
  115. if (this.children === 0) {
  116. this.done()
  117. }
  118. }
  119. childDep (dep) {
  120. if (this.node_modules.indexOf(dep) !== -1) {
  121. if (!this.seen.has(dep)) {
  122. this.seen.add(dep)
  123. this.child(dep)
  124. }
  125. } else if (this.parent) {
  126. this.parent.childDep(dep)
  127. }
  128. }
  129. child (dep) {
  130. const p = this.path + '/node_modules/' + dep
  131. this.children += 1
  132. const child = new BundleWalker({
  133. path: p,
  134. parent: this,
  135. })
  136. child.on('done', () => {
  137. if (--this.children === 0) {
  138. this.done()
  139. }
  140. })
  141. child.start()
  142. }
  143. }
  144. class BundleWalkerSync extends BundleWalker {
  145. start () {
  146. super.start()
  147. this.done()
  148. return this
  149. }
  150. readPackageJson (pj) {
  151. try {
  152. this.onPackageJson(pj, fs.readFileSync(pj))
  153. } catch {
  154. // empty catch
  155. }
  156. return this
  157. }
  158. readModules () {
  159. try {
  160. this.onReaddir(readdirNodeModulesSync(this.path + '/node_modules'))
  161. } catch {
  162. this.onReaddir([])
  163. }
  164. }
  165. child (dep) {
  166. new BundleWalkerSync({
  167. path: this.path + '/node_modules/' + dep,
  168. parent: this,
  169. }).start()
  170. }
  171. }
  172. const readdirNodeModules = (nm, cb) => {
  173. fs.readdir(nm, (er, set) => {
  174. if (er) {
  175. cb(er)
  176. } else {
  177. const scopes = set.filter(f => /^@/.test(f))
  178. if (!scopes.length) {
  179. cb(null, set)
  180. } else {
  181. const unscoped = set.filter(f => !/^@/.test(f))
  182. let count = scopes.length
  183. scopes.forEach(scope => {
  184. fs.readdir(nm + '/' + scope, (readdirEr, pkgs) => {
  185. if (readdirEr || !pkgs.length) {
  186. unscoped.push(scope)
  187. } else {
  188. unscoped.push.apply(unscoped, pkgs.map(p => scope + '/' + p))
  189. }
  190. if (--count === 0) {
  191. cb(null, unscoped)
  192. }
  193. })
  194. })
  195. }
  196. }
  197. })
  198. }
  199. const readdirNodeModulesSync = nm => {
  200. const set = fs.readdirSync(nm)
  201. const unscoped = set.filter(f => !/^@/.test(f))
  202. const scopes = set.filter(f => /^@/.test(f)).map(scope => {
  203. try {
  204. const pkgs = fs.readdirSync(nm + '/' + scope)
  205. return pkgs.length ? pkgs.map(p => scope + '/' + p) : [scope]
  206. } catch (er) {
  207. return [scope]
  208. }
  209. }).reduce((a, b) => a.concat(b), [])
  210. return unscoped.concat(scopes)
  211. }
  212. const walk = (options, callback) => {
  213. const p = new Promise((resolve, reject) => {
  214. new BundleWalker(options).on('done', resolve).on('error', reject).start()
  215. })
  216. return callback ? p.then(res => callback(null, res), callback) : p
  217. }
  218. const walkSync = options => {
  219. return new BundleWalkerSync(options).start().result
  220. }
  221. module.exports = walk
  222. walk.sync = walkSync
  223. walk.BundleWalker = BundleWalker
  224. walk.BundleWalkerSync = BundleWalkerSync