Skip to content

Commit 5c36f19

Browse files
authored
Merge pull request #390 from vhiairrassary/vhiairrassary/webpack5
Add support for Webpack 5 and drop previous versions
2 parents d66d00c + 4811d07 commit 5c36f19

File tree

33 files changed

+227
-244
lines changed

33 files changed

+227
-244
lines changed

manual/src/ornate/cookbook.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ webpackDevServerExtraArgs := Seq("--inline")
287287
`webpack` is then called with the following arguments:
288288
289289
~~~
290-
--bail --config <configfile>
290+
--config <configfile>
291291
~~~
292292
293293
You can add extra params to the `webpack` call, for example, to increase debugging

sbt-scalajs-bundler/src/main/scala/scalajsbundler/PackageJson.scala

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,25 +42,18 @@ object PackageJson {
4242

4343
val sourceMapLoaderVersion =
4444
NpmPackage(webpackVersion).major match {
45-
case Some(1) | Some(2) => "0.1.5"
46-
case Some(3) => "0.2.1"
47-
case Some(4) => "0.2.3"
48-
case Some(x) => sys.error(s"Unsupported webpack major version $x")
49-
case None => sys.error("No webpack version defined")
50-
}
51-
52-
val webpackPackages =
53-
NpmPackage(webpackVersion).major match {
54-
case Some(1) | Some(2) | Some(3) => Seq("webpack" -> webpackVersion)
55-
case Some(4) => Seq("webpack" -> webpackVersion, "webpack-cli" -> webpackCliVersion)
56-
case _ => Seq.empty
45+
case Some(5) => "2.0.0"
46+
case Some(x) => sys.error(s"Unsupported webpack major version $x")
47+
case None => sys.error("No webpack version defined")
5748
}
5849

5950
val devDependencies =
6051
npmDevDependencies ++ (
6152
if (currentConfiguration == Compile) npmManifestDependencies.compileDevDependencies
6253
else npmManifestDependencies.testDevDependencies
63-
) ++ webpackPackages ++ Seq(
54+
) ++ Seq(
55+
"webpack" -> webpackVersion,
56+
"webpack-cli" -> webpackCliVersion,
6457
"webpack-dev-server" -> webpackDevServerVersion,
6558
"concat-with-sourcemaps" -> "1.0.7", // Used by the reload workflow
6659
"source-map-loader" -> sourceMapLoaderVersion // Used by webpack when emitSourceMaps is enabled
@@ -77,7 +70,6 @@ object PackageJson {
7770

7871
log.debug("Writing 'package.json'")
7972
IO.write(targetFile, packageJson.toJson)
80-
()
8173
}
8274

8375
/**

sbt-scalajs-bundler/src/main/scala/scalajsbundler/Stats.scala

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11

22
package scalajsbundler
33

4-
import java.time.LocalDateTime
5-
64
import play.api.libs.json._
75
import play.api.libs.functional.syntax._
86
import sbt.Logger
@@ -15,7 +13,16 @@ import java.nio.file.Path
1513
*/
1614
object Stats {
1715

18-
final case class Asset(name: String, size: Long, emitted: Option[Boolean], chunkNames: List[String])
16+
final case class Asset(name: String, size: Long, emitted: Boolean, chunkNames: List[String]) {
17+
def formattedSize: String = {
18+
val oneKiB = 1024L
19+
val oneMiB = oneKiB * oneKiB
20+
21+
if (size < oneKiB) s"$size bytes"
22+
else if (size < oneMiB) f"${size / oneKiB.toFloat}%1.2f KiB"
23+
else f"${size / oneMiB.toFloat}%1.2f MiB"
24+
}
25+
}
1926

2027
object formatting {
2128

@@ -43,20 +50,32 @@ object Stats {
4350

4451
}
4552

46-
final case class WebpackStats(version: String, hash: String, time: Long, outputPath: Option[Path], errors: List[String], warnings: List[String], assets: List[Asset]) {
53+
final case class WebpackError(moduleName: String, message: String, loc: String)
54+
55+
final case class WebpackWarning(moduleName: String, message: String)
56+
57+
final case class WebpackStats(
58+
version: String,
59+
hash: String,
60+
time: Long,
61+
outputPath: Option[Path],
62+
errors: List[WebpackError],
63+
warnings: List[WebpackWarning],
64+
assets: List[Asset]
65+
) {
4766

4867
/**
4968
* Prints to the log an output similar to what webpack pushes to stdout
5069
*/
5170
def print(log: Logger): Unit = {
5271
import formatting._
5372
// Print base info
54-
List(s"Version: $version", s"Hash: $hash", s"Time: ${time}ms", s"Path: ${outputPath.getOrElse("<default>")}", s"Built at ${LocalDateTime.now}").foreach(x => log.info(x))
73+
List(s"Version: $version", s"Hash: $hash", s"Time: ${time}ms", s"Path: ${outputPath.getOrElse("<default>")}").foreach(x => log.info(x))
5574
log.info("")
5675
// Print the assets
5776
assets.map { a =>
58-
val emitted = a.emitted.fold("<unknown>")(a => if (a) "[emitted]" else "")
59-
AssetLine(Part(a.name), Part(a.size.toString), Part(emitted), Part(a.chunkNames.mkString("[", ",", "]")))
77+
val emitted = if (a.emitted) "[emitted]" else ""
78+
AssetLine(Part(a.name), Part(a.formattedSize), Part(emitted), Part(a.chunkNames.mkString("[", ",", "]")))
6079
}.foldLeft(List(AssetLine.Zero)) {
6180
case (lines, curr) =>
6281
val adj = lines.map(_.adjustPadding(curr))
@@ -91,17 +110,28 @@ object Stats {
91110
implicit val assetsReads: Reads[Asset] = (
92111
(JsPath \ "name").read[String] and
93112
(JsPath \ "size").read[Long] and
94-
(JsPath \ "emitted").readNullable[Boolean] and
95-
(JsPath \\ "chunkNames").read[List[String]]
113+
(JsPath \ "emitted").read[Boolean] and
114+
(JsPath \ "chunkNames").read[List[String]]
96115
)(Asset.apply _)
97116

117+
implicit val errorReads: Reads[WebpackError] = (
118+
(JsPath \ "moduleName").read[String] and
119+
(JsPath \ "message").read[String] and
120+
(JsPath \ "loc").read[String]
121+
)(WebpackError.apply _)
122+
123+
implicit val warningReads: Reads[WebpackWarning] = (
124+
(JsPath \ "moduleName").read[String] and
125+
(JsPath \ "message").read[String]
126+
)(WebpackWarning.apply _)
127+
98128
implicit val statsReads: Reads[WebpackStats] = (
99129
(JsPath \ "version").read[String] and
100130
(JsPath \ "hash").read[String] and
101131
(JsPath \ "time").read[Long] and
102132
(JsPath \ "outputPath").readNullable[String].map(x => x.map(new File(_).toPath)) and // It seems webpack 2 doesn't produce outputPath
103-
(JsPath \ "errors").read[List[String]] and
104-
(JsPath \ "warnings").read[List[String]] and
133+
(JsPath \ "errors").read[List[WebpackError]] and
134+
(JsPath \ "warnings").read[List[WebpackWarning]] and
105135
(JsPath \ "assets").read[List[Asset]]
106136
)(WebpackStats.apply _)
107137

sbt-scalajs-bundler/src/main/scala/scalajsbundler/Webpack.scala

Lines changed: 80 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Stats._
1010
import scala.util.{Failure, Success, Try}
1111

1212
object Webpack {
13-
// Represents webpack 4 modes
13+
// Represents webpack 5 modes
1414
sealed trait WebpackMode {
1515
def mode: String
1616
}
@@ -61,95 +61,74 @@ object Webpack {
6161
webpackConfigFile: BundlerFile.WebpackConfig,
6262
libraryBundleName: Option[String],
6363
mode: WebpackMode,
64+
devServerPort: Int,
6465
log: Logger
6566
): Unit = {
67+
val webpackConfigContent = generateConfigFile(emitSourceMaps, entry, webpackConfigFile, libraryBundleName, mode,
68+
devServerPort)
69+
6670
log.info("Writing scalajs.webpack.config.js")
67-
// Build the output configuration, configured for library output
68-
// if a library bundle name is provided
69-
val output = libraryBundleName match {
70-
case Some(bundleName) =>
71-
JS.obj(
72-
"path" -> JS.str(webpackConfigFile.targetDir.toAbsolutePath.toString),
73-
"filename" -> JS.str(BundlerFile.Library.fileName("[name]")),
74-
"library" -> JS.str(bundleName),
75-
"libraryTarget" -> JS.str("var")
76-
)
77-
case None =>
78-
JS.obj(
79-
"path" -> JS.str(webpackConfigFile.targetDir.toAbsolutePath.toString),
80-
"filename" -> JS.str(BundlerFile.ApplicationBundle.fileName("[name]"))
81-
)
82-
}
71+
IO.write(webpackConfigFile.file, webpackConfigContent.show)
72+
}
8373

84-
// Build the file itself
85-
val webpackConfigContent =
86-
JS.ref("module").dot("exports").assign(JS.obj(Seq(
87-
"entry" -> JS.obj(
88-
entry.project -> JS.arr(JS.str(entry.file.absolutePath))
89-
),
90-
"output" -> output
91-
) ++ (
92-
if (emitSourceMaps) {
93-
val webpackNpmPackage = NpmPackage.getForModule(webpackConfigFile.targetDir.toFile, "webpack")
94-
webpackNpmPackage.flatMap(_.major) match {
95-
case Some(1) =>
96-
Seq(
97-
"devtool" -> JS.str("source-map"),
98-
"module" -> JS.obj(
99-
"preLoaders" -> JS.arr(
100-
JS.obj(
101-
"test" -> JS.regex("\\.js$"),
102-
"loader" -> JS.str("source-map-loader")
103-
)
104-
)
105-
)
106-
)
107-
case Some(2) =>
108-
Seq(
109-
"devtool" -> JS.str("source-map"),
110-
"module" -> JS.obj(
111-
"rules" -> JS.arr(
112-
JS.obj(
113-
"test" -> JS.regex("\\.js$"),
114-
"enforce" -> JS.str("pre"),
115-
"loader" -> JS.str("source-map-loader")
116-
)
117-
)
118-
)
119-
)
120-
case Some(3) =>
121-
Seq(
122-
"devtool" -> JS.str("source-map"),
123-
"module" -> JS.obj(
124-
"rules" -> JS.arr(
125-
JS.obj(
126-
"test" -> JS.regex("\\.js$"),
127-
"enforce" -> JS.str("pre"),
128-
"use" -> JS.arr(JS.str("source-map-loader"))
129-
)
130-
)
131-
)
132-
)
133-
case Some(4) =>
134-
Seq(
135-
"mode" -> JS.str(mode.mode),
136-
"devtool" -> JS.str("source-map"),
137-
"module" -> JS.obj(
138-
"rules" -> JS.arr(
139-
JS.obj(
140-
"test" -> JS.regex("\\.js$"),
141-
"enforce" -> JS.str("pre"),
142-
"use" -> JS.arr(JS.str("source-map-loader"))
143-
)
74+
private def generateConfigFile(
75+
emitSourceMaps: Boolean,
76+
entry: BundlerFile.WebpackInput,
77+
webpackConfigFile: BundlerFile.WebpackConfig,
78+
libraryBundleName: Option[String],
79+
mode: WebpackMode,
80+
devServerPort: Int
81+
): JS = {
82+
val webpackNpmPackage = NpmPackage.getForModule(webpackConfigFile.targetDir.toFile, "webpack")
83+
webpackNpmPackage.flatMap(_.major) match {
84+
case Some(5) =>
85+
// Build the output configuration, configured for library output
86+
// if a library bundle name is provided
87+
val output = libraryBundleName match {
88+
case Some(bundleName) =>
89+
JS.obj(
90+
"path" -> JS.str(webpackConfigFile.targetDir.toAbsolutePath.toString),
91+
"filename" -> JS.str(BundlerFile.Library.fileName("[name]")),
92+
"library" -> JS.str(bundleName),
93+
"libraryTarget" -> JS.str("var")
94+
)
95+
case None =>
96+
JS.obj(
97+
"path" -> JS.str(webpackConfigFile.targetDir.toAbsolutePath.toString),
98+
"filename" -> JS.str(BundlerFile.ApplicationBundle.fileName("[name]"))
99+
)
100+
}
101+
102+
JS.ref("module").dot("exports").assign(JS.obj(Seq(
103+
"entry" -> JS.obj(
104+
entry.project -> JS.arr(JS.str(entry.file.absolutePath))
105+
),
106+
"output" -> output,
107+
"mode" -> JS.str(mode.mode),
108+
"devServer" -> JS.obj("port" -> JS.int(devServerPort)),
109+
) ++ (
110+
if (emitSourceMaps) {
111+
Seq(
112+
"devtool" -> JS.str("source-map"),
113+
"module" -> JS.obj(
114+
"rules" -> JS.arr(
115+
JS.obj(
116+
"test" -> JS.regex("\\.js$"),
117+
"enforce" -> JS.str("pre"),
118+
"use" -> JS.arr(JS.str("source-map-loader"))
144119
)
145120
)
146121
)
147-
case Some(x) => sys.error(s"Unsupported webpack major version $x")
148-
case None => sys.error("No webpack version defined")
149-
}
150-
} else Nil
151-
): _*))
152-
IO.write(webpackConfigFile.file, webpackConfigContent.show)
122+
)
123+
} else Nil
124+
): _*))
125+
126+
case Some(x) =>
127+
sys.error(s"Unsupported webpack major version $x")
128+
129+
case None =>
130+
sys.error("No webpack version defined")
131+
}
153132
}
154133

155134
/**
@@ -162,7 +141,8 @@ object Webpack {
162141
* @param entry Scala.js application to bundle
163142
* @param targetDir Target directory (and working directory for Nodejs)
164143
* @param extraArgs Extra arguments passed to webpack
165-
* @param mode Mode for webpack 4
144+
* @param mode Mode for webpack 5
145+
* @param devServerPort Port used by webpack-dev-server
166146
* @param log Logger
167147
* @return The generated bundles
168148
*/
@@ -176,9 +156,10 @@ object Webpack {
176156
extraArgs: Seq[String],
177157
nodeArgs: Seq[String],
178158
mode: WebpackMode,
159+
devServerPort: Int,
179160
log: Logger
180161
): BundlerFile.ApplicationBundle = {
181-
writeConfigFile(emitSourceMaps, entry, generatedWebpackConfigFile, None, mode, log)
162+
writeConfigFile(emitSourceMaps, entry, generatedWebpackConfigFile, None, mode, devServerPort, log)
182163

183164
val configFile = customWebpackConfigFile
184165
.map(Webpack.copyCustomWebpackConfigFiles(targetDir, webpackResources))
@@ -206,7 +187,7 @@ object Webpack {
206187
* @param entryPointFile The entrypoint file to bundle dependencies for
207188
* @param libraryModuleName The library module name to assign the webpack bundle to
208189
* @param extraArgs Extra arguments passed to webpack
209-
* @param mode Mode for webpack 4
190+
* @param mode Mode for webpack 5
210191
* @param log Logger
211192
* @return The generated bundle
212193
*/
@@ -220,6 +201,7 @@ object Webpack {
220201
extraArgs: Seq[String],
221202
nodeArgs: Seq[String],
222203
mode: WebpackMode,
204+
devServerPort: Int,
223205
log: Logger
224206
): BundlerFile.Library = {
225207
writeConfigFile(
@@ -228,6 +210,7 @@ object Webpack {
228210
generatedWebpackConfigFile,
229211
Some(libraryModuleName),
230212
mode,
213+
devServerPort,
231214
log
232215
)
233216

@@ -260,8 +243,16 @@ object Webpack {
260243
if (p.warnings.nonEmpty || p.errors.nonEmpty) {
261244
logger.info("")
262245
// Filtering is a workaround for #111
263-
p.warnings.filterNot(_.contains("https://raw.githubusercontent.com")).foreach(x => logger.warn(x))
264-
p.errors.foreach(x => logger.error(x))
246+
p.warnings.filterNot(_.message.contains("https://raw.githubusercontent.com")).foreach { warning =>
247+
logger.warn(s"WARNING in ${warning.moduleName}")
248+
logger.warn(warning.message)
249+
logger.warn("\n")
250+
}
251+
p.errors.foreach { error =>
252+
logger.error(s"ERROR in ${error.moduleName} ${error.loc}")
253+
logger.error(error.message)
254+
logger.error("\n")
255+
}
265256
}
266257
Some(p)
267258
}
@@ -290,7 +281,7 @@ object Webpack {
290281
*/
291282
def run(nodeArgs: String*)(args: String*)(workingDir: File, log: Logger): Option[WebpackStats] = {
292283
val webpackBin = workingDir / "node_modules" / "webpack" / "bin" / "webpack"
293-
val params = nodeArgs ++ Seq(webpackBin.absolutePath, "--bail", "--profile", "--json") ++ args
284+
val params = nodeArgs ++ Seq(webpackBin.absolutePath, "--profile", "--json") ++ args
294285
val cmd = "node" +: params
295286
Commands.run(cmd, workingDir, log, jsonOutput(cmd, log)).fold(sys.error, _.flatten)
296287
}

0 commit comments

Comments
 (0)