An embedded dart sass compiler and watch task for Clojure.
Implemented as a wrapper around the dart-sass-java embedded host, and the Axle watcher. Aims to be a drop-in replacement for the deprecated sass4clj.
While it's true that you can shell out to
dart-sass
that process will not have access to resources on the java
classpath. If you depend on webjars, like org.webjars/bootstrap
, or
your own jar bundles, then you are out of luck. With this embedded
compiler you have access to anything on the classpath, as well as
anything on disk.
First, you will probably need some version of Dart Sass installed.
With that prerequisite out of the way, add the following dependency in
your deps.edn
:
io.zalky/dart-sass-clj {:mvn/version "0.2.1"}
You should then be able to configure an alias that looks something like:
{:sass {:extra-deps {io.zalky/dart-sass-clj {:mvn/version "0.2.1"}}
:main-opts ["-m" "dart-sass-clj.core"
"--source-dirs" "[\"src/scss/path\" \"other/scss\"]"
"--target-dir" "resources/assets/"
"--output-style" ":expanded"
"--source-maps"
"--watch"]}}
Here we have configured the following:
-
--source-dirs
is a list of paths thatdart-sass-clj
will search for sass main files. If--source-dirs
is omitted, then each directory on the classpath is searched. -
--target-dir
is where all emitted files (css, source-maps etc...) will be written. -
--output-style
specifies whether CSS will be compiled in:expanded
or:compressed
mode. Default is:compressed
. -
--source-maps
specifies that source maps and their corresponding scss source will be emitted along with compiled css. Default is false. -
--watch
will start a watch task and recompile all main files if any sass files in--source-dirs
change. Unfortunately, because there is currently no dependency tracking, there is no way to know specifically which main file should be recompiled, so they all are.
You can display the full list of options with:
clojure -M:sass -m dart-sass-clj.core --help
Any file ending with .scss
or .sass
is considered sass source.
The compiler recursively searches each source path for sass main files, which in the semantics of dart-sass is any sass file that does not start with an underscore. Each main file is then treated as a root node, which together with its import tree is compiled into a single css file. These css files are then emitted to the target directory, preserving any sub directory structure relative to the original source path.
Given the following source tree, where main.scss
imports
_dependency.scss
:
source-dir/subdir/main.scss
source-dir/subdir/sub/_dependency.scss
Then the compiler would emit:
target-dir/subdir/main.css
If you have source maps turned on, the compiler will additionally emit a source map file, as well as its associated source tree:
target-dir/subdir/main.css
target-dir/subdir/main.css.map
target-dir/subdir/scss/subdir/main.scss
target-dir/subdir/scss/subdir/sub/_dependency.scss
dart-sass-clj
attempts to preserve the import semantics of both
dart-sass and
sass4clj
.
First note that import statements can choose to omit the file
extension, or the leading underscore. So @import "module";
will
match @import "_module";
will match @import "_module.scss";
, but not necessarily the reverse.
Then for a file at {path}/{name}
containing @import "{module}";
,
the following will be searched to resolve the import:
- Local file at path
{path}/{module}.scss
: - Local file in any source-dir
{source-dir}/{module}.scss
- Classpath resource
(io/resource "{module}.scss")
As a specific example, given source-dir/subdir/main.scss
containing @import "some/other/dependency";
:
source-dir/subdir/some/other/_dependency.scss
source-dir/some/other/_dependency.scss
classpath-folder/some/other/_dependency.scss
Where a file with no underscore, or a .sass
extension would also be
matched.
Finally, if it does not resolve to any of these, the compiler will check webjars:
-
@import "{package}/{module}";
will resolve to:META-INF/resources/webjars/{package}/{version}/{module}
Ex:
@import "bootstrap/scss/bootstrap";
will import:META-INF/resources/webjars/bootstrap/5.2.2/scss/bootstrap.scss
If you are using Runway, you can
configure your alias via :exec-args
instead:
{:sass {:extra-deps {io.zalky/runway {:mvn/version "0.2.2"}
io.zalky/dart-sass-clj {:mvn/version "0.2.1"}}
:exec-fn runway.core/exec
:exec-args {dart-sass-clj.core/compile {:source-dirs ["src/scss/"]
:target-dir "resources/assets/"
:output-style :expanded
:watch true
:source-maps true}}}}
See the Runway README.md for more details on usage.
Thanks to Lars Grefer for putting together the excellent
dart-sass-java
embedded host!
Dart-sass-clj is distributed under the terms of the Apache License 2.0.