index.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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. return this
  72. }
  73. readPackageJson (pj) {
  74. fs.readFile(pj, (er, data) =>
  75. er ? this.done() : this.onPackageJson(pj, data))
  76. }
  77. onPackageJson (pj, data) {
  78. try {
  79. this.package = normalizePackageBin(JSON.parse(data + ''))
  80. } catch (er) {
  81. return this.done()
  82. }
  83. this.packageJsonCache.set(pj, this.package)
  84. this.onPackage(this.package)
  85. }
  86. allDepsBundled (pkg) {
  87. return Object.keys(pkg.dependencies || {}).concat(
  88. Object.keys(pkg.optionalDependencies || {}))
  89. }
  90. onPackage (pkg) {
  91. // all deps are bundled if we got here as a child.
  92. // otherwise, only bundle bundledDeps
  93. // Get a unique-ified array with a short-lived Set
  94. const bdRaw = this.parent ? this.allDepsBundled(pkg)
  95. : pkg.bundleDependencies || pkg.bundledDependencies || []
  96. const bd = Array.from(new Set(
  97. Array.isArray(bdRaw) ? bdRaw
  98. : bdRaw === true ? this.allDepsBundled(pkg)
  99. : Object.keys(bdRaw)))
  100. if (!bd.length)
  101. return this.done()
  102. this.bundle = bd
  103. const nm = this.path + '/node_modules'
  104. this.readModules()
  105. }
  106. readModules () {
  107. readdirNodeModules(this.path + '/node_modules', (er, nm) =>
  108. er ? this.onReaddir([]) : this.onReaddir(nm))
  109. }
  110. onReaddir (nm) {
  111. // keep track of what we have, in case children need it
  112. this.node_modules = nm
  113. this.bundle.forEach(dep => this.childDep(dep))
  114. if (this.children === 0)
  115. this.done()
  116. }
  117. childDep (dep) {
  118. if (this.node_modules.indexOf(dep) !== -1) {
  119. if (!this.seen.has(dep)) {
  120. this.seen.add(dep)
  121. this.child(dep)
  122. }
  123. } else if (this.parent) {
  124. this.parent.childDep(dep)
  125. }
  126. }
  127. child (dep) {
  128. const p = this.path + '/node_modules/' + dep
  129. this.children += 1
  130. const child = new BundleWalker({
  131. path: p,
  132. parent: this
  133. })
  134. child.on('done', _ => {
  135. if (--this.children === 0)
  136. this.done()
  137. })
  138. child.start()
  139. }
  140. }
  141. class BundleWalkerSync extends BundleWalker {
  142. constructor (opt) {
  143. super(opt)
  144. }
  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 (er) {}
  154. return this
  155. }
  156. readModules () {
  157. try {
  158. this.onReaddir(readdirNodeModulesSync(this.path + '/node_modules'))
  159. } catch (er) {
  160. this.onReaddir([])
  161. }
  162. }
  163. child (dep) {
  164. new BundleWalkerSync({
  165. path: this.path + '/node_modules/' + dep,
  166. parent: this
  167. }).start()
  168. }
  169. }
  170. const readdirNodeModules = (nm, cb) => {
  171. fs.readdir(nm, (er, set) => {
  172. if (er)
  173. cb(er)
  174. else {
  175. const scopes = set.filter(f => /^@/.test(f))
  176. if (!scopes.length)
  177. cb(null, set)
  178. else {
  179. const unscoped = set.filter(f => !/^@/.test(f))
  180. let count = scopes.length
  181. scopes.forEach(scope => {
  182. fs.readdir(nm + '/' + scope, (er, pkgs) => {
  183. if (er || !pkgs.length)
  184. unscoped.push(scope)
  185. else
  186. unscoped.push.apply(unscoped, pkgs.map(p => scope + '/' + p))
  187. if (--count === 0)
  188. cb(null, unscoped)
  189. })
  190. })
  191. }
  192. }
  193. })
  194. }
  195. const readdirNodeModulesSync = nm => {
  196. const set = fs.readdirSync(nm)
  197. const unscoped = set.filter(f => !/^@/.test(f))
  198. const scopes = set.filter(f => /^@/.test(f)).map(scope => {
  199. try {
  200. const pkgs = fs.readdirSync(nm + '/' + scope)
  201. return pkgs.length ? pkgs.map(p => scope + '/' + p) : [scope]
  202. } catch (er) {
  203. return [scope]
  204. }
  205. }).reduce((a, b) => a.concat(b), [])
  206. return unscoped.concat(scopes)
  207. }
  208. const walk = (options, callback) => {
  209. const p = new Promise((resolve, reject) => {
  210. new BundleWalker(options).on('done', resolve).on('error', reject).start()
  211. })
  212. return callback ? p.then(res => callback(null, res), callback) : p
  213. }
  214. const walkSync = options => {
  215. return new BundleWalkerSync(options).start().result
  216. }
  217. module.exports = walk
  218. walk.sync = walkSync
  219. walk.BundleWalker = BundleWalker
  220. walk.BundleWalkerSync = BundleWalkerSync