DashboardPlugin.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. // From https://github.com/FormidableLabs/webpack-dashboard/blob/7f99b31c5f00a7818d8129cb8a8fc6eb1b71799c/plugin/index.js
  2. // Modified by Guillaume Chau (Akryum)
  3. /* eslint-disable max-params, max-statements */
  4. 'use strict'
  5. const path = require('path')
  6. const fs = require('fs-extra')
  7. const webpack = require('webpack')
  8. const { IpcMessenger } = require('@vue/cli-shared-utils')
  9. const { analyzeBundle } = require('./analyzeBundle')
  10. const ID = 'vue-cli-dashboard-plugin'
  11. const ONE_SECOND = 1000
  12. const FILENAME_QUERY_REGEXP = /\?.*$/
  13. const ipc = new IpcMessenger()
  14. function getTimeMessage (timer) {
  15. let time = Date.now() - timer
  16. if (time >= ONE_SECOND) {
  17. time /= ONE_SECOND
  18. time = Math.round(time)
  19. time += 's'
  20. } else {
  21. time += 'ms'
  22. }
  23. return ` (${time})`
  24. }
  25. class DashboardPlugin {
  26. constructor (options) {
  27. this.type = options.type
  28. if (this.type === 'build' && options.moduleBuild) {
  29. this.type = 'build-modern'
  30. }
  31. this.watching = false
  32. this.autoDisconnect = !options.keepAlive
  33. }
  34. cleanup () {
  35. this.sendData = null
  36. if (this.autoDisconnect) ipc.disconnect()
  37. }
  38. apply (compiler) {
  39. let sendData = this.sendData
  40. let timer
  41. let inProgress = false
  42. let assetSources = new Map()
  43. if (!sendData) {
  44. sendData = data => ipc.send({
  45. webpackDashboardData: {
  46. type: this.type,
  47. value: data
  48. }
  49. })
  50. }
  51. // Progress status
  52. let progressTime = Date.now()
  53. const progressPlugin = new webpack.ProgressPlugin((percent, msg) => {
  54. // in webpack 5, progress plugin will continue sending progresses even after the done hook
  55. // for things like caching, causing the progress indicator stuck at 0.99
  56. // so we have to use a flag to stop sending such `compiling` progress data
  57. if (!inProgress) {
  58. return
  59. }
  60. // Debouncing
  61. const time = Date.now()
  62. if (time - progressTime > 300) {
  63. progressTime = time
  64. sendData([
  65. {
  66. type: 'status',
  67. value: 'Compiling'
  68. },
  69. {
  70. type: 'progress',
  71. value: percent
  72. },
  73. {
  74. type: 'operations',
  75. value: msg + getTimeMessage(timer)
  76. }
  77. ])
  78. }
  79. })
  80. progressPlugin.apply(compiler)
  81. compiler.hooks.watchRun.tap(ID, c => {
  82. this.watching = true
  83. })
  84. compiler.hooks.run.tap(ID, c => {
  85. this.watching = false
  86. })
  87. compiler.hooks.compile.tap(ID, () => {
  88. inProgress = true
  89. timer = Date.now()
  90. sendData([
  91. {
  92. type: 'status',
  93. value: 'Compiling'
  94. },
  95. {
  96. type: 'progress',
  97. value: 0
  98. }
  99. ])
  100. })
  101. compiler.hooks.invalid.tap(ID, () => {
  102. sendData([
  103. {
  104. type: 'status',
  105. value: 'Invalidated'
  106. },
  107. {
  108. type: 'progress',
  109. value: 0
  110. },
  111. {
  112. type: 'operations',
  113. value: 'idle'
  114. }
  115. ])
  116. })
  117. compiler.hooks.failed.tap(ID, () => {
  118. sendData([
  119. {
  120. type: 'status',
  121. value: 'Failed'
  122. },
  123. {
  124. type: 'operations',
  125. value: `idle${getTimeMessage(timer)}`
  126. }
  127. ])
  128. inProgress = false
  129. })
  130. compiler.hooks.afterEmit.tap(ID, compilation => {
  131. assetSources = new Map()
  132. for (const name in compilation.assets) {
  133. const asset = compilation.assets[name]
  134. const filename = name.replace(FILENAME_QUERY_REGEXP, '')
  135. try {
  136. assetSources.set(filename, asset.source())
  137. } catch (e) {
  138. const webpackFs = compiler.outputFileSystem
  139. const fullPath = (webpackFs.join || path.join)(compiler.options.output.path, filename)
  140. const buf = webpackFs.readFileSync(fullPath)
  141. assetSources.set(filename, buf.toString())
  142. }
  143. }
  144. })
  145. compiler.hooks.done.tap(ID, stats => {
  146. let statsData = stats.toJson()
  147. // Sometimes all the information is located in `children` array
  148. if ((!statsData.assets || !statsData.assets.length) && statsData.children && statsData.children.length) {
  149. statsData = statsData.children[0]
  150. }
  151. const outputPath = compiler.options.output.path
  152. statsData.assets.forEach(asset => {
  153. // Removing query part from filename (yes, somebody uses it for some reason and Webpack supports it)
  154. asset.name = asset.name.replace(FILENAME_QUERY_REGEXP, '')
  155. asset.fullPath = path.join(outputPath, asset.name)
  156. })
  157. // Analyze the assets and update sizes on assets and modules
  158. analyzeBundle(statsData, assetSources)
  159. const hasErrors = stats.hasErrors()
  160. sendData([
  161. {
  162. type: 'status',
  163. value: hasErrors ? 'Failed' : 'Success'
  164. },
  165. {
  166. type: 'progress',
  167. value: 1
  168. },
  169. {
  170. type: 'operations',
  171. value: `idle${getTimeMessage(timer)}`
  172. }
  173. ])
  174. inProgress = false
  175. const statsFile = path.resolve(process.cwd(), `./node_modules/.stats-${this.type}.json`)
  176. fs.writeJson(statsFile, {
  177. errors: hasErrors,
  178. warnings: stats.hasWarnings(),
  179. data: statsData
  180. }).then(() => {
  181. sendData([
  182. {
  183. type: 'stats'
  184. }
  185. ])
  186. if (!this.watching) {
  187. this.cleanup()
  188. }
  189. }).catch(error => {
  190. console.error(error)
  191. })
  192. })
  193. }
  194. }
  195. module.exports = DashboardPlugin