-
-
Notifications
You must be signed in to change notification settings - Fork 68
/
compression.cljc
104 lines (95 loc) · 3.62 KB
/
compression.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
(ns ^{:mranderson/inlined true} day8.re-frame-10x.inlined-deps.garden.v1v3v10.garden.compression
"Stylesheet compression utilities."
#?(:clj
(:import (java.io StringReader StringWriter)
(com.yahoo.platform.yui.compressor CssCompressor))))
;; ---------------------------------------------------------------------
;; Clojure
;; Clojure stylesheet compression leverages the YUI Compressor as it
;; provides a performant and excellent solution to CSS compression.
#?(:clj
(defn compress-stylesheet
"Compress a stylesheet with the YUI CSSCompressor. Set
line-break-position to -1 for no line breaks, 0 for a line break
after each rule, and n > 0 for a line break after at most n
columns. Defaults to no -1"
([stylesheet]
(compress-stylesheet stylesheet -1))
([^String stylesheet line-break-position]
;; XXX: com.yahoo.platform.yui.compressor.CssCompressor#compress replaces "0%" with "0" everywhere
;; which might have worked in 2013 when YUI Compressor 2.4.8 was released, but not anymore in 2019.
(with-open [reader (-> stylesheet
(.replaceAll "(^|[^0-9])0%" "$10__YUIHACK__%")
(StringReader.))
writer (StringWriter.)]
(doto (CssCompressor. reader)
(.compress writer line-break-position))
(-> (str writer)
(.replaceAll "0__YUIHACK__%" "0%"))))))
;; ---------------------------------------------------------------------
;; ClojureScript
;; ClojureScript stylesheet compression uses a simple tokenizer and
;; loop/recur to construct a new string of minified CSS.
#?(:cljs
(defn- token-fn
"Return a function which when given a string will return a map
containing the chunk of text matched by re, it's size, and tag."
[[tag re]]
(fn [s]
(when-let [chunk (re-find re s)]
{:tag tag
:chunk chunk
:size (count chunk)}))))
#?(:cljs
(defn- tokenizer
"Given an arbitrary number of [tag regex] pairs, return a function
which when given a string s will return the first matching token of s.
Token precedence is determined by the order of the pairs. The first
and last pairs have the highest and lowest precedence respectively."
[& tags+regexes]
(let [fs (map token-fn tags+regexes)]
(fn [s]
(some #(% s) fs)))))
#?(:cljs
(def
^{:private true
:doc "Tokenizer used during stylesheet compression."}
stylesheet-tokenizer
(tokenizer
;; String literals
[:string #"^\"(?:\\.|[^\"])*\""]
;; Delimiters
[:r-brace #"^\s*\{\s*"]
[:l-brace #"^;?\s*}"]
[:r-paren #"^\s*\(\s*"]
[:l-paren #"^\s*\)"]
[:comma #"^,\s*"]
[:colon #"^:\s*"]
[:semicolon #"^;"]
;; White space
[:and #"^and\s+"]
[:space+ #"^ +"]
[:white-space+ #"^\s+"]
;; Everything else
[:any #"^."])))
#?(:cljs
(defn compress-stylesheet
"Compress a string of CSS using a basic compressor."
[stylesheet]
(loop [s1 stylesheet s2 ""]
(if-let [{:keys [tag chunk size]} (stylesheet-tokenizer s1)]
(recur (subs s1 size)
(str s2 (case tag
:string chunk
:r-brace "{"
:l-brace "}"
:r-paren "("
:l-paren ")"
:and "and "
:comma ","
:semi-comma ";"
:colon ":"
:space+ " "
:white-space+ ""
chunk)))
s2))))