Skip to content

Commit 5cc0e6c

Browse files
committed
CLJS-2903: Support fingerprinting
Add a new compiler flag :fingerprint which will append content SHA to file name. manifest.edn is emitted to :output-dir for mapping to fingerprinted names.
1 parent 52112c5 commit 5cc0e6c

File tree

4 files changed

+158
-56
lines changed

4 files changed

+158
-56
lines changed

src/main/cljs/cljs/loader.cljs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212
[goog.module ModuleManager]))
1313

1414
(def module-infos MODULE_INFOS) ;; set by compiler
15-
(def module-uris MODULE_URIS) ;; set by compiler
15+
(def module-uris
16+
(if (exists? js/COMPILED_MODULE_URIS)
17+
js/COMPILED_MODULE_URIS
18+
MODULE_URIS)) ;; set by compiler
1619

1720
(defn deps-for [x graph]
1821
(let [depends-on (get graph x)]
@@ -39,7 +42,8 @@
3942
(defonce ^:dynamic *module-manager* (create-module-manager))
4043

4144
(.setAllModuleInfo *module-manager* (to-js module-infos))
42-
(.setModuleUris *module-manager* (to-js module-uris))
45+
(.setModuleUris *module-manager*
46+
(cond-> module-uris (map? module-uris) to-js))
4347

4448
(defn loaded?
4549
"Return true if modules is loaded. module-name should be a keyword matching

src/main/clojure/cljs/closure.clj

Lines changed: 108 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@
202202
:fn-invoke-direct :checked-arrays :closure-module-roots :rewrite-polyfills :use-only-custom-externs
203203
:watch :watch-error-fn :watch-fn :install-deps :process-shim :rename-prefix :rename-prefix-namespace
204204
:closure-variable-map-in :closure-property-map-in :closure-variable-map-out :closure-property-map-out
205-
:stable-names :ignore-js-module-exts :opts-cache :aot-cache :elide-strict})
205+
:stable-names :ignore-js-module-exts :opts-cache :aot-cache :elide-strict :fingerprint})
206206

207207
(def string->charset
208208
{"iso-8859-1" StandardCharsets/ISO_8859_1
@@ -1595,7 +1595,19 @@
15951595
(cond-> js
15961596
(not (false? elide-strict)) (string/replace #"(?m)^['\"]use strict['\"]" " ")))
15971597

1598-
(defn output-one-file [{:keys [output-to] :as opts} js]
1598+
(defn ^File fingerprint-out-file
1599+
[content ^File out-file]
1600+
(let [dir (.getParent out-file)
1601+
fn (.getName out-file)
1602+
idx (.lastIndexOf fn ".")
1603+
ext (subs fn (inc idx))
1604+
name (subs fn 0 idx)]
1605+
(io/file dir
1606+
(str name "-"
1607+
(string/lower-case
1608+
(util/content-sha content 7)) "." ext))))
1609+
1610+
(defn output-one-file [{:keys [output-to fingerprint] :as opts} js]
15991611
(let [js (elide-strict js opts)]
16001612
(cond
16011613
(nil? output-to) js
@@ -1604,7 +1616,13 @@
16041616
(util/file? output-to))
16051617
(let [f (io/file output-to)]
16061618
(util/mkdirs f)
1607-
(spit f js))
1619+
(spit f js)
1620+
(when fingerprint
1621+
(let [dir (.getParent f)
1622+
mf (io/file dir "manifest.edn")
1623+
g (fingerprint-out-file js f)]
1624+
(.renameTo f g)
1625+
(spit mf (pr-str {(.toString f) (.toString g)})))))
16081626

16091627
:else (println js))))
16101628

@@ -1722,56 +1740,99 @@
17221740
(when-let [main (:main opts)]
17231741
[main])))))))))
17241742

