-
Notifications
You must be signed in to change notification settings - Fork 23
/
minify.clj
143 lines (126 loc) · 4.37 KB
/
minify.clj
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
(ns optimus.optimizations.minify
(:require [clojure.java.io :as io]
[clojure.string :as str]
[optimus.js :as js]))
(defn- escape [str]
(-> str
(str/replace "\\" "\\\\")
(str/replace "'" "\\'")
(str/replace "\n" "\\n")))
(defn looks-like-already-minified
"Files with a single line over 5000 characters are considered already
minified, and skipped. This avoid issues with huge bootstrap.css files
and its ilk."
[contents]
(->> contents
(str/split-lines)
(some (fn [^String s] (> (.length s) 5000)))))
(defn normalize-line-endings [str]
(-> str
(str/replace "\r\n" "\n")
(str/replace "\r" "\n")))
(def ^String babel
(slurp (io/resource "babel.js")))
(defn- js-minification-code
[js options]
(let [js-code (escape (normalize-line-endings js))]
(str "(function () {"
(if (:transpile-es6? options)
(str "var jsCode = Babel.transform('" js-code "', { presets: ['env'], sourceType: 'script' }).code;")
(str "var jsCode = '" js-code "';"))
"var ast = UglifyJS.parse(jsCode);
ast.figure_out_scope();
var compressor = UglifyJS.Compressor();
var compressed = ast.transform(compressor);
compressed.figure_out_scope();
compressed.compute_char_frequency();"
(if (get options :mangle-names true) "compressed.mangle_names();" "")
"var stream = UglifyJS.OutputStream();
compressed.print(stream);
return stream.toString();
}());")))
(def ^String uglify
"The UglifyJS source code, free of dependencies and runnable in a
stripped context"
(slurp (io/resource "uglify.js")))
(defn prepare-uglify-engine
[]
(let [engine (js/make-engine)]
(.eval engine uglify)
(.eval engine babel)
engine))
(defn minify-js
([js] (minify-js js {}))
([js options]
(js/with-engine [engine (prepare-uglify-engine)]
(minify-js engine js options)))
([engine js options]
(if (looks-like-already-minified js)
js
(js/run-script-with-error-handling
engine
(js-minification-code js (:uglify-js options))
(:path options)))))
(defn minify-js-asset
[engine asset options]
(let [#^String path (:path asset)]
(if (.endsWith path ".js")
(update-in asset [:contents] #(minify-js engine % (assoc options :path path)))
asset)))
(defn minify-js-assets
([assets] (minify-js-assets assets {}))
([assets options]
(js/with-engine [engine (prepare-uglify-engine)]
(doall (map #(minify-js-asset engine % options)
assets)))))
;; minify CSS
(defn- css-minification-code
[css options]
(str "(function () {
var CleanCSS = require('clean-css');
var source = '" (escape (normalize-line-endings css)) "';
var options = {
processImport: false,
aggressiveMerging: " (:aggressive-merging options true) ",
advanced: " (:advanced-optimizations options true) ",
keepBreaks: " (:keep-line-breaks options false) ",
keepSpecialComments: '" (:keep-special-comments options "*") "',
compatibility: '" (:compatibility options "*") "'
};
var minified = new CleanCSS(options).minify(source).styles;
return minified;
}());"))
(def clean-css
"The clean-css source code, free of dependencies and runnable in a
stripped context"
(slurp (io/resource "clean-css.js")))
(defn prepare-clean-css-engine
"Minify CSS with the bundled clean-css version"
[]
(let [engine (js/make-engine)]
(.eval engine "var window = { XMLHttpRequest: {} };")
(.eval engine clean-css)
engine))
(defn minify-css
([css] (minify-css css {}))
([css options]
(js/with-engine [engine (prepare-clean-css-engine)]
(minify-css engine css options)))
([engine css options]
(if (looks-like-already-minified css)
css
(js/run-script-with-error-handling
engine
(css-minification-code css (:clean-css options))
(:path options)))))
(defn minify-css-asset
[engine asset options]
(let [#^String path (:path asset)]
(if (.endsWith path ".css")
(update-in asset [:contents] #(minify-css engine % (assoc options :path path)))
asset)))
(defn minify-css-assets
([assets] (minify-css-assets assets {}))
([assets options]
(js/with-engine [engine (prepare-clean-css-engine)]
(doall (map #(minify-css-asset engine % options) assets)))))