-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Failure to parse Webpack stats fails the build #424
base: main
Are you sure you want to change the base?
Changes from all commits
f5aa4a8
a42a8a3
3912d62
d83aea8
ecb5b6f
d33cb21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -168,12 +168,10 @@ object Webpack { | |
log.info("Bundling the application with its NPM dependencies") | ||
val args = extraArgs ++: Seq("--config", configFile.absolutePath) | ||
val stats = Webpack.run(nodeArgs: _*)(args: _*)(targetDir, log) | ||
stats.foreach(_.print(log)) | ||
stats.print(log) | ||
|
||
// Attempt to discover the actual name produced by webpack indexing by chunk name and discarding maps | ||
val bundle = generatedWebpackConfigFile.asApplicationBundle(stats) | ||
assert(bundle.file.exists(), "Webpack failed to create application bundle") | ||
assert(bundle.assets.forall(_.exists()), "Webpack failed to create application assets") | ||
bundle | ||
} | ||
|
||
|
@@ -220,55 +218,51 @@ object Webpack { | |
|
||
val args = extraArgs ++: Seq("--config", configFile.absolutePath) | ||
val stats = Webpack.run(nodeArgs: _*)(args: _*)(generatedWebpackConfigFile.targetDir.toFile, log) | ||
stats.foreach(_.print(log)) | ||
stats.print(log) | ||
|
||
val library = generatedWebpackConfigFile.asLibrary(stats) | ||
assert(library.file.exists, "Webpack failed to create library file") | ||
assert(library.assets.forall(_.exists), "Webpack failed to create library assets") | ||
library | ||
} | ||
|
||
private def jsonOutput(cmd: Seq[String], logger: Logger)(in: InputStream): Option[WebpackStats] = { | ||
Try { | ||
val parsed = Json.parse(in) | ||
parsed.validate[WebpackStats] match { | ||
case JsError(e) => | ||
logger.error("Error parsing webpack stats output") | ||
// In case of error print the result and return None. it will be ignored upstream | ||
e.foreach { | ||
case (p, v) => logger.error(s"$p: ${v.mkString(",")}") | ||
} | ||
None | ||
case JsSuccess(p, _) => | ||
if (p.warnings.nonEmpty || p.errors.nonEmpty) { | ||
logger.info("") | ||
// Filtering is a workaround for #111 | ||
p.warnings.filterNot(_.message.contains("https://raw.githubusercontent.com")).foreach { warning => | ||
logger.warn(s"WARNING in ${warning.moduleName}") | ||
logger.warn(warning.message) | ||
logger.warn("\n") | ||
} | ||
p.errors.foreach { error => | ||
logger.error(s"ERROR in ${error.moduleName} ${error.loc}") | ||
logger.error(error.message) | ||
logger.error("\n") | ||
} | ||
private def jsonOutput(cmd: Seq[String], logger: Logger)(in: InputStream): Try[WebpackStats] = { | ||
Try(Json.parse(in)) | ||
.fold( | ||
e => Failure( | ||
new RuntimeException( | ||
"Failure on parsing the output of webpack\n" + | ||
"You can try to manually execute the command\n" + | ||
s"${cmd.mkString(" ")}", | ||
e | ||
) | ||
), | ||
parsed => { | ||
parsed.validate[WebpackStats] match { | ||
case JsError(e) => | ||
Failure( | ||
new RuntimeException( | ||
"Error parsing webpack stats output\n" + | ||
s"${e.map { case (p, v) => s"$p: ${v.mkString(",")}"}.mkString("\n")}" | ||
) | ||
) | ||
case JsSuccess(p, _) => | ||
if (p.warnings.nonEmpty || p.errors.nonEmpty) { | ||
logger.info("") | ||
// Filtering is a workaround for #111 | ||
p.warnings.filterNot(_.message.contains("https://raw.githubusercontent.com")).foreach { warning => | ||
logger.warn(s"WARNING") | ||
logger.warn(warning.format()) | ||
logger.warn("\n") | ||
} | ||
p.errors.foreach { error => | ||
logger.error(s"ERROR") | ||
logger.error(error.format()) | ||
logger.error("\n") | ||
} | ||
} | ||
Success(p) | ||
} | ||
Some(p) | ||
} | ||
} match { | ||
case Success(x) => | ||
x | ||
case Failure(e) => | ||
// In same cases errors are not reported on the json output but comes on stdout | ||
// where they cannot be parsed as json. The best we can do here is to suggest | ||
// running the command manually | ||
logger.error(s"Failure on parsing the output of webpack: ${e.getMessage}") | ||
logger.error(s"You can try to manually execute the command") | ||
logger.error(cmd.mkString(" ")) | ||
logger.error("\n") | ||
None | ||
} | ||
} | ||
) | ||
} | ||
|
||
/** | ||
|
@@ -279,11 +273,16 @@ object Webpack { | |
* @param workingDir Working directory in which the Nodejs will be run (where there is the `node_modules` subdirectory) | ||
* @param log Logger | ||
*/ | ||
def run(nodeArgs: String*)(args: String*)(workingDir: File, log: Logger): Option[WebpackStats] = { | ||
def run(nodeArgs: String*)(args: String*)(workingDir: File, log: Logger): WebpackStats = { | ||
val webpackBin = workingDir / "node_modules" / "webpack" / "bin" / "webpack" | ||
val params = nodeArgs ++ Seq(webpackBin.absolutePath, "--profile", "--json") ++ args | ||
val cmd = "node" +: params | ||
Commands.run(cmd, workingDir, log, jsonOutput(cmd, log)).fold(sys.error, _.flatten) | ||
log.debug(s"Running command [${cmd.mkString(" ")}]") | ||
Commands.run(cmd, workingDir, log, jsonOutput(cmd, log)) | ||
.fold( | ||
sys.error, | ||
Comment on lines
+282
to
+283
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you're going to throw when the val runResult = Commands.run(...).get But then again, why change There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to modify the existing code as little as possible, I see that expression was lost in a way with nesting of all these monads.
So current implementation looks more funky because I wanted to separate the cases 2) and 3) - failures are related to command output, but mean different things. Maybe this would have looked clearer? Commands.run(cmd, workingDir, log, jsonOutput(cmd, log))
.fold(
sys.error,
_.fold(throw new RuntimeException("Command succeeded, but returned no webpack stats output"))(_.get)
) Otherwise Knowing this, what further modifications would you suggest? |
||
_.map(_.get).getOrElse(throw new RuntimeException("Failure on returning webpack stats from command output")) | ||
) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,23 @@ | ||
package scalajsbundler.util | ||
|
||
import sbt.Logger | ||
import java.io.{InputStream, File} | ||
import java.io.{File, InputStream} | ||
|
||
import scala.sys.process.Process | ||
Comment on lines
3
to
6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Spurious changes. |
||
import scala.sys.process.BasicIO | ||
import scala.sys.process.ProcessLogger | ||
import scala.util.Try | ||
|
||
object Commands { | ||
|
||
def run[A](cmd: Seq[String], cwd: File, logger: Logger, outputProcess: InputStream => A): Either[String, Option[A]] = { | ||
def run[A](cmd: Seq[String], cwd: File, logger: Logger, outputProcess: InputStream => Try[A]): Either[String, Option[Try[A]]] = { | ||
val toErrorLog = (is: InputStream) => { | ||
scala.io.Source.fromInputStream(is).getLines.foreach(msg => logger.error(msg)) | ||
is.close() | ||
} | ||
|
||
// Unfortunately a var is the only way to capture the result | ||
var result: Option[A] = None | ||
var result: Option[Try[A]] = None | ||
def outputCapture(o: InputStream): Unit = { | ||
result = Some(outputProcess(o)) | ||
o.close() | ||
|
@@ -34,7 +36,7 @@ object Commands { | |
} | ||
|
||
def run(cmd: Seq[String], cwd: File, logger: Logger): Unit = { | ||
val toInfoLog = (is: InputStream) => scala.io.Source.fromInputStream(is).getLines.foreach(msg => logger.info(msg)) | ||
val toInfoLog = (is: InputStream) => Try(scala.io.Source.fromInputStream(is).getLines.foreach(msg => logger.info(msg))) | ||
run(cmd, cwd, logger, toInfoLog).fold(sys.error, _ => ()) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
scalaVersion := "2.12.8" | ||
|
||
useYarn := true | ||
|
||
yarnExtraArgs in Compile := Seq("--silent") | ||
|
||
scalaJSUseMainModuleInitializer := true | ||
|
||
webpackConfigFile := Some(baseDirectory.value / "webpack.config.js") | ||
|
||
enablePlugins(ScalaJSBundlerPlugin) | ||
|
||
ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
val scalaJSVersion = sys.props.getOrElse("scalajs.version", sys.error("'scalajs.version' environment variable is not defined")) | ||
val scalaJSBundlerVersion = sys.props.getOrElse("plugin.version", sys.error("'plugin.version' environment variable is not set")) | ||
|
||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % scalaJSVersion) | ||
|
||
addSbtPlugin("ch.epfl.scala" % "sbt-scalajs-bundler" % scalaJSBundlerVersion) | ||
|
||
ivyLoggingLevel in ThisBuild := UpdateLogging.Quiet |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package example | ||
|
||
object Main { | ||
def main(args: Array[String]): Unit = { | ||
println("webpack-stats main") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
> fullOptJS::webpack | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand how this test relates to the fix. If this PR is about failing a build, there should a be at least one negative test line (starting with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test would fail in current |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// Load the config generated by scalajs-bundler | ||
var config = require('./scalajs.webpack.config'); | ||
|
||
module.exports = config; | ||
|
||
module.exports.performance = { | ||
maxEntrypointSize: 1 // force warning | ||
}; | ||
|
||
module.exports.output.filename = '[name].[contenthash].bundle.js' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These changes don't seem to have anything to do with the purpose of this PR, have they?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sjrd It does because you need to update the JSON decoders to parse the new
Option
types that have been added. So rather than explicitly modify the individual fields it parses as Options, this solves the problem using derived decoders, which is also fewer lines and I don't think there are any downsides since it's compile-time derivation (maybe slightly longer time taken compiling?)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the model has changed,
Reads
do have to be modified, and derived decoders are a simplification of those changes.