1743+
(defn fingerprinted-modules [modules fingerprint-info]
1744+
(into {}
1745+
(map
1746+
(fn [[module-name module-info]]
1747+
(let [module-info'
1748+
(assoc module-info :output-to
1749+
(get-in fingerprint-info
1750+
[module-name :output-to-fingerprint]))]
1751+
[module-name module-info'])))
1752+
modules))
1753+
17251754
(defn output-modules
17261755
"Given compiler options, original IJavaScript sources and a sequence of
17271756
module name and module description tuples output module sources to disk.
17281757
Modules description must define :output-to and supply :source entry with
17291758
the JavaScript source to write to disk."
17301759
[opts js-sources modules]
1731-
(doseq [[name {:keys [output-to source foreign-deps] :as module-desc}] modules]
1732-
(assert (not (nil? output-to))
1733-
(str "Module " name " does not define :output-to"))
1734-
(assert (not (nil? source))
1735-
(str "Module " name " did not supply :source"))
1736-
(let [fdeps-str (when-not (empty? foreign-deps)
1737-
(foreign-deps-str opts foreign-deps))
1738-
sm-name (when (:source-map opts)
1739-
(str output-to ".map"))
1740-
out-file (io/file output-to)]
1741-
(util/mkdirs out-file)
1742-
(spit out-file
1743-
(as-> source source
1744-
(if (= name :cljs-base)
1745-
(add-header opts source)
1746-
source)
1747-
(if fdeps-str
1748-
(str fdeps-str "\n" source)
1749-
source)
1750-
(elide-strict source opts)
1751-
(if sm-name
1752-
(add-source-map-link
1753-
(assoc opts
1754-
:output-to output-to
1755-
:source-map sm-name)
1756-
source)
1757-
source)))
1758-
(when (:source-map opts)
1759-
(let [sm-json-str (:source-map-json module-desc)
1760-
sm-json (json/read-str sm-json-str :key-fn keyword)]
1761-
(when (true? (:closure-source-map opts))
1762-
(spit (io/file (:source-map-name module-desc)) sm-json-str))
1763-
(emit-optimized-source-map sm-json js-sources sm-name
1764-
(merge opts
1765-
{:source-map sm-name
1766-
:preamble-line-count
1767-
(if (= name :cljs-base)
1768-
(+ (- (count (.split #"\r?\n" (make-preamble opts) -1)) 1)
1769-
(if (:output-wrapper opts) 1 0))
1770-
0)
1771-
:foreign-deps-line-count
1772-
(if fdeps-str
1773-
(- (count (.split #"\r?\n" fdeps-str -1)) 1)
1774-
0)})))))))
1760+
(let [fingerprint-info (atom {})]
1761+
(doseq [[name {:keys [output-to source foreign-deps] :as module-desc}] modules]
1762+
(assert (not (nil? output-to))
1763+
(str "Module " name " does not define :output-to"))
1764+
(assert (not (nil? source))
1765+
(str "Module " name " did not supply :source"))
1766+
(let [fdeps-str (when-not (empty? foreign-deps)
1767+
(foreign-deps-str opts foreign-deps))
1768+
sm-name (when (:source-map opts)
1769+
(str output-to ".map"))
1770+
out-file (io/file output-to)
1771+
_ (util/mkdirs out-file)
1772+
js (as-> source source
1773+
(if (= name :cljs-base)
1774+
(add-header opts source)
1775+
source)
1776+
(if fdeps-str
1777+
(str fdeps-str "\n" source)
1778+
source)
1779+
(elide-strict source opts)
1780+
(if sm-name
1781+
(add-source-map-link
1782+
(assoc opts
1783+
:output-to output-to
1784+
:source-map sm-name)
1785+
source)
1786+
source))
1787+
fingerprint-base? (and (:fingerprint opts) (= :cljs-base name))]
1788+
(when-not fingerprint-base?
1789+
(spit out-file js))
1790+
(when (:fingerprint opts)
1791+
(let [out-file' (fingerprint-out-file js out-file)]
1792+
(when-not fingerprint-base?
1793+
(.renameTo out-file out-file'))
1794+
(swap! fingerprint-info update name merge
1795+
(when fingerprint-base? {:source js})
1796+
{:output-to (.toString output-to)
1797+
:output-to-fingerprint (.toString out-file')})))
1798+
(when (:source-map opts)
1799+
(let [sm-json-str (:source-map-json module-desc)
1800+
sm-json (json/read-str sm-json-str :key-fn keyword)]
1801+
(when (true? (:closure-source-map opts))
1802+
(spit (io/file (:source-map-name module-desc)) sm-json-str))
1803+
(emit-optimized-source-map sm-json js-sources sm-name
1804+
(merge opts
1805+
{:source-map sm-name
1806+
:preamble-line-count
1807+
(if (= name :cljs-base)
1808+
(+ (- (count (.split #"\r?\n" (make-preamble opts) -1)) 1)
1809+
(if (:output-wrapper opts) 1 0)
1810+
(if (:fingerprint opts) 1 0))
1811+
0)
1812+
:foreign-deps-line-count
1813+
(if fdeps-str
1814+
(- (count (.split #"\r?\n" fdeps-str -1)) 1)
1815+
0)}))))))
1816+
(when (:fingerprint opts)
1817+
(let [fi @fingerprint-info
1818+
g (get-in fi [:cljs-base :output-to-fingerprint])
1819+
out (io/file g)
1820+
dir (.getParent out)
1821+
mnf (io/file dir "manifest.edn")
1822+
uris (module-graph/modules->module-uris
1823+
(fingerprinted-modules modules fi) js-sources opts)]
1824+
(spit mnf
1825+
(pr-str
1826+
(into {}
1827+
(map (juxt :output-to :output-to-fingerprint))
1828+
(vals fi))))
1829+
(spit out
1830+
(str "var COMPILED_MODULE_URIS = "
1831+
(json/write-str
1832+
(into {}
1833+
(map (fn [[k v]] [(-> k name munge) v])) uris))
1834+
";\n"
1835+
(get-in fi [:cljs-base :source])))))))
17751836

17761837
(defn lib-rel-path [{:keys [lib-path url provides] :as ijs}]
17771838
(if (nil? lib-path)

src/main/clojure/cljs/module_graph.cljc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@
362362
(:advanced :simple :whitespace)
363363
(reduce-kv
364364
(fn [ret k {:keys [output-to]}]
365+
;; TODO: move validation
366+
(assert output-to
367+
(str "Module " k " does not specify :output-to"))
365368
(assoc ret k [(-> output-to get-rel-path get-uri)]))
366369
{:cljs-base [(-> (or (get-in modules [:cljs-base :output-to])
367370
(io/file output-dir "cljs_base.js"))

src/test/clojure/cljs/build_api_tests.clj

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,18 @@
99
(ns cljs.build-api-tests
1010
(:refer-clojure :exclude [compile])
1111
(:import java.io.File)
12-
(:require [clojure.test :refer [deftest is testing]]
12+
(:require [cljs.analyzer :as ana]
13+
[cljs.build.api :as build]
14+
[cljs.closure :as closure]
15+
[cljs.env :as env]
16+
[cljs.test-util :as test]
17+
[cljs.util :as util]
1318
[clojure.data.json :as json]
19+
[clojure.edn :as edn]
1420
[clojure.java.io :as io]
1521
[clojure.java.shell :as sh]
16-
[cljs.env :as env]
17-
[cljs.analyzer :as ana]
18-
[cljs.util :as util]
19-
[cljs.test-util :as test]
20-
[cljs.build.api :as build]
21-
[cljs.closure :as closure]))
22+
[clojure.test :refer [deftest is testing]]
23+
[clojure.string :as string]))
2224

2325
(deftest test-target-file-for-cljs-ns
2426
(is (= (.getPath (build/target-file-for-cljs-ns 'example.core-lib nil))
@@ -645,3 +647,35 @@
645647
(is (re-find #"module\$.+\$node_modules\$graphql\$index\[\"default\"\]" (slurp core-js))))))
646648
(.delete (io/file "package.json"))
647649
(test/delete-node-modules))
650+
651+
(deftest test-fingerprint
652+
(let [out (io/file (test/tmp-dir) "cljs-2903-out")
653+
opts {:output-to (.getPath (io/file out "main.js"))
654+
:output-dir (.getPath out)
655+
:fingerprint true
656+
:stable-names true
657+
:optimizations :advanced}]
658+
(test/delete-out-files out)
659+
(build/build "src/test/cljs/hello.cljs" opts)
660+
(let [mf (edn/read-string (slurp (io/file out "manifest.edn")))
661+
f (io/file (get mf (:output-to opts)))
662+
sha (string/lower-case (util/content-sha (slurp (io/file f)) 7))]
663+
(is (true? (.exists f)))
664+
(is (string/includes? (.getPath f) sha)))))
665+
666+
(deftest test-fingerprint-modules
667+
(let [out (.getPath (io/file (test/tmp-dir) "cljs-2903-modules-out"))
668+
project (update-in (test/project-with-modules out)
669+
[:opts] merge
670+
{:fingerprint true
671+
:stable-names true
672+
:optimizations :advanced})]
673+
(test/delete-out-files out)
674+
(build/build (build/inputs (:inputs project)) (:opts project))
675+
(let [mf (edn/read-string (slurp (io/file out "manifest.edn")))]
676+
(doseq [[name {:keys [output-to]}] (get-in project [:opts :modules])]
677+
(when-not (= :cljs-base name)
678+
(let [f (io/file (get mf output-to))
679+
sha (string/lower-case (util/content-sha (slurp (io/file f)) 7))]
680+
(is (true? (.exists f)))
681+
(is (string/includes? (.getPath f) sha))))))))

0 commit comments

Comments
 (0)