-
-
Save nafg/6ecce298a0a20f1e4a259cdae5634060 to your computer and use it in GitHub Desktop.
| import java.io._ | |
| import java.util.zip.ZipInputStream | |
| import geny.Generator | |
| import mill._ | |
| import mill.define.Target | |
| import mill.scalajslib._ | |
| object WebpackLib { | |
| case class JsDeps(dependencies: List[(String, String)] = Nil, | |
| devDependencies: List[(String, String)] = Nil, | |
| jsSources: Map[String, String] = Map.empty) { | |
| def ++(that: JsDeps): JsDeps = | |
| JsDeps( | |
| dependencies ++ that.dependencies, | |
| devDependencies ++ that.devDependencies, | |
| jsSources ++ that.jsSources) | |
| } | |
| object JsDeps { | |
| def apply(dependencies: (String, String)*): JsDeps = JsDeps(dependencies = dependencies.toList) | |
| implicit def rw: upickle.default.ReadWriter[JsDeps] = upickle.default.macroRW | |
| } | |
| case class WebpackParams(inputFile: os.Path, | |
| jsDeps: JsDeps, | |
| outputDirectory: os.Path, | |
| opt: Boolean, | |
| libraryName: Option[String]) { | |
| lazy val copiedInputFile = outputDirectory / inputFile.last | |
| } | |
| private def writePkgJson(params: WebpackParams, | |
| deps: JsDeps, | |
| webpackVersion: String, | |
| webpackCliVersion: String, | |
| webpackDevServerVersion: String) = { | |
| val webpackDevDependencies = Seq( | |
| "webpack" -> webpackVersion, | |
| "webpack-cli" -> webpackCliVersion, | |
| "webpack-dev-server" -> webpackDevServerVersion, | |
| "source-map-loader" -> "0.2.3" | |
| ) | |
| os.write.over( | |
| params.outputDirectory / "package.json", | |
| ujson.Obj( | |
| "dependencies" -> deps.dependencies, | |
| "devDependencies" -> (deps.devDependencies ++ webpackDevDependencies) | |
| ).render(2) + "\n" | |
| ) | |
| } | |
| @scala.annotation.tailrec | |
| private def readAllBytes(in: InputStream, | |
| buffer: Array[Byte] = new Array[Byte](8192), | |
| out: ByteArrayOutputStream = new ByteArrayOutputStream): String = { | |
| val byteCount = in.read(buffer) | |
| if (byteCount < 0) | |
| out.toString | |
| else { | |
| out.write(buffer, 0, byteCount) | |
| readAllBytes(in, buffer, out) | |
| } | |
| } | |
| private def jsDepsFromJar(jar: File): Seq[JsDeps] = { | |
| val stream = new ZipInputStream(new BufferedInputStream(new FileInputStream(jar))) | |
| try | |
| Iterator.continually(stream.getNextEntry) | |
| .takeWhile(_ != null) | |
| .collect { | |
| case z if z.getName == "NPM_DEPENDENCIES" => | |
| val contentsAsJson = ujson.read(readAllBytes(stream)).obj | |
| def dependenciesOfType(key: String): List[(String, String)] = | |
| contentsAsJson.getOrElse(key, ujson.Arr()).arr.flatMap(_.obj.map { case (s, v) => s -> v.str }).toList | |
| JsDeps( | |
| dependenciesOfType("compileDependencies") ++ dependenciesOfType("compile-dependencies"), | |
| dependenciesOfType("compileDevDependencies") ++ dependenciesOfType("compile-devDependencies") | |
| ) | |
| case z if z.getName.endsWith(".js") && !z.getName.startsWith("scala/") => | |
| JsDeps(Nil, Nil, Map(z.getName -> readAllBytes(stream))) | |
| } | |
| .toList | |
| finally | |
| stream.close() | |
| } | |
| private def writeEntrypoint0(dst: os.Path, depNames: Iterable[String]) = { | |
| val path = dst / "entrypoint.js" | |
| os.write.over( | |
| path, | |
| s""" | |
| |module.exports = { | |
| | "require": (function(moduleName) { | |
| | return { | |
| | ${depNames.map { name => s"'$name': require('$name')" }.mkString(",\n ")} | |
| | }[moduleName] | |
| | }) | |
| |} | |
| |""".stripMargin | |
| .trim | |
| ) | |
| PathRef(path) | |
| } | |
| val webpackConfigFilename = "webpack.config.js" | |
| def writeWpConfig(params: WebpackParams, bundleFilename: String) = { | |
| val libraryOutputCfg = | |
| params.libraryName.map(n => Map("library" -> n, "libraryTarget" -> "var")).getOrElse(Map.empty) | |
| val outputCfg = | |
| libraryOutputCfg ++ Map("path" -> params.outputDirectory.toString, "filename" -> bundleFilename) | |
| os.write.over( | |
| params.outputDirectory / webpackConfigFilename, | |
| "module.exports = " + ujson.Obj( | |
| "mode" -> (if (params.opt) "production" else "development"), | |
| "devtool" -> "source-map", | |
| "entry" -> params.copiedInputFile.toString, | |
| "output" -> ujson.Obj.from(outputCfg.view.mapValues(ujson.Str)) | |
| ).render(2) + ";\n" | |
| ) | |
| } | |
| trait ScalaJSDepsModule extends ScalaJSModule { | |
| def moduleDepJsDepsTarget = | |
| T.sequence(recursiveModuleDeps.collect { case mod: ScalaJSDepsModule => mod.jsDeps }) | |
| def jsDeps: Target[JsDeps] = T { | |
| val jsDepsFromIvyDeps = | |
| resolveDeps(transitiveIvyDeps)().iterator.toList.flatMap(pathRef => jsDepsFromJar(pathRef.path.toIO)) | |
| val allJsDeps = jsDepsFromIvyDeps ++ moduleDepJsDepsTarget() | |
| allJsDeps.foldLeft(JsDeps())(_ ++ _) | |
| } | |
| } | |
| trait ScalaJSWebpackBaseModule extends ScalaJSDepsModule { | |
| def webpackVersion: Target[String] = "4.17.1" | |
| def webpackCliVersion: Target[String] = "3.1.0" | |
| def webpackDevServerVersion: Target[String] = "3.1.7" | |
| def writePackageJson = T.task { params: WebpackParams => | |
| writePkgJson(params, params.jsDeps, webpackVersion(), webpackCliVersion(), webpackDevServerVersion()) | |
| } | |
| def bundleFilename = T { | |
| "out-bundle.js" | |
| } | |
| def webpack = T.task { params: WebpackParams => | |
| val _bundleFilename = bundleFilename() | |
| if (params.inputFile != params.copiedInputFile) | |
| os.copy.over(params.inputFile, params.copiedInputFile) | |
| params.jsDeps.jsSources foreach { case (n, s) => os.write.over(params.outputDirectory / n, s) } | |
| writeWpConfig(params, _bundleFilename) | |
| writePackageJson().apply(params) | |
| val logger = T.ctx().log | |
| val npmInstall = os.proc("npm", "install").call(params.outputDirectory) | |
| logger.debug(npmInstall.out.text()) | |
| val webpackPath = params.outputDirectory / "node_modules" / "webpack" / "bin" / "webpack" | |
| val webpack = | |
| os.proc("node", webpackPath, "--bail", "--profile", "--config", webpackConfigFilename) | |
| .call(params.outputDirectory) | |
| logger.debug(webpack.out.text()) | |
| if (params.inputFile != params.copiedInputFile) | |
| os.remove(params.copiedInputFile) | |
| List( | |
| PathRef(params.outputDirectory / _bundleFilename), | |
| PathRef(params.outputDirectory / (_bundleFilename + ".map")) | |
| ) | |
| } | |
| def devWebpack: Target[Seq[PathRef]] | |
| def prodWebpack: Target[Seq[PathRef]] | |
| } | |
| trait ScalaJSWebpackApplicationModule extends ScalaJSWebpackBaseModule { | |
| override def devWebpack: Target[Seq[PathRef]] = T.persistent { | |
| webpack().apply(WebpackParams(fastOpt().path, jsDeps(), T.ctx().dest, opt = false, None)) | |
| } | |
| override def prodWebpack: Target[Seq[PathRef]] = T.persistent { | |
| webpack().apply(WebpackParams(fullOpt().path, jsDeps(), T.ctx().dest, opt = true, None)) | |
| } | |
| } | |
| trait ScalaJSWebpackLibraryModule extends ScalaJSWebpackBaseModule { | |
| private val regex = """require\("([^"]*)"\)""".r | |
| def writeEntrypoint = T.task { (src: PathRef, dest: os.Path) => | |
| val requires = | |
| os.read.lines.stream(src.path) | |
| .flatMap(line => Generator.from(regex.findAllMatchIn(line).map(_.group(1)))) | |
| .toList | |
| writeEntrypoint0(dest, requires) | |
| } | |
| def webpackLibraryName = T { | |
| "app" | |
| } | |
| override def devWebpack: Target[Seq[PathRef]] = T.persistent { | |
| val dest = T.ctx().dest | |
| val deps = jsDeps() | |
| val src = fastOpt() | |
| val entrypoint = writeEntrypoint().apply(src, dest).path | |
| webpack().apply(WebpackParams(entrypoint, deps, dest, opt = false, Some(webpackLibraryName()))) :+ src | |
| } | |
| override def prodWebpack: Target[Seq[PathRef]] = T.persistent { | |
| val dest = T.ctx().dest | |
| val deps = jsDeps() | |
| val src = fullOpt() | |
| val entrypoint = writeEntrypoint().apply(src, dest).path | |
| webpack().apply(WebpackParams(entrypoint, deps, dest, opt = true, Some(webpackLibraryName()))) :+ | |
| src | |
| } | |
| } | |
| } |
@ikamthania try wrapping it with ujson.Str
Doesn't work. Still gives quotes. I guess only option is to use a separate file or multi line string
At I didn't understand the issue. Indeed that's not json so there's no way to express it with ujson. If you have a better way (than using ujson I guess) please share (e.g. by forking and modifying the gist and linking back)
Hmm, I wonder if you could leave the main part as json then append another statement, module.exports.plugins = ..., to the string (after the ;\n)
If that's not valid to webpack then change it to work with a local var, then set module.exports to it.
Thanks for the tip. I will try it out.
Ended up using a separate file like
os.write.over(
params.outputDirectory / webpackConfigFilename,
// Generate webpack config
/* "module.exports = " + ujson
.Obj(
"mode" -> (if (params.opt) "production" else "development"),
"devtool" -> "source-map",
"entry" -> params.copiedInputFile.toString,
"output" -> ujson.Obj.from(outputCfg.mapValues(ujson.Str))
)
.render(2) + ";\n" */
// or read from separate file
os.read(os.pwd / "webpack.config.js")
)Thanks @nafg for this file. It solved a great deal of issue for me.
Hi,
How can I write something like
to webpack.config.js ?
I tried
"plugins" -> ujson.Arr("new webpack.ProvidePlugin({ adapter: 'webrtc-adapter' })"but as expected it is not working since the type is string.