I am using Sass as my CSS preprocesser, and I'm trying to have it run via the asset pipeline. I've tried implementing this sassTask as a source file task and as a web asset task, but I'm running into problems both ways.
If I run Sass as a source task (see below), it gets triggered during activator run
when a page is requested and updated files are found upon page reloads. The problem I'm running into is that the resulting CSS files are all getting dumped directly into target/web/public/main/lib
, instead of into the subdirectories reflecting the ones they are getting built into under the resources-managed
directory. I can't figure out how to make this happen.
Instead, I tried implementing Sass compilation as a web asset task (see below). Working this way, as far as I can tell, resources-managed
does not come into play, and so I compile my files directly into target/web/public/main/lib
. I'm sure I'm not doing this dynamically enough, but I don't know how to do it any better. But the biggest problem here is that the pipeline apparently does not run when working through activator run
. I can get it to run using activator stage
, but I really need this to work in the regular development workflow so that I can change style files as the dev server is running, same as with Scala files.
I have tried combing through these forums, through the sbt-web docs, and through some of the existing plugins, but I am finding this process to be highly frustrating, due to the complexity of SBT and the opaqueness of what is actually happening in the build process.
Sass compilation as a source file task:
lazy val sassTask = TaskKey[Seq[java.io.File]]("sassTask", "Compiles Sass files")
sassTask := {
import sys.process._
val x = (WebKeys.nodeModules in Assets).value
val sourceDir = (sourceDirectory in Assets).value
val targetDir = (resourceManaged in Assets).value
Seq("sass", "-I", "target/web/web-modules/main/webjars/lib/susy/sass", "--update", s"$sourceDir:$targetDir").!
val sources = sourceDir ** "*.scss"
val mappings = sources pair relativeTo(sourceDir)
val renamed = mappings map { case (file, path) => file -> path.replaceAll("scss", "css") }
val copies = renamed map { case (file, path) => file -> targetDir / path }
copies map (_._2)
}
sourceGenerators in Assets <+= sassTask
Sass compilation as web asset task:
lazy val sassTask = taskKey[Pipeline.Stage]("Compiles Sass files")
sassTask := {
(mappings: Seq[PathMapping]) =>
import sys.process._
val sourceDir = (sourceDirectory in Assets).value
val targetDir = target.value / "web" / "public" / "main"
val libDir = (target.value / "web" / "web-modules" / "main" / "webjars" / "lib" / "susy" / "sass").toString
Seq("sass", "-I", libDir, "--update", s"$sourceDir:$targetDir").!
val sources = sourceDir ** "*.scss"
val mappings = sources pair relativeTo(sourceDir)
val renamed = mappings map { case (file, path) => file -> path.replaceAll("scss", "css") }
renamed
}
pipelineStages := Seq(sassTask)
I think that according to the documentation related to the Asset Pipeline, a Source File task is a way to go:
Examples of source file tasks as plugins are CoffeeScript, LESS and JSHint. Some of these take a source file and produce a target web asset e.g. CoffeeScript produces JS files. Plugins in this category are mutually exclusive to each other in terms of their function i.e. only one CoffeeScript plugin will take CoffeeScript sources and produce target JS files. In summary, source file plugins produce web assets.
I think what you try to achieve falls into this category.
build.sbt
val sassTask = taskKey[Seq[File]]("Compiles Sass files")
val sassOutputDir = settingKey[File]("Output directory for Sass generated files")
sassOutputDir := target.value / "web" / "sass" / "main"
resourceDirectories in Assets += sassOutputDir.value
sassTask := {
val sourceDir = (sourceDirectory in Assets).value
val outputDir = sassOutputDir.value
val sourceFiles = (sourceDir ** "*.scss").get
Seq("sass", "--update", s"$sourceDir:$outputDir").!
(outputDir ** "*.css").get
}
sourceGenerators in Assets += sassTask.taskValue
Assuming you have sass file in a app/assets/<whatever>
directory, and that you want to create css files in web/public/main/<whatever>
directory, this is what you could do.
Create a task, which will read in files in the app/assets/<whatever>
directory and subdirectories, and output them to our defined sassOutputDir
.
val sassTask = taskKey[Seq[File]]("Compiles Sass files")
val sassOutputDir = settingKey[File]("Output directory for Sass generated files")
sassOutputDir := target.value / "web" / "sass" / "main"
resourceDirectories in Assets += sassOutputDir.value
sassTask := {
val sourceDir = (sourceDirectory in Assets).value
val outputDir = sassOutputDir.value
val sourceFiles = (sourceDir ** "*.scss").get
Seq("sass", "--update", s"$sourceDir:$outputDir").!
(outputDir ** "*.css").get
}
This is not enough though. If you want to keep the directory structure you have to add your sassOutputDir
to the resourceDirectories in Assets
. This is because mappings in sbt-web are declared like this:
mappings := {
val files = (sources.value ++ resources.value ++ webModules.value) ---
(sourceDirectories.value ++ resourceDirectories.value ++ webModuleDirectories.value)
files pair relativeTo(sourceDirectories.value ++ resourceDirectories.value ++ webModuleDirectories.value) | flat
}
which means that all unmapped files are mapped using an alternative flat
strategy. However the fix for it is simple, just add this to your build.sbt
resourceDirectories in Assets += sassOutputDir.value
This will make sure the directory structure is preserved.