Skip to content

Commit

Permalink
Added resource bundle generation, fixed #2
Browse files Browse the repository at this point in the history
  • Loading branch information
viktor-podzigun committed Jul 6, 2020
1 parent 97d608f commit 56e87ee
Show file tree
Hide file tree
Showing 11 changed files with 309 additions and 111 deletions.
28 changes: 24 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,41 @@ It provides the following settings:
val scommonsResourcesFileFilter: SettingKey[FileFilter] = settingKey[FileFilter](
"File filter of resources files, that should be automatically copied/extracted to the webpack directory"
)

val scommonsResourcesArtifacts: SettingKey[Seq[ModuleID]] = settingKey[Seq[ModuleID]](
"List of artifacts (JARs) with resources, that should be automatically extracted to the webpack directory"
)

val scommonsBundlesFileFilter: SettingKey[FileFilter] = settingKey[FileFilter](
"File filter of bundles files, that should be automatically generated in the webpack directory"
)
```

With default values:
```scala
scommonsResourcesFileFilter :=
"*.css" ||
"*.js" ||
"*.json" ||
"*.css" ||
"*.ico" ||
"*.png" ||
"*.jpg" ||
"*.jpeg" ||
"*.gif"
"*.gif" ||
"*.svg" ||
"*.ttf" ||
"*.mp3" ||
"*.wav" ||
"*.mp4" ||
"*.mov" ||
"*.html" ||
"*.pdf"

scommonsResourcesArtifacts := Seq(
"org.scommons.react" % "scommons-react-core" % "*",
"org.scommons.client" % "scommons-client-ui" % "*"
)

scommonsBundlesFileFilter := NothingFilter
```

You can extend/override the default values:
Expand All @@ -56,7 +72,11 @@ settings(

scommonsResourcesArtifacts ++= Seq(
"your.org" % "your-dependency" % "*"
)
),

// will generate bundle.json file(s) with migrations for SQLite
// see `scommons-websql-migrations` module
scommonsBundlesFileFilter := "*.sql"
)
```

Expand Down
37 changes: 35 additions & 2 deletions src/main/scala/scommons/sbtplugin/ScommonsPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package scommons.sbtplugin
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
import sbt.Keys._
import sbt._
import scommons.sbtplugin.util.ResourcesUtils
import scommons.sbtplugin.util.{BundlesUtils, ResourcesUtils}

import scalajsbundler.sbtplugin.ScalaJSBundlerPlugin

Expand All @@ -20,24 +20,41 @@ object ScommonsPlugin extends AutoPlugin {
val scommonsResourcesArtifacts: SettingKey[Seq[ModuleID]] = settingKey[Seq[ModuleID]](
"List of artifacts (JARs) with resources, that should be automatically extracted to the webpack directory"
)

val scommonsBundlesFileFilter: SettingKey[FileFilter] = settingKey[FileFilter](
"File filter of bundles files, that should be automatically generated in the webpack directory"
)
}

import autoImport._

override lazy val projectSettings = Seq(

scommonsResourcesFileFilter :=
"*.js" ||
"*.json" ||
"*.css" ||
"*.ico" ||
"*.png" ||
"*.jpg" ||
"*.jpeg" ||
"*.gif",
"*.gif" ||
"*.svg" ||
"*.ttf" ||
"*.mp3" ||
"*.wav" ||
"*.mp4" ||
"*.mov" ||
"*.html" ||
"*.pdf",

scommonsResourcesArtifacts := Seq(
"org.scommons.react" % "scommons-react-core" % "*",
"org.scommons.client" % "scommons-client-ui" % "*"
),

scommonsBundlesFileFilter := NothingFilter,

sjsStageSettings(fastOptJS, Compile),
sjsStageSettings(fullOptJS, Compile),
sjsStageSettings(fastOptJS, Test),
Expand All @@ -64,6 +81,12 @@ object ScommonsPlugin extends AutoPlugin {
scommonsResourcesFileFilter.value,
scommonsResourcesArtifacts.value
)
genWebpackBundles(
streams.value.log,
(crossTarget in (config, sjsStage)).value,
(fullClasspath in config).value,
scommonsBundlesFileFilter.value
)
(sjsStage in config).value
}
}
Expand All @@ -77,6 +100,16 @@ object ScommonsPlugin extends AutoPlugin {
ResourcesUtils.extractFromClasspath(msg => log.info(msg), webpackDir, cp, fileFilter, includeArtifacts)
}

