-
Notifications
You must be signed in to change notification settings - Fork 97
Expand file tree
/
Copy pathStats.scala
More file actions
141 lines (117 loc) · 4.54 KB
/
Copy pathStats.scala
File metadata and controls
141 lines (117 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package scalajsbundler
import play.api.libs.json._
import play.api.libs.functional.syntax._
import sbt.Logger
import scala.math.max
import java.io.File
import java.nio.file.Path
/**
* Webpack stats model and json parsers.
*
* See:
* https://webpack.js.org/api/stats/
*/
object Stats {
final case class Asset(name: String, size: Long, emitted: Boolean, chunkNames: List[String]) {
def formattedSize: String = {
val oneKiB = 1024L
val oneMiB = oneKiB * oneKiB
if (size < oneKiB) s"$size bytes"
else if (size < oneMiB) f"${size / oneKiB.toFloat}%1.2f KiB"
else f"${size / oneMiB.toFloat}%1.2f MiB"
}
}
object formatting {
final case class Part(t: String, l: Int) {
def maxL(p: Part): Part =
copy(l = max(l, p.l))
def leftPad: String =
// String interpolation doesn't support dynamic padding
t.reverse.padTo(l, ' ').reverse.mkString
}
object Part {
def apply(t: String) = new Part(t, t.length)
}
final case class AssetLine(asset: Part, size: Part, emitted: Part, chunks: Part) {
def adjustPadding(p: AssetLine): AssetLine = copy(asset.maxL(p.asset), size.maxL(p.size), emitted.maxL(p.emitted), chunks.maxL(p.chunks))
def show: String = List(asset, size, emitted, chunks).map(_.leftPad).mkString(" ")
}
object AssetLine {
val Zero: AssetLine = AssetLine(Part("Asset"), Part("Size"), Part(""), Part("Chunks"))
}
}
final case class WebpackError(moduleName: Option[String], message: String, loc: Option[String])
final case class WebpackWarning(moduleName: Option[String], message: String)
final case class WebpackStats(
version: String,
hash: String,
time: Long,
outputPath: Option[Path],
errors: List[WebpackError],
warnings: List[WebpackWarning],
assets: List[Asset]
) {
/**
* Prints to the log an output similar to what webpack pushes to stdout
*/
def print(log: Logger): Unit = {
import formatting._
// Print base info
List(s"Version: $version", s"Hash: $hash", s"Time: ${time}ms", s"Path: ${outputPath.getOrElse("<default>")}").foreach(x => log.info(x))
log.info("")
// Print the assets
assets.map { a =>
val emitted = if (a.emitted) "[emitted]" else ""
AssetLine(Part(a.name), Part(a.formattedSize), Part(emitted), Part(a.chunkNames.mkString("[", ",", "]")))
}.foldLeft(List(AssetLine.Zero)) {
case (lines, curr) =>
val adj = lines.map(_.adjustPadding(curr))
val adjNew = adj.headOption.fold(curr)(curr.adjustPadding)
(adjNew :: adj.reverse).reverse
}.foreach { l =>
log.info(l.show)
}
log.info("")
}
/**
* Attempts to find the name of the asset for the project name
* Note that we only search on files ending on .js skipping e.g. map files
*/
def assetName(project: String): Option[String] =
assets.find(a => a.chunkNames.contains(project) && a.name.endsWith(".js")).map(_.name)
/**
* Resolve the asset on the output path or the target dir if unavailable
*/
def resolveAsset(altDir: Path, asset: String): Option[File] =
assetName(asset).map(a => outputPath.getOrElse(altDir).resolve(a).toFile)
/**
* Resolve alles asset on the output path or the target dir if unavailable
*/
def resolveAllAssets(altDir: Path): List[File] =
assets.map(a => outputPath.getOrElse(altDir).resolve(a.name).toFile)
}
implicit val assetsReads: Reads[Asset] = (
(JsPath \ "name").read[String] and
(JsPath \ "size").read[Long] and
(JsPath \ "emitted").read[Boolean] and
(JsPath \ "chunkNames").read[List[String]]
)(Asset.apply _)
implicit val errorReads: Reads[WebpackError] = (
(JsPath \ "moduleName").readNullable[String] and
(JsPath \ "message").read[String] and
(JsPath \ "loc").readNullable[String]
)(WebpackError.apply _)
implicit val warningReads: Reads[WebpackWarning] = (
(JsPath \ "moduleName").readNullable[String] and
(JsPath \ "message").read[String]
)(WebpackWarning.apply _)
implicit val statsReads: Reads[WebpackStats] = (
(JsPath \ "version").read[String] and
(JsPath \ "hash").read[String] and
(JsPath \ "time").read[Long] and
(JsPath \ "outputPath").readNullable[String].map(x => x.map(new File(_).toPath)) and // It seems webpack 2 doesn't produce outputPath
(JsPath \ "errors").read[List[WebpackError]] and
(JsPath \ "warnings").read[List[WebpackWarning]] and
(JsPath \ "assets").read[List[Asset]]
)(WebpackStats.apply _)
}