├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── README.md ├── appveyor.yml ├── boot.properties ├── build.boot ├── deps.edn ├── src ├── deraen │ └── boot_sass.clj ├── leiningen │ └── sass4clj.clj └── sass4clj │ ├── api.clj │ ├── component.clj │ ├── core.clj │ ├── integrant.clj │ ├── main.clj │ ├── util.clj │ ├── watcher.clj │ └── webjars.clj ├── test-resources ├── _icons.scss ├── bar.css ├── foo.scss ├── material-icons-test.scss └── xyz.sass ├── test.sh └── test └── sass4clj ├── api_test.clj ├── core_test.clj └── webjars_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | .cpcache/ 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: java 3 | script: ./test.sh 4 | install: 5 | - mkdir -p ~/bin 6 | - export PATH=~/bin:$PATH 7 | - curl -L https://github.com/boot-clj/boot-bin/releases/download/latest/boot.sh -o ~/bin/boot 8 | - chmod +x ~/bin/boot 9 | jdk: 10 | - openjdk8 11 | - openjdk11 12 | cache: 13 | directories: 14 | - $HOME/.m2 15 | - $HOME/.boot/cache 16 | - $HOME/bin 17 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.6.0 (2024-04-10) 2 | 3 | **[compare](https://github.com/Deraen/sass4clj/compare/0.5.5...0.6.0)** 4 | 5 | - Update to latest jsass 6 | - Supports Apple Silicon 7 | 8 | ## 0.5.5 (2021-04-12) 9 | 10 | **[compare](https://github.com/Deraen/sass4clj/compare/0.5.4...0.5.5)** 11 | 12 | - Fix @import load order for classpath relative paths being shadowed. 13 | - Files in :source-paths could be selected before correct file from 14 | relative path in the classpath. E.g. local `_icons.scss` before one in 15 | material-design-iconic-font. 16 | 17 | ## 0.5.4 (2021-02-19) 18 | 19 | **[compare](https://github.com/Deraen/sass4clj/compare/0.5.3...0.5.4)** 20 | 21 | - Fix lein plugin when `:source-paths` is merged from multiple lein profiles 22 | 23 | ## 0.5.3 (2021-01-21) 24 | 25 | **[compare](https://github.com/Deraen/sass4clj/compare/0.5.2...0.5.3)** 26 | 27 | - Read imports from other source-paths before the classpath 28 | - Print resolved imports on debug log 29 | 30 | ## 0.5.2 (2021-01-07) 31 | 32 | **[compare](https://github.com/Deraen/sass4clj/compare/0.5.1...0.5.2)** 33 | 34 | - Set exit code in `-main` and Lein plugin if errors occured 35 | 36 | ## 0.5.1 (2020-02-07) 37 | 38 | **[compare](https://github.com/Deraen/sass4clj/compare/0.5.0...0.5.1)** 39 | 40 | - Fix `:source-paths` not being passed to compile file, so files 41 | from another source-paths folder couldn't be imported. ([#31](https://github.com/Deraen/sass4clj/pull/31/)) 42 | 43 | ## 0.5.0 (2019-10-03) 44 | 45 | **[compare](https://github.com/Deraen/sass4clj/compare/0.4.1...0.5.0)** 46 | 47 | - Support mixed Scss & Sass projects (include `.sass` files from `.scss` files) ([#22](https://github.com/Deraen/sass4clj/issues/22)) 48 | 49 | ## 0.4.1 (2019-08-28) 50 | 51 | **[compare](https://github.com/Deraen/sass4clj/compare/0.4.0...0.4.1)** 52 | 53 | - Fix the Integrant namespace ([#30](https://github.com/Deraen/sass4clj/pull/30)) 54 | 55 | ## 0.4.0 (2019-08-20) 56 | 57 | **[compare](https://github.com/Deraen/sass4clj/compare/0.3.1...0.4.0)** 58 | 59 | - **Breaking**: 60 | - Requires Clojure 1.9 (for spec) 61 | - Sass4clj now contains main namespace for `clj` use 62 | - Sass4clj new has `sass4clj.api` namespace with easy to use `start` and `stop` functions 63 | - Add [Integrant](https://github.com/weavejester/integrant) namespace `sass4clj.integrant` namespace 64 | - Add [Component](https://github.com/stuartsierra/component) namespace `sass4clj.component` namespace 65 | - Use [Hawk](https://github.com/wkf/hawk/) for watching for file changes, this should work better on OS X 66 | - Support Webpack style import paths, which support starting the path with `~` 67 | when referring to Node packages. 68 | - Add `inputs` option which can be used to select main files 69 | 70 | ## 0.3.1 (8.3.2017) 71 | 72 | **[compare](https://github.com/Deraen/sass4clj/compare/0.3.0...0.3.1)** 73 | 74 | - Fixed a bad macro in Leiningen plugin which broke less4clj with Clojure 1.9-alpha14 75 | 76 | ## 0.3.0 (18.10.2016) 77 | 78 | **[compare](https://github.com/Deraen/sass4clj/compare/0.2.1...0.3.0)** 79 | 80 | - Run tests on Windows CI 81 | - Update to latest jsass ([#6](https://github.com/Deraen/sass4clj/pull/6)) 82 | - Supports source maps ([#1](https://github.com/Deraen/sass4clj/pull/1)) 83 | - Support raw css imports ([#14](https://github.com/Deraen/sass4clj/pull/14)) 84 | 85 | ## 0.2.1 (22.2.2016) 86 | 87 | **[compare](https://github.com/Deraen/sass4clj/compare/0.2.0...0.2.1)** 88 | 89 | - Fixed the documentation about main files 90 | - Main files don't need to end with `.main.ext` 91 | - Main files are the files not starting with `_` 92 | - Fix URI construction on Windows ([#7](https://github.com/Deraen/sass4clj/pull/7)) 93 | 94 | ## 0.2.0 (25.12.2015) 95 | 96 | - Synchronized versions between all packages 97 | - Boot and Lein packages are now maintained in sass4clj repository 98 | 99 | ## 0.1.1 (27.9.2015) 100 | 101 | - Fixed local file imports 102 | 103 | ## 0.1.0 (26.9.2015) 104 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Contributions are welcome. 4 | 5 | Please file bug reports and feature requests to https://github.com/Deraen/sass4clj/issues. 6 | 7 | Please mention which task (Leiningen or Boot) your are using in the issue. 8 | 9 | ## Making changes 10 | 11 | * Fork the repository on Github 12 | * Create a topic branch from where you want to base your work (usually the master branch) 13 | * Check the formatting rules from existing code (no trailing whitepace, mostly default indentation) 14 | * Ensure any new code is well-tested, and if possible, any issue fixed is covered by one or more new tests 15 | * Verify that all tests pass using ```boot run-tests``` 16 | * Push your code to your fork of the repository 17 | * Make a Pull Request 18 | 19 | ## Commit messages 20 | 21 | 1. Separate subject from body with a blank line 22 | 2. Limit the subject line to 50 characters 23 | 3. Capitalize the subject line 24 | 4. Do not end the subject line with a period 25 | 5. Use the imperative mood in the subject line 26 | - "Add x", "Fix y", "Support z", "Remove x" 27 | 6. Wrap the body at 72 characters 28 | 7. Use the body to explain what and why vs. how 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sass4clj 2 | [![Clojars Project](https://img.shields.io/clojars/v/deraen/sass4clj.svg)](https://clojars.org/deraen/sass4clj) 3 | [![Build Status](https://travis-ci.org/Deraen/sass4clj.svg?branch=master)](https://travis-ci.org/Deraen/sass4clj) 4 | [![AppVeyor](https://img.shields.io/appveyor/ci/deraen/sass4clj.svg?maxAge=2592000&label=windows)](https://ci.appveyor.com/project/Deraen/sass4clj) 5 | 6 | Clojure wrapper for [jsass](https://github.com/bit3/jsass/) JNA wrapper for Libsass. 7 | This repository also contains [Boot](http://boot-clj.com/) and [Leiningen](http://leiningen.org/) tasks. 8 | 9 | For parallel Less library check [less4clj](https://github.com/Deraen/less4clj) 10 | 11 | ## ATTENTION: [libsass](https://sass-lang.com/blog/libsass-is-deprecated) (the C library) and the JNA wrapper library [jsass](https://github.com/bit3/jsass/) are deprecated. Consider using [Dart Sass](https://sass-lang.com/dart-sass) if you do not need to read SCSS files from the Java classpath. [You can integrate Dart Sass process with Shadow CLJS.](https://gist.github.com/Deraen/695c94848d3ee05990239d403f7fe733) 12 | 13 | Both sass4clj still works and will receive bug fixes, but the difference 14 | between libsass and dart-sass will continue growing. 15 | 16 | Some ideas if you need to read files from the classpath or jar files: 17 | 18 | - It might be possible to extend dart-sass through [Importer API](https://pub.dev/documentation/sass/latest/sass/Importer-class.html) 19 | - Extract files from the jar files in a script before calling dart-sass compile (for example, using `clj` and `unzip`) 20 | 21 | ## Features 22 | 23 | - Jsass features 24 | - Requires Java 1.8 25 | - Linux & Windows builds are automatically tested 26 | - (Doesn't work on Alpine based Docker images) 27 | - Load imports directly from Java classpath (e.g. Webjars) 28 | - Add dependency `[org.webjars.bower/bootstrap "4.0.0-alpha"]` to use [Bootstrap](http://getbootstrap.com/) 29 | - Assumes that files starting with `_` are [partial files](http://sass-lang.com/guide) and should not be compiled into CSS files. 30 | - Non feature: compilation warnings are reported by libsass/jsass directly to stdout, 31 | and aren't accessible from sass4clj API. 32 | 33 | ## Boot [![Clojars Project](https://img.shields.io/clojars/v/deraen/boot-sass.svg)](https://clojars.org/deraen/boot-sass) 34 | 35 | * Provides the `sass` task (`deraen.boot-sass/sass`) 36 | * Select main files using `inputs` option 37 | * or, for each `.sass` or `.scss` file not starting with `_` in the fileset creates equivalent `.css` file. 38 | * Check `boot sass --help` for task options. 39 | 40 | ## Leiningen [![Clojars Project](https://img.shields.io/clojars/v/deraen/lein-sass4clj.svg)](https://clojars.org/deraen/lein-sass4clj) 41 | 42 | * Provides the `sass4clj` task 43 | * Select main files using `inputs` option 44 | * or, for each `.sass` or `.scss` file not starting with `_` in source-dirs creates equivalent `.css` file. 45 | * Check `lein help sass4clj` for options. 46 | 47 | ## Clj 48 | 49 | Test in the repository: 50 | 51 | `clj -m sass4clj.main --source-paths test-resources` 52 | 53 | Check `clj -m sass4clj.main --help` for options. 54 | 55 | ## Import load order 56 | 57 | Loading order for `@import "{name}";` on file at `{path}` 58 | 59 | 1. Local file at `{path}/{name}.sass` or `{path}/{name}.scss` 60 | 2. Local files on other source-paths, `{source-path}/{name}.ext` 61 | 2. Classpath resource `(io/resource "{name}.ext")` 62 | 3. Classpath resource `(io/resource "{path}/{name}.ext")` 63 | 4. Webjar asset 64 | - Resource `META-INF/resources/webjars/{package}/{version}/{path}` can be referred using `{package}/{path}` 65 | - For example `@import "bootstrap/scss/bootstrap.scss";` will import `META-INF/resources/webjars/bootstrap/4.0.0-alpha/scss/bootstrap.scss` 66 | 67 | ## FAQ 68 | 69 | ### Shadow-cljs integration 70 | 71 | If you want to combine CLJS compilation with Shadow CLJS and Sass compilation, 72 | you can create your function which starts both watches and run this using 73 | shadow-cljs [clj-run](https://shadow-cljs.github.io/docs/UsersGuide.html#_calling_watch_via_clj_run) 74 | task. 75 | 76 | ```clj 77 | (ns app.shadow 78 | (:require [shadow.cljs.devtools.api :as shadow] 79 | [sass4clj.api :as sass] 80 | [clojure.edn :as edn])) 81 | 82 | (defn watch 83 | {:shadow/requires-server true} 84 | [& _args] 85 | ;; Sass compilation probably starts faster. 86 | ;; Both watches keep running until ctrl-c. 87 | (sass/start (-> (edn/read-string (slurp "sass4clj.edn")) 88 | (assoc :target-path "target/dev/public/css"))) 89 | (shadow/watch :app)) 90 | ``` 91 | 92 | > The configuration file, sass4clj.edn, is also supported by sass4clj.main 93 | > namespace. You could use that to compile CSS for your production build 94 | > instead of the leiningen plugin. 95 | 96 | And start the watch with (or with whatever tool you are using): 97 | 98 | ``` 99 | lein run -m shadow.cljs.devtools.cli clj-run app.shadow/watch 100 | ``` 101 | 102 | You need to ensure that the classpath used by shadow-cljs contains `deraen/sass4clj` 103 | and any packages where you are importing sass files from. 104 | 105 | ### Log configuration 106 | 107 | If you don't have any slf4j implementations, you will see a warning: 108 | 109 | ``` 110 | SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". 111 | SLF4J: Defaulting to no-operation (NOP) logger implementation 112 | SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. 113 | ``` 114 | 115 | To disable this, add a no operation logger to your project. As this is only required 116 | on the build phase, you can use `:scope "test"` so that the dependency is not 117 | transitive and is not included in Uberjar. Alternatively, you can add this 118 | dependency to your Leiningen dev profile. 119 | 120 | ``` 121 | [org.slf4j/slf4j-nop "1.7.13" :scope "test"] 122 | ``` 123 | 124 | ## License 125 | 126 | Copyright © 2014-2021 Juho Teperi 127 | 128 | Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version. 129 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | build: off 3 | install: 4 | - cmd: 'mkdir "C:\Users\appveyor\bin" || cmd /c "exit /b 0"' 5 | - cmd: SET PATH=C:\Users\appveyor\bin;%PATH% 6 | - cmd: curl -L https://github.com/boot-clj/boot-bin/releases/download/latest/boot.exe -o C:\Users\appveyor\bin\boot.exe 7 | cache: 8 | - C:\Users\appveyor\.m2 9 | - C:\Users\appveyor\.boot\cache 10 | - C:\Users\appveyor\bin 11 | test_script: 12 | - boot test 13 | -------------------------------------------------------------------------------- /boot.properties: -------------------------------------------------------------------------------- 1 | #http://boot-clj.com 2 | #Tue Aug 23 11:25:38 EEST 2016 3 | BOOT_CLOJURE_NAME=org.clojure/clojure 4 | BOOT_CLOJURE_VERSION=1.10.1 5 | BOOT_VERSION=2.8.3 6 | -------------------------------------------------------------------------------- /build.boot: -------------------------------------------------------------------------------- 1 | (def +version+ "0.6.0") 2 | 3 | (set-env! 4 | :resource-paths #{"src"} 5 | :source-paths #{"test" "test-resources"} 6 | :dependencies '[[org.clojure/clojure "1.10.1" :scope "provided"] 7 | [metosin/bat-test "0.4.3" :scope "test"] 8 | 9 | [io.bit3/jsass "5.11.0"] 10 | [cheshire "5.9.0"] 11 | 12 | [org.webjars/webjars-locator "0.37"] 13 | [hawk "0.2.11"] 14 | [org.clojure/tools.cli "0.4.2"] 15 | 16 | [com.stuartsierra/component "0.4.0" :scope "test"] 17 | [suspendable "0.1.1" :scope "test"] 18 | [integrant "0.7.0" :scope "test"] 19 | 20 | ;; Webjars-locator uses logging 21 | [org.slf4j/slf4j-nop "1.7.29" :scope "test"] 22 | 23 | ;; For testing the webjars asset locator implementation 24 | [org.webjars.bower/bootstrap "4.3.1" :scope "test"] 25 | [org.webjars.bower/material-design-iconic-font "2.2.0" :scope "test"] 26 | [org.webjars.bower/material-design-lite "1.3.0" :scope "test"]]) 27 | 28 | (require '[metosin.bat-test :refer [bat-test]]) 29 | 30 | (task-options! 31 | pom {:version +version+ 32 | :url "https://github.com/deraen/sass4clj" 33 | :scm {:url "https://github.com/deraen/sass4clj"} 34 | :license {"Eclipse Public License" "http://www.eclipse.org/legal/epl-v10.html"}}) 35 | 36 | (defn with-files 37 | "Runs middleware with filtered fileset and merges the result back into complete fileset." 38 | [p middleware] 39 | (fn [next-handler] 40 | (fn [fileset] 41 | (let [merge-fileset-handler (fn [fileset'] 42 | (next-handler (commit! (assoc fileset :tree (merge (:tree fileset) (:tree fileset')))))) 43 | handler (middleware merge-fileset-handler) 44 | fileset (assoc fileset :tree (reduce-kv 45 | (fn [tree path x] 46 | (if (p x) 47 | (assoc tree path x) 48 | tree)) 49 | (empty (:tree fileset)) 50 | (:tree fileset)))] 51 | (handler fileset))))) 52 | 53 | (deftask write-version-file 54 | [n namespace NAMESPACE sym "Namespace"] 55 | (let [d (tmp-dir!)] 56 | (fn [next-handler] 57 | (fn [fileset] 58 | (let [f (clojure.java.io/file d (-> (name namespace) 59 | (clojure.string/replace #"\." "/") 60 | (clojure.string/replace #"-" "_") 61 | (str ".clj")))] 62 | (clojure.java.io/make-parents f) 63 | (spit f (format "(ns %s)\n\n(def +version+ \"%s\")" (name namespace) +version+))) 64 | (next-handler (-> fileset (add-resource d) commit!)))))) 65 | 66 | (deftask build [] 67 | (comp 68 | (with-files 69 | (fn [x] (and (re-find #"sass4clj" (tmp-path x)) 70 | (not (re-find #"leiningen" (tmp-path x))))) 71 | (comp 72 | (pom 73 | :project 'deraen/sass4clj 74 | :description "Clojure wrapper for jsass") 75 | (jar) 76 | (install))) 77 | 78 | (with-files 79 | (fn [x] (re-find #"boot_sass" (tmp-path x))) 80 | (comp 81 | (pom 82 | :project 'deraen/boot-sass 83 | :description "Boot task to compile SASS" 84 | :dependencies []) 85 | (write-version-file :namespace 'deraen.boot-sass.version) 86 | (jar) 87 | (install))) 88 | 89 | (with-files 90 | (fn [x] (re-find #"leiningen" (tmp-path x))) 91 | (comp 92 | (pom 93 | :project 'deraen/lein-sass4clj 94 | :description "Leinigen task to compile SASS" 95 | :dependencies []) 96 | (write-version-file :namespace 'leiningen.sass4clj.version) 97 | (jar) 98 | (install))))) 99 | 100 | (deftask dev [] 101 | (comp 102 | (watch) 103 | (repl :server true) 104 | (build) 105 | (target))) 106 | 107 | (deftask deploy [] 108 | (comp 109 | (build) 110 | (push :repo "clojars" :gpg-sign (not (.endsWith +version+ "-SNAPSHOT"))))) 111 | 112 | (ns-unmap *ns* 'test) 113 | 114 | (deftask test [] 115 | (comp 116 | (write-version-file :namespace 'sass4clj.version) 117 | (bat-test :report 'eftest.report.pretty/report))) 118 | 119 | (deftask autotest [] 120 | (comp 121 | (watch) 122 | (test))) 123 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {io.bit3/jsass {:mvn/version "5.11.0"} 2 | cheshire/cheshire {:mvn/version "5.9.0"} 3 | org.webjars/webjars-locator {:mvn/version "0.37"} 4 | hawk/hawk {:mvn/version "0.2.11"} 5 | org.clojure/tools.cli {:mvn/version "0.4.2"}} 6 | 7 | :aliases {:test {:extra-deps {org.slf4j/slf4j-nop {:mvn/version "1.7.29"}}}}} 8 | -------------------------------------------------------------------------------- /src/deraen/boot_sass.clj: -------------------------------------------------------------------------------- 1 | (ns deraen.boot-sass 2 | {:boot/export-tasks true} 3 | (:require 4 | [clojure.java.io :as io] 5 | [boot.pod :as pod] 6 | [boot.core :as core] 7 | [boot.util :as util] 8 | [boot.file :as file] 9 | [clojure.string :as string] 10 | [deraen.boot-sass.version :refer [+version+]])) 11 | 12 | (def ^:private deps 13 | [['deraen/sass4clj +version+]]) 14 | 15 | (defn by-pre 16 | [prefixes files & [negate?]] 17 | ((core/file-filter (fn [prefix] 18 | (fn [f] 19 | (.startsWith (.getName f) prefix)))) 20 | prefixes files negate?)) 21 | 22 | (defn- find-mainfiles [fs inputs] 23 | (if inputs 24 | (->> fs 25 | core/input-files 26 | (core/by-path inputs)) 27 | (by-pre ["_"] 28 | (->> fs 29 | core/input-files 30 | (core/by-ext [".scss" ".sass"])) 31 | true))) 32 | 33 | (defn- find-relative-path [dirs filepath] 34 | (if-let [file (io/file filepath)] 35 | (let [parent (->> dirs 36 | (map io/file) 37 | (some (fn [x] (if (file/parent? x file) x))))] 38 | (if parent (.getPath (file/relative-to parent file)))))) 39 | 40 | (defn- find-original-path [source-paths dirs filepath] 41 | (if-let [rel-path (find-relative-path dirs filepath)] 42 | (or (some (fn [source-path] 43 | (let [f (io/file source-path rel-path)] 44 | (if (.exists f) 45 | (.getPath f)))) 46 | source-paths) 47 | rel-path) 48 | filepath)) 49 | 50 | (defn- fixed-message 51 | "Replaces the tmp-path in formatted error message using path in working dir." 52 | [{:keys [formatted file]}] 53 | (let [correct-path (find-original-path (concat (:source-paths pod/env) (:resource-paths pod/env)) 54 | (:directories pod/env) 55 | file)] 56 | (string/replace formatted #"(on line \d* of )(.*)" (fn [[_ prefix wrong-path]] 57 | ;; FIXME: search file using wrong-path 58 | (str prefix correct-path))))) 59 | 60 | (core/deftask sass 61 | "SASS CSS compiler. 62 | 63 | For each `.sass` or `.scss` file not starting with `_` in source-paths creates equivalent `.css` file. 64 | For example to create file `{target-path}/public/css/style.css` your sass 65 | code should be at path `{source-path}/public/css/style.scss`. 66 | 67 | If you are seeing SLF4J warnings, check https://github.com/Deraen/sass4clj#log-configuration 68 | 69 | Output-styles: 70 | - :nested 71 | - :compact 72 | - :expanded 73 | - :compressed" 74 | [s source-map bool "Enable source-maps for compiled CSS." 75 | o output-style STYLE kw "Set output-style" 76 | i inputs PATHS [str] "List of SCSS main file paths, relative to fileset" 77 | _ options VAL edn "Other options to sass4clj"] 78 | (let [output-dir (core/tmp-dir!) 79 | p (-> (core/get-env) 80 | (update-in [:dependencies] into deps) 81 | pod/make-pod 82 | future) 83 | prev (atom nil)] 84 | (core/with-pre-wrap fileset 85 | (let [sources (->> fileset 86 | (core/fileset-diff @prev) 87 | core/input-files 88 | (core/by-ext [".scss" ".sass"]))] 89 | (reset! prev fileset) 90 | (when (seq sources) 91 | (util/info "Compiling {sass}... %d changed files.\n" (count sources)) 92 | (doseq [f (find-mainfiles fileset inputs) 93 | :let [input-path (.getPath (core/tmp-file f)) 94 | output-rel-path (string/replace (core/tmp-path f) #"\.(scss|sass)$" ".css") 95 | output-path (.getPath (io/file output-dir output-rel-path))]] 96 | (let [{:keys [warnings error]} 97 | (pod/with-eval-in @p 98 | (require 'sass4clj.core) 99 | (try 100 | (sass4clj.core/sass-compile-to-file 101 | ~input-path 102 | ~output-path 103 | (merge 104 | ~options 105 | {:source-map ~source-map 106 | :verbosity ~(deref util/*verbosity*) 107 | :output-style ~output-style})) 108 | (catch Exception e# 109 | (let [data# (ex-data e#)] 110 | (if (= :sass4clj.core/error (:type data#)) 111 | {:error data#} 112 | (throw e#))))))] 113 | 114 | ;; minimal stack trace and no data -> shorter message 115 | (when error 116 | (throw (Exception. (fixed-message error)))) 117 | 118 | ;; TODO: warnings 119 | ))) 120 | (-> fileset 121 | (core/add-resource output-dir) 122 | core/commit!))))) 123 | -------------------------------------------------------------------------------- /src/leiningen/sass4clj.clj: -------------------------------------------------------------------------------- 1 | (ns leiningen.sass4clj 2 | (:require [leiningen.help :as help] 3 | [leiningen.core.eval :as leval] 4 | [leiningen.core.project :as project] 5 | [leiningen.core.main :as main] 6 | [leiningen.sass4clj.version :refer [+version+]])) 7 | 8 | (def sass4j-profile {:dependencies [['hawk "0.2.11"] 9 | ['deraen/sass4clj +version+]]}) 10 | 11 | (defn- remove-prep-tasks 12 | "Get copy of project without prep tasks to avoid triggering infinite loops 13 | when using sass4clj as a prep task." 14 | [project] 15 | (with-meta 16 | (dissoc project :prep-tasks) 17 | (meta project))) 18 | 19 | (defn- run-compiler 20 | "Run the sasscss compiler." 21 | [project options] 22 | (leval/eval-in-project 23 | (remove-prep-tasks (project/merge-profiles project [sass4j-profile])) 24 | `(try 25 | ;; Quote the options, e.g. :source-paths could be a list after 26 | ;; lein profile merge. 27 | (sass4clj.api/build '~options) 28 | (catch Exception e# 29 | (if (= :sass4clj.core/error (:type (ex-data e#))) 30 | (do 31 | (println (.getMessage e#)) 32 | (System/exit 1)) 33 | (throw e#)))) 34 | '(require 'sass4clj.api))) 35 | 36 | ;; For docstrings 37 | 38 | (defn- once 39 | "Compile sass files once." 40 | [project] 41 | nil) 42 | 43 | (defn- auto 44 | "Compile sass files, then watch for changes and recompile until interrupted." 45 | [project] 46 | nil) 47 | 48 | (defn sass4clj 49 | "SASS CSS compiler. 50 | 51 | For each `.sass` or `.scss` file not starting with `_` in source-paths creates equivalent `.css` file. 52 | For example to create file `{target-path}/public/css/style.css` your sass 53 | code should be at path `{source-path}/public/css/style.scss`. 54 | 55 | If you are seeing SLF4J warnings, check https://github.com/Deraen/sass4clj#log-configuration 56 | 57 | Options should be provided using `:sass` key in project map. 58 | 59 | Available options: 60 | :target-path The path where CSS files are written to. 61 | :source-paths Collection of paths where SASS files are read from. 62 | :inputs Collection of main file paths, relative to source paths, to compile. 63 | :output-style Possible types are :nested, :compact, :expanded and :compressed. 64 | :verbosity Set verbosity level, valid values are 1 and 2. 65 | :source-map Enable source-maps. 66 | 67 | Other options are passed as is to sass4clj. 68 | 69 | Command arguments: 70 | Add `:debug` as subtask argument to enable debugging output." 71 | {:help-arglists '([once auto]) 72 | :subtasks [#'once #'auto]} 73 | ([project] 74 | (println (help/help-for "sass4clj")) 75 | (main/abort)) 76 | ([project subtask & args] 77 | (let [args (set args) 78 | config (cond-> (:sass project) 79 | (contains? args ":debug") (assoc :verbosity 2))] 80 | (case subtask 81 | "once" (run-compiler project config) 82 | "auto" (run-compiler project (assoc config :auto true)) 83 | "help" (println (help/help-for "sass4clj")) 84 | (main/warn "Unknown task."))))) 85 | -------------------------------------------------------------------------------- /src/sass4clj/api.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.api 2 | (:require [sass4clj.watcher :as watcher] 3 | [sass4clj.core :as core] 4 | [clojure.java.io :as io] 5 | [clojure.spec.alpha :as s] 6 | [clojure.string :as string])) 7 | 8 | (defn main-file? [file] 9 | (and (or (.endsWith (.getName file) ".scss") 10 | (.endsWith (.getName file) ".sass") ) 11 | (not (.startsWith (.getName file) "_")))) 12 | 13 | (defn find-main-files [source-paths {:keys [inputs]}] 14 | (mapcat (fn [source-path] 15 | (let [file (io/file source-path)] 16 | (->> (if inputs 17 | (->> inputs 18 | (map #(io/file file %)) 19 | (filter #(.exists %))) 20 | (->> (file-seq file) 21 | (filter main-file?))) 22 | (map (fn [x] [(.getPath x) (.toString (.relativize (.toURI file) (.toURI x)))]))))) 23 | source-paths)) 24 | 25 | ;; Unused 26 | (defn print-warning [warning] 27 | (println (format "WARN: %s %s\n" (:message warning) 28 | (str (if (:uri (:source warning)) 29 | (str "on file " 30 | (:uri (:source warning)) 31 | (if (:line warning) 32 | (str " at line " (:line warning) " character " (:char warning))))))))) 33 | 34 | (defn compile-sass [main-files {:keys [auto target-path] :as options}] 35 | (doseq [[path relative-path] main-files] 36 | (println (format "Compiling {sass}... %s" relative-path)) 37 | (let [result 38 | (try 39 | (core/sass-compile-to-file 40 | path 41 | (.getPath (io/file target-path (string/replace relative-path #"\.(scss|sass)$" ".css"))) 42 | (dissoc options :target-path)) 43 | (catch Exception e 44 | (if auto 45 | (println (.getMessage e)) 46 | (throw e))))] 47 | ;; FIXME: core doesn't set :warnings because jsass doesn't return them in useful format, 48 | ;; this is never called. 49 | (doseq [warning (:warnings result)] 50 | (print-warning warning))))) 51 | 52 | (s/def ::source-paths (s/coll-of string? :into vec)) 53 | (s/def ::inputs (s/coll-of string? :into vec)) 54 | (s/def ::auto boolean?) 55 | (s/def ::help boolean?) 56 | (s/def ::target-path string?) 57 | (s/def ::source-map boolean?) 58 | (s/def ::verbosity #{1 2}) 59 | (s/def ::output-style #{:nested :compact :expanded :compressed}) 60 | (s/def ::options (s/keys :req-un [::source-paths ::target-path] 61 | :opt-un [::inputs ::auto ::help ::source-map ::verbosity ::output-style])) 62 | 63 | (defn build [{:keys [source-paths auto] :as options}] 64 | (when-not (s/valid? ::options options) 65 | (s/explain-out (s/explain-data ::options options))) 66 | (if auto 67 | (watcher/start source-paths (fn [& _] 68 | (let [main-files (find-main-files source-paths options)] 69 | (compile-sass main-files options)))) 70 | (let [main-files (find-main-files source-paths options)] 71 | (compile-sass main-files options)))) 72 | 73 | (defn start [options] 74 | (build (assoc options :auto true))) 75 | 76 | (defn stop [this] 77 | (if this 78 | (watcher/stop this))) 79 | -------------------------------------------------------------------------------- /src/sass4clj/component.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.component 2 | (:require [sass4clj.api :as api] 3 | [com.stuartsierra.component :as component] 4 | [suspendable.core :as suspendable])) 5 | 6 | (defrecord Sass4Clj [options] 7 | component/Lifecycle 8 | (start [this] 9 | (assoc this :watcher (api/start options))) 10 | (stop [this] 11 | (api/stop (:watcher this))) 12 | suspendable/Suspendable 13 | (suspend [this] 14 | this) 15 | (resume [this old-this] 16 | (if (= (:options this) (:options old-this)) 17 | old-this 18 | (do 19 | (component/stop old-this) 20 | (component/start this))))) 21 | -------------------------------------------------------------------------------- /src/sass4clj/core.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.core 2 | (:require 3 | [clojure.java.io :as io] 4 | [clojure.string :as string] 5 | [sass4clj.util :as util] 6 | [sass4clj.webjars :as webjars] 7 | [cheshire.core :as json]) 8 | (:import 9 | [java.net URI] 10 | [java.util Collection Collections] 11 | [io.bit3.jsass CompilationException Options Output OutputStyle Sass2ScssOptions] 12 | [io.bit3.jsass.importer Import Importer])) 13 | 14 | (defn find-local-file [names current-dir] 15 | (some 16 | (fn [name] 17 | (let [f (io/file current-dir name)] 18 | (if (.exists f) 19 | [(.getPath f) f]))) 20 | names)) 21 | 22 | (defn normalize-url 23 | "Simple URL normalization logic for import paths. Can normalize 24 | relative paths." 25 | [url-string] 26 | (loop [result nil 27 | parts (string/split url-string #"/")] 28 | (if (seq parts) 29 | (let [part (first parts)] 30 | (case part 31 | ;; Skip empty 32 | "" (recur result (rest parts)) 33 | ;; Skip "." 34 | "." (recur result (rest parts)) 35 | ;; Remove previous part, if there are previous non ".." parts 36 | ".." (if (and (seq result) (not= ".." (first result))) 37 | (recur (rest result) (rest parts)) 38 | (recur (conj result part) (rest parts))) 39 | (recur (conj result part) (rest parts)))) 40 | (string/join "/" (reverse result))))) 41 | 42 | (defn join-url [& parts] 43 | (normalize-url (string/join "/" parts))) 44 | 45 | (defn find-resource [names] 46 | (some (fn [name] 47 | (if-let [url (io/resource name)] 48 | (case (.getProtocol url) 49 | "file" 50 | [(.toString url) url] 51 | 52 | "jar" 53 | (let [jar-url (.openConnection url) 54 | entry (.getEntryName jar-url)] 55 | ; (util/dbug "Found %s from resources\n" url) 56 | [entry url])))) 57 | names)) 58 | 59 | (defn find-webjars [ctx names] 60 | (some (fn [name] 61 | (when-let [path (get (:asset-map ctx) name)] 62 | (find-resource [path]))) 63 | names)) 64 | 65 | (defn with-underscore [url] 66 | (let [parts (string/split url #"/")] 67 | (cond-> [url] 68 | (not (.startsWith (last parts) "_")) (conj (string/join "/" (conj (vec (butlast parts)) (str "_" (last parts)))))))) 69 | 70 | (defn remove-tilde-start [names] 71 | (mapcat (fn [name] 72 | (if (.startsWith name "~") 73 | [name (subs name 1)] 74 | [name])) 75 | names)) 76 | 77 | (defn possible-names [name] 78 | (let [scss? (.endsWith name ".scss") 79 | sass? (.endsWith name ".sass") 80 | css? (.endsWith name ".css") 81 | has-ext? (or scss? sass? css?)] 82 | (remove-tilde-start 83 | (cond-> [] 84 | (or (not has-ext?) scss?) (into (with-underscore (if scss? name (str name ".scss")))) 85 | (or (not has-ext?) sass?) (into (with-underscore (if sass? name (str name ".sass")))) 86 | (or (not has-ext?) css?) (conj (if css? name (str name ".css"))))))) 87 | 88 | (defn sass2scss [source] 89 | (io.bit3.jsass.Compiler/sass2scss source (bit-xor Sass2ScssOptions/PRETTIFY2 90 | Sass2ScssOptions/KEEP_COMMENT))) 91 | 92 | (defn custom-sass-importer [ctx] 93 | (reify 94 | Importer 95 | (^Collection apply [this ^String import-url ^Import prev] 96 | (let [;; Generates different possibilies of names with _ and extensions added 97 | names (possible-names import-url) 98 | [_ parent] (re-find #"(.*)/([^/]*)$" (str (.getAbsoluteUri prev)))] 99 | ; (util/info "Names: %s\n" names) 100 | (when-let [[found-absolute-uri uri] 101 | (or ;; First try paths relative to the file where @import is used 102 | (find-local-file names parent) 103 | (find-resource (map #(join-url parent %) names)) 104 | 105 | ;; Then other paths 106 | (some (fn [source-path] 107 | (find-local-file names source-path)) 108 | (:source-paths ctx)) 109 | (find-resource names) 110 | (find-webjars ctx names))] 111 | (util/dbug "Import: %s (from %s), result: %s\n" 112 | import-url 113 | (.getAbsoluteUri prev) 114 | uri) 115 | ; jsass doesn't know how to read content from other than files? 116 | ;; FIXME: If extension is sass, should convert the content to scss 117 | (Collections/singletonList 118 | (Import. import-url 119 | found-absolute-uri 120 | (cond-> (slurp uri) 121 | (.endsWith found-absolute-uri ".sass") (sass2scss))))))))) 122 | 123 | (def ^:private output-styles 124 | {:nested OutputStyle/NESTED 125 | :compact OutputStyle/COMPACT 126 | :expanded OutputStyle/EXPANDED 127 | :compressed OutputStyle/COMPRESSED}) 128 | 129 | (defn- build-options 130 | [{:keys [source-paths output-style source-map precision set-indented-syntax-src]}] 131 | (let [opts (Options.) 132 | include-paths (.getIncludePaths opts)] 133 | ;; Hardcode to use Unix newlines, mostly because that's what the tests use 134 | (.setLinefeed opts "\n") 135 | (doseq [source-path source-paths] 136 | (.add include-paths (io/file source-path))) 137 | (when output-style 138 | (.setOutputStyle opts (get output-styles output-style))) 139 | (when source-map 140 | ;; we manually append source-map uri in sass-compile-to-file 141 | (.setOmitSourceMapUrl opts true) 142 | ;; would be hard to deal with adding all the source-files to output... 143 | ;; or at least harder than just one file. 144 | (.setSourceMapContents opts true) 145 | (.setSourceMapFile opts (URI. "placeholder.css.map"))) 146 | (when precision 147 | (.setPrecision opts precision)) 148 | (.setIsIndentedSyntaxSrc opts (true? set-indented-syntax-src)) 149 | opts)) 150 | 151 | (defn sass-compile 152 | "Input can be: 153 | - String 154 | - File 155 | 156 | Options: 157 | - :source-map-path - Enables source-maps and uses this URL for 158 | sourceMappingURL. Relative to css file." 159 | [input {:keys [verbosity source-map source-paths] 160 | :or {verbosity 1} 161 | :as options}] 162 | (binding [util/*verbosity* verbosity] 163 | (try 164 | (let [ctx {:asset-map (webjars/asset-map) 165 | :source-paths source-paths} 166 | compiler (io.bit3.jsass.Compiler.) 167 | opts (build-options options) 168 | _ (doto (.getImporters opts) 169 | (.add (custom-sass-importer ctx))) 170 | output (if (string? input) 171 | (.compileString compiler input opts) 172 | (.compileFile compiler (.toURI input) nil opts))] 173 | {:output (.getCss output) 174 | :source-map (if source-map (.getSourceMap output))}) 175 | (catch CompilationException e 176 | (throw (ex-info (.getMessage e) (assoc (json/parse-string (.getErrorJson e) true) 177 | :type ::error))))))) 178 | 179 | (defn sass-compile-to-file 180 | "Arguments: 181 | - input-path - Path to the input file 182 | - output-path - Path to the output file, possible to source map will be 183 | written to same path with `.map` appended 184 | - options 185 | 186 | Options: 187 | - :source-map - Enables source-maps and sets URI using output-path." 188 | [input-path output-path {:keys [source-map] :as options}] 189 | (let [input-file (io/file input-path) 190 | output-file (io/file output-path) 191 | source-map-name (if source-map (str output-path ".map")) 192 | source-map-output (io/file (str output-path ".map")) 193 | {:keys [output source-map] :as result} (sass-compile input-file options) 194 | source-map-url (str "/*# sourceMappingURL=" (.getName source-map-output) " */") ] 195 | (when output 196 | (io/make-parents output-file) 197 | (spit output-file output) 198 | (when source-map 199 | (spit output-file source-map-url :append true) 200 | (spit source-map-output source-map))) 201 | (if source-map 202 | (assoc result :output (str output source-map-url)) 203 | result))) 204 | -------------------------------------------------------------------------------- /src/sass4clj/integrant.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.integrant 2 | (:require [sass4clj.api :as api] 3 | [integrant.core :as ig])) 4 | 5 | (defmethod ig/init-key ::sass4clj [_ options] 6 | (api/start options)) 7 | 8 | (defmethod ig/halt-key! ::sass4clj [_ watcher] 9 | (api/stop watcher)) 10 | 11 | (defmethod ig/suspend-key! ::sass4clj [_ watcher] 12 | nil) 13 | 14 | (defmethod ig/resume-key ::sass4clj [key opts old-opts old-impl] 15 | (if (= opts old-opts) 16 | old-impl 17 | (do 18 | (ig/halt-key! key old-impl) 19 | (ig/init-key key opts)))) 20 | -------------------------------------------------------------------------------- /src/sass4clj/main.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.main 2 | (:require [sass4clj.api :as api] 3 | [clojure.java.io :as io] 4 | [clojure.tools.cli :as cli] 5 | [clojure.string :as str] 6 | [clojure.spec.alpha :as s] 7 | [clojure.edn :as edn])) 8 | 9 | (def cli-opts 10 | [["-h" "--help"] 11 | ["-a" "--auto" "Enable file watcher"] 12 | ["-t" "--target-path TARGET" "The path where CSS files are written to" 13 | :default "target"] 14 | ["-s" "--source-map" "Enable source-maps for compiled CSS"] 15 | [nil "--output-style STYLE" "Select output style, available styles are nested, compact, expanded and compressed" 16 | :parse-fn (fn [s] (keyword s)) 17 | :validate [#{:nested :compact :expanded :compressed} "Invalid value"]] 18 | ["-v" "--verbosity LEVEL" "Set verbosity level, valid values are 1 and 2." 19 | :parse-fn (fn [s] 20 | (Integer/parseInt s)) 21 | :validate [#{1 2} "Must be 1 or 2"]] 22 | [nil "--source-paths PATHS" "List of SASS source paths, comma separated" 23 | :default ["src"] 24 | :parse-fn (fn [x] 25 | (str/split x #","))] 26 | ["-i" "--inputs PATHS" "List of SASS main file paths, relative to source-path, comma separated" 27 | :parse-fn (fn [x] 28 | (str/split x #","))] 29 | ["-c" "--config PATH" "EDN file to read config options from"]]) 30 | 31 | (defn help-text [options-summary] 32 | (str "{sass} CSS compiler. 33 | 34 | Usage: program-name [options] 35 | 36 | If inputs option if given, compiles each file to same relative path in 37 | target-path. If inputs is not used, finds each `.scss` or `.sass` file, 38 | not starting with prefix `_`, in source-paths. 39 | 40 | If you are seeing SLF4J warnings, check https://github.com/Deraen/sass4clj#log-configuration 41 | 42 | Options: 43 | " options-summary 44 | " 45 | 46 | Config file options are merged over the default options, before CLI options.")) 47 | 48 | (defn -main [& args] 49 | (let [{:keys [options summary errors]} (cli/parse-opts args cli-opts :no-defaults true) 50 | {:keys [help]} options 51 | config-file (if (:config options) 52 | (edn/read-string (slurp (io/file (:config options))))) 53 | options (merge (cli/get-default-options cli-opts) 54 | config-file 55 | (dissoc options :config))] 56 | (cond 57 | errors (do 58 | (println (str/join "\n" errors)) 59 | (System/exit (count errors))) 60 | help (println (help-text summary)) 61 | :else (try 62 | (api/build options) 63 | (catch Exception e 64 | (if (= :sass4clj.core/error (:type (ex-data e))) 65 | (do 66 | (println (.getMessage e)) 67 | (System/exit 1)) 68 | (throw e))))))) 69 | -------------------------------------------------------------------------------- /src/sass4clj/util.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.util) 2 | 3 | ;; 4 | ;; Debugging 5 | ;; from boot.util 6 | ;; 7 | 8 | (def ^:dynamic *verbosity* 1) 9 | 10 | (defn- print* 11 | [verbosity args] 12 | (when (>= *verbosity* verbosity) 13 | (binding [*out* *err*] 14 | (print (if (seq (rest args)) 15 | (apply format args) 16 | (first args))) 17 | (flush)))) 18 | 19 | (defn dbug [& more] (print* 2 more)) 20 | (defn info [& more] (print* 1 more)) 21 | (defn warn [& more] (print* 1 more)) 22 | (defn fail [& more] (print* 1 more)) 23 | -------------------------------------------------------------------------------- /src/sass4clj/watcher.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.watcher 2 | (:require [hawk.core :as hawk])) 3 | 4 | ;; From figwheel-main 5 | ;; https://github.com/bhauman/figwheel-main/blob/be9c7b87d115cc1db807e0de7d9a763519be3e2d/src/figwheel/main/watching.clj#L41 6 | (defn throttle [millis f] 7 | (fn [{:keys [collector] :as ctx} e] 8 | (let [collector (or collector (atom {})) 9 | {:keys [collecting? events]} (deref collector)] 10 | (if collecting? 11 | (swap! collector update :events (fnil conj []) e) 12 | (do 13 | (swap! collector assoc :collecting? true) 14 | (future (Thread/sleep millis) 15 | (let [events (volatile! nil)] 16 | (swap! collector 17 | #(-> % 18 | (assoc :collecting? false) 19 | (update :events (fn [evts] (vreset! events evts) nil)))) 20 | (f (cons e @events)))))) 21 | (assoc ctx :collector collector)))) 22 | 23 | (defn start [source-paths f] 24 | (f) 25 | (hawk/watch! 26 | [{:paths source-paths 27 | :filter (fn [_ {:keys [file]}] 28 | (and (not (.startsWith (.getName file) ".")) 29 | (or (.endsWith (.getName file) ".scss") 30 | (.endsWith (.getName file) ".sass") 31 | (.endsWith (.getName file) ".css")))) 32 | :handler (throttle 50 f)}])) 33 | 34 | (defn stop [watcher] 35 | (hawk/stop! watcher)) 36 | -------------------------------------------------------------------------------- /src/sass4clj/webjars.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.webjars 2 | (:import [java.io File] 3 | [org.webjars WebJarAssetLocator])) 4 | 5 | (def ^:private webjars-pattern 6 | #"META-INF/resources/webjars/([^/]+)/([^/]+)/(.*)") 7 | 8 | (defn- asset-path [resource] 9 | (let [[_ name version path] (re-matches webjars-pattern resource)] 10 | (str name "/" path))) 11 | 12 | (defn asset-map 13 | "Create map of asset path to classpath resource url. Asset path is 14 | the resource url without webjars part." 15 | [] 16 | (->> (.listAssets (WebJarAssetLocator.)) 17 | (map (juxt asset-path identity)) 18 | (into {}))) 19 | 20 | (comment 21 | (time (asset-map))) 22 | -------------------------------------------------------------------------------- /test-resources/_icons.scss: -------------------------------------------------------------------------------- 1 | .i { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /test-resources/bar.css: -------------------------------------------------------------------------------- 1 | .from-css { 2 | color: black; 3 | } 4 | -------------------------------------------------------------------------------- /test-resources/foo.scss: -------------------------------------------------------------------------------- 1 | .from-scss { font-size: 12px; } 2 | -------------------------------------------------------------------------------- /test-resources/material-icons-test.scss: -------------------------------------------------------------------------------- 1 | @import "material-design-iconic-font/scss/material-design-iconic-font"; 2 | @import "icons"; 3 | -------------------------------------------------------------------------------- /test-resources/xyz.sass: -------------------------------------------------------------------------------- 1 | .from-sass 2 | color: salmon 3 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "1.7.0" 6 | BOOT_CLOJURE_VERSION=1.7.0 boot test 7 | 8 | echo 9 | echo "1.6.0" 10 | BOOT_CLOJURE_VERSION=1.6.0 boot test 11 | 12 | echo 13 | echo "1.8.0" 14 | BOOT_CLOJURE_VERSION=1.8.0 boot test 15 | 16 | echo 17 | echo "1.9.0-alpha14" 18 | BOOT_CLOJURE_VERSION=1.9.0-alpha14 boot test 19 | -------------------------------------------------------------------------------- /test/sass4clj/api_test.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.api-test 2 | (:require [clojure.test :refer [deftest is]] 3 | [clojure.java.io :as io] 4 | [sass4clj.api :as sass]) 5 | (:import (java.nio.file Files) 6 | (java.nio.file.attribute FileAttribute))) 7 | 8 | (defn- temp-dir 9 | [prefix] 10 | (.toString (Files/createTempDirectory prefix (into-array FileAttribute [])))) 11 | 12 | (def ^:private includer-scss 13 | "body { 14 | color: red; 15 | } 16 | 17 | @import 'includee'; 18 | ") 19 | 20 | (def ^:private includee-sass 21 | "@charset 'utf-8' 22 | 23 | p 24 | color: blue 25 | ") 26 | 27 | (deftest include-paths 28 | (let [input-dir (temp-dir "sass4clj-input") 29 | include-dir (temp-dir "sass4clj-include") 30 | output-dir (temp-dir "sass4clj-output") 31 | options {:source-paths [input-dir include-dir] 32 | :target-path output-dir}] 33 | (spit (io/file input-dir "includer.scss") includer-scss) 34 | (spit (io/file include-dir "includee.sass") includee-sass) 35 | (sass/build options) 36 | (is (= "body {\n color: red; }\n\np {\n color: blue; }\n" 37 | (slurp (io/file output-dir "includer.css")))))) 38 | -------------------------------------------------------------------------------- /test/sass4clj/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.core-test 2 | (:require [clojure.test :refer :all] 3 | [clojure.string :as string] 4 | [clojure.java.io :as io] 5 | [sass4clj.core :refer :all]) 6 | (:import [java.io File])) 7 | 8 | (deftest normalize-url-test 9 | (is (= "foo/bar" (normalize-url "foo/./bar"))) 10 | (is (= "foo/bar" (normalize-url "foo//bar"))) 11 | (is (= "bar" (normalize-url "foo/../bar"))) 12 | (is (= "../foo" (normalize-url "../foo"))) 13 | (is (= "../../foo" (normalize-url "../../foo"))) 14 | (is (= "../../../foo" (normalize-url "../../../foo"))) 15 | (is (= "../foo" (normalize-url "a/../../foo")))) 16 | 17 | (deftest join-url-test 18 | (is (= "foo/bar" (join-url "foo" "bar"))) 19 | (is (= "foo/bar" (join-url "foo" "bar"))) 20 | (is (= "foo/bar" (join-url "foo/" "bar"))) 21 | (is (= "foo/xxx" (join-url "foo/bar" "../xxx"))) 22 | (is (= "foo bar/xxx" (join-url "foo bar" "xxx"))) 23 | (is (= "foo%20bar/xxx" (join-url "foo%20bar" "xxx"))) 24 | (is (= "a/d.less" (join-url "a/b/c" "../../d.less")))) 25 | 26 | (deftest with-underscore-test 27 | (is (= ["foo.scss" "_foo.scss"] 28 | (with-underscore "foo.scss"))) 29 | (is (= ["_foo.scss"] 30 | (with-underscore "_foo.scss"))) 31 | (is (= ["foo/bar.scss" "foo/_bar.scss"] 32 | (with-underscore "foo/bar.scss"))) 33 | (is (= ["foo/_bar.scss"] 34 | (with-underscore "foo/_bar.scss")))) 35 | 36 | (deftest possible-names-test 37 | (is (= ["foo.scss" "_foo.scss"] 38 | (possible-names "foo.scss"))) 39 | (is (= ["foo/bar.scss" "foo/_bar.scss"] 40 | (possible-names "foo/bar.scss"))) 41 | (is (= ["_foo.scss"] 42 | (possible-names "_foo.scss"))) 43 | (is (= ["foo.sass" "_foo.sass"] 44 | (possible-names "foo.sass"))) 45 | (is (= ["foo.css"] 46 | (possible-names "foo.css"))) 47 | (is (= ["foo.scss" "_foo.scss" "foo.sass" "_foo.sass" "foo.css"] 48 | (possible-names "foo"))) 49 | (is (= ["~bootstrap/foo/bar.scss" "bootstrap/foo/bar.scss" 50 | "~bootstrap/foo/_bar.scss" "bootstrap/foo/_bar.scss" 51 | "~bootstrap/foo/bar.sass" "bootstrap/foo/bar.sass" 52 | "~bootstrap/foo/_bar.sass" "bootstrap/foo/_bar.sass" 53 | "~bootstrap/foo/bar.css" "bootstrap/foo/bar.css"] 54 | (possible-names "~bootstrap/foo/bar"))) ) 55 | 56 | (def sass-file (File/createTempFile "sass4clj" "sass-file.sass")) 57 | (def scss-file (File/createTempFile "sass4clj" "scss-file.scss")) 58 | 59 | (def scss-code 60 | "$test: #fff; 61 | 62 | @import \"url.css\"; 63 | @import \"foo\"; 64 | @import \"xyz\"; 65 | @import \"bar\"; 66 | 67 | a { color: $test;}") 68 | 69 | (def sass-code 70 | "$test: red; 71 | 72 | @import \"url.css\" 73 | @import \"foo\" 74 | @import \"xyz\" 75 | @import \"bar\" 76 | 77 | h1 78 | color: $test") 79 | 80 | (def scss-css 81 | "@import url(url.css); 82 | .from-scss { 83 | font-size: 12px; } 84 | 85 | .from-sass { 86 | color: salmon; } 87 | 88 | .from-css { 89 | color: black; } 90 | 91 | a { 92 | color: #fff; } 93 | ") 94 | 95 | (def sass-css 96 | "@import url(url.css); 97 | .from-scss { 98 | font-size: 12px; } 99 | 100 | .from-sass { 101 | color: salmon; } 102 | 103 | .from-css { 104 | color: black; } 105 | 106 | h1 { 107 | color: red; } 108 | ") 109 | 110 | (spit scss-file scss-code) 111 | (spit sass-file sass-code) 112 | 113 | (def local-import-file (File/createTempFile "sass4clj" "local.scss")) 114 | (spit local-import-file (str "@import \"" (.getName scss-file) "\";")) 115 | 116 | (deftest sass-compile-test 117 | (is (= {:output scss-css :source-map nil} 118 | (sass-compile scss-file {}))) 119 | 120 | (is (= {:output sass-css :source-map nil} 121 | (sass-compile sass-file {}))) 122 | 123 | (is (= {:output scss-css :source-map nil} 124 | (sass-compile scss-code {}))) 125 | 126 | ;; When compling string, syntax can't be detected from file name 127 | (is (= {:output sass-css :source-map nil} 128 | (sass-compile sass-code {:set-indented-syntax-src true}))) 129 | 130 | (is (= {:output scss-css :source-map nil} 131 | (sass-compile local-import-file {})))) 132 | 133 | (deftest sass-compile-source-map-test 134 | (let [out-file (File/createTempFile "sass4clj" "main.css") 135 | {:keys [output source-map]} (sass-compile-to-file local-import-file out-file {:source-map true})] 136 | (is (= (str "/*# sourceMappingURL=" (.getName out-file) ".map */") 137 | (last (string/split output #"\n")))) 138 | 139 | (is (= (str "/*# sourceMappingURL=" (.getName out-file) ".map */") 140 | (last (string/split (slurp out-file) #"\n")))) 141 | 142 | (is (string? source-map)))) 143 | 144 | (deftest import-werbjars 145 | (is (:output (sass-compile "@import \"bootstrap/scss/bootstrap.scss\";" {:verbosity 0}))) 146 | 147 | (testing "webpack style import with ~, refering to Node package" 148 | (is (:output (sass-compile "@import \"~bootstrap/scss/bootstrap.scss\";" {:verbosity 0}))))) 149 | 150 | (deftest compile-material-design-lite 151 | (is (:output (sass-compile "@import \"material-design-lite/src/material-design-lite\";" {})))) 152 | 153 | (deftest sass-compile-error 154 | (is (thrown? clojure.lang.ExceptionInfo (sass-compile "foosdfsdf%;" {}))) 155 | 156 | (try 157 | (sass-compile "foosdfsdf%;" {}) 158 | (catch Exception e 159 | (let [error (ex-data e)] 160 | (is (= {:status 1 161 | :file "stdin" 162 | :line 1 163 | :column 1 164 | :message "Invalid CSS after \"f\": expected 1 selector or at-rule, was \"foosdfsdf%;\"" 165 | :formatted "Error: Invalid CSS after \"f\": expected 1 selector or at-rule, was \"foosdfsdf%;\"\n on line 1:1 of stdin\n>> foosdfsdf%;\n ^\n" 166 | :type :sass4clj.core/error} error)))))) 167 | 168 | ;; Warnings not implmented because jsass doesn't return them in useful format. 169 | (def warning-file (doto (File/createTempFile "sass4clj" "warning-test.scss") 170 | (spit "@warn \"test\";"))) 171 | 172 | (deftest sass-compile-warning 173 | (is (= nil 174 | (first (:warnings (sass-compile warning-file {})))))) 175 | 176 | (deftest compile-material-design-iconic-font-test 177 | (is (:output (sass-compile (io/file "test-resources/material-icons-test.scss") 178 | {:verbosity 6 179 | :source-paths ["test-resources"]})))) 180 | -------------------------------------------------------------------------------- /test/sass4clj/webjars_test.clj: -------------------------------------------------------------------------------- 1 | (ns sass4clj.webjars-test 2 | (:require [clojure.test :refer :all] 3 | [sass4clj.webjars :as webjars])) 4 | 5 | (deftest asset-map-test 6 | (is (contains? (webjars/asset-map) "bootstrap/scss/bootstrap.scss"))) 7 | --------------------------------------------------------------------------------