private def genWebpackBundles(log: Logger,
webpackDir: File,
cp: Seq[Attributed[File]],
fileFilter: FileFilter): Unit = {

if (fileFilter != NothingFilter) {
BundlesUtils.genFromClasspath(msg => log.info(msg), webpackDir, cp, fileFilter)
}
}

private def doClean(clean: Seq[File], preserve: Seq[File]): Unit =
IO.withTemporaryDirectory { temp =>
val (dirs, files) = preserve.filter(_.exists).flatMap(_.allPaths.get).partition(_.isDirectory)
Expand Down
57 changes: 57 additions & 0 deletions src/main/scala/scommons/sbtplugin/util/BundlesUtils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package scommons.sbtplugin.util

import play.api.libs.json.Json
import sbt._

object BundlesUtils {

def genFromClasspath(logger: String => Unit,
targetDir: File,
cp: Seq[Attributed[File]],
fileFilter: FileFilter): Unit = {

for (entry <- cp) {
val cpEntry = entry.data
if (cpEntry.exists && cpEntry.isDirectory) {
var bundles = Map.empty[File, (Seq[File], Long)]
for ((file, relPath) <- Path.selectSubpaths(cpEntry, fileFilter)) {
val targetFile = new File(targetDir, relPath)
val bundleDir = targetFile.getParentFile
val (bundleFiles, lastModified) = bundles.getOrElse(bundleDir, (Nil, 0L))
bundles = bundles.updated(bundleDir, (bundleFiles :+ file,
if (lastModified < file.lastModified()) file.lastModified()
else lastModified
))
}

var generated = Seq.empty[File]
for ((bundleDir, (bundleFiles, lastModified)) <- bundles) {
val bundle = new File(bundleDir, "bundle.json")
if (!bundle.exists() || bundle.lastModified() < lastModified) {
generated = generated :+ bundle

val content = bundleFiles.map { file =>
Json.obj(
"file" -> file.getName,
"content" -> IO.read(file)
)
}

IO.write(bundle, Json.prettyPrint(Json.toJson(content)))
bundle.setLastModified(lastModified)
}
}

val total = bundles.size
if (generated.nonEmpty) {
logger(s"Generated ${generated.size} bundle files (out of $total)" +
s"\n\t${generated.mkString("\n\t")}")
}
else if (total > 0) {
logger(s"Nothing to generate, all $total bundle files are up to date" +
s"\n\t${bundles.keys.map(new File(_, "bundle.json")).mkString("\n\t")}")
}
}
}
}
}
1 change: 1 addition & 0 deletions src/sbt-test/sbt-scommons-plugin/simple/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lazy val client = (project in file("client"))
scommonsResourcesArtifacts ++= Seq(
"com.googlecode.web-commons" % "web-common-client" % "*"
),
scommonsBundlesFileFilter := "*.sql",

//scala.js specific settings
//scalaJSModuleKind := ModuleKind.CommonJSModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

CREATE TABLE test3;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

CREATE TABLE test;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

CREATE TABLE test2;
8 changes: 6 additions & 2 deletions src/sbt-test/sbt-scommons-plugin/simple/test
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
# check if resource files were copied and extracted in Compile
# check if resource/bundle files were copied/extracted/generated in Compile
> fullOptJS
$ absent client/target/scala-2.12/scalajs-bundler/main/scommons/sbtplugin/test/test.jpeg
$ exists client/target/scala-2.12/scalajs-bundler/main/scommons/sbtplugin/test/test.png
$ exists client/target/scala-2.12/scalajs-bundler/main/scommons/sbtplugin/test/test.css
$ exists client/target/scala-2.12/scalajs-bundler/main/scommons/sbtplugin/test/bundle.json
$ exists client/target/scala-2.12/scalajs-bundler/main/scommons/sbtplugin/test/nested/test.css
$ exists client/target/scala-2.12/scalajs-bundler/main/scommons/sbtplugin/test/nested/bundle.json
$ exists client/target/scala-2.12/scalajs-bundler/main/com/googlecode/common/client/ui/icons/dialog-error.png
$ exists client/target/scala-2.12/scalajs-bundler/main/com/googlecode/common/client/ui/TablePanel.css

# check if resource files were copied and extracted in Test
# check if resource/bundle files were copied/extracted/generated in Test
> test:fastOptJS
$ absent client/target/scala-2.12/scalajs-bundler/test/scommons/sbtplugin/test/test.jpeg
$ exists client/target/scala-2.12/scalajs-bundler/test/scommons/sbtplugin/test/test.png
$ exists client/target/scala-2.12/scalajs-bundler/test/scommons/sbtplugin/test/test.css
$ exists client/target/scala-2.12/scalajs-bundler/test/scommons/sbtplugin/test/bundle.json
$ exists client/target/scala-2.12/scalajs-bundler/test/scommons/sbtplugin/test/nested/test.css
$ exists client/target/scala-2.12/scalajs-bundler/test/scommons/sbtplugin/test/nested/bundle.json
$ exists client/target/scala-2.12/scalajs-bundler/test/com/googlecode/common/client/ui/icons/dialog-error.png
$ exists client/target/scala-2.12/scalajs-bundler/test/com/googlecode/common/client/ui/TablePanel.css

Expand Down
107 changes: 107 additions & 0 deletions src/test/scala/scommons/sbtplugin/util/BaseUtilsSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package scommons.sbtplugin.util

import java.io._
import java.util.zip.{ZipEntry, ZipOutputStream}

import org.scalamock.scalatest.MockFactory
import org.scalatest._
import sbt._

abstract class BaseUtilsSpec extends FlatSpec
with Matchers
with BeforeAndAfterEach
with MockFactory {

protected var tmpSourceDir: Option[File] = None
protected var tmpTargetDir: Option[File] = None

override protected def beforeEach(): Unit = {
tmpSourceDir = Some(createTmpDir(tmpSourceDir, "scommons.sbtplugin.sourceDir."))
tmpTargetDir = Some(createTmpDir(tmpTargetDir, "scommons.sbtplugin.targetDir."))
}

override protected def afterEach(): Unit = {
tmpSourceDir = {
deleteDirRecursively(tmpSourceDir.get)
tmpSourceDir.get.exists() shouldBe false
None
}
tmpTargetDir = {
deleteDirRecursively(tmpTargetDir.get)
tmpTargetDir.get.exists() shouldBe false
None
}
}

def assertFile(dir: File, relPathName: String, contents: String, exists: Boolean = true): Assertion = {
val file = new File(s"${dir.getPath}${File.separator}$relPathName")
file.exists() shouldBe exists

if (exists) {
IO.read(file) shouldBe contents
}

succeed
}

def writeFile(dir: File, relPathName: String, contents: String): (String, String) = {
val file = new File(s"${dir.getPath}/$relPathName")
IO.write(file, contents)
(relPathName, contents)
}

def writeZipFile(file: File, relPathNamesWithContents: List[(String, String)]): List[(String, String)] = {
val stream = new ZipOutputStream(new FileOutputStream(file))
try {
for ((relPathName, contents) <- relPathNamesWithContents) {
stream.putNextEntry(new ZipEntry(relPathName))
stream.write(contents.getBytes("UTF-8"))
stream.closeEntry()
}

stream.finish()
relPathNamesWithContents
} finally {
stream.close()
}
}

private def createTmpDir(currTmpDir: Option[File], prefix: String): File = {
if (currTmpDir.isEmpty) {
val tmpFile = File.createTempFile(prefix, "")
deleteFile(tmpFile)
if (!tmpFile.mkdirs()) {
throw new IllegalStateException(s"Cannot create directory(s): $tmpFile")
}

tmpFile
}
else {
throw new IllegalStateException("Temp directory already exists, probably it was not cleaned-up properly")
}
}

private def deleteFile(file: File): Unit = {
if (!file.delete()) {
throw new IllegalStateException(s"Cannot delete file/directory: $file")
}
}

private def deleteDirRecursively(dir: File): Unit = {
val filter = new FilenameFilter {
override def accept(dir: File, name: String): Boolean = name != "." && name != ".."
}

val (dirs, files) = dir.listFiles(filter).partition(_.isDirectory)

for (dir <- dirs) {
deleteDirRecursively(dir)
}

for (file <- files) {
deleteFile(file)
}

deleteFile(dir)
}
}
Loading

0 comments on commit 56e87ee

Please sign in to comment.