Created
October 31, 2019 08:04
-
-
Save erikkaplun/b86b79857612b00427846c36a2bbe4f1 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns shadow.build.targets.chrome-extension | |
(:require | |
[clojure.java.io :as io] | |
[clojure.data.json :as json] | |
[clojure.string :as str] | |
[clojure.pprint :refer (pprint)] | |
[shadow.build :as b] | |
[shadow.build.targets.browser :as browser] | |
[shadow.build.targets.shared :as shared] | |
[shadow.cljs.repl :as repl] | |
[shadow.build.api :as build-api] | |
[shadow.cljs.devtools.api :as api] | |
[shadow.build.modules :as modules] | |
[shadow.build.output :as output] | |
[shadow.build.data :as data] | |
[shadow.cljs.util :as util] | |
[clojure.edn :as edn])) | |
(defn extract-outputs | |
[modules outputs] | |
(reduce-kv | |
(fn [modules mod-id {:keys [output-type] :as mod}] | |
(assoc modules mod-id | |
(-> mod | |
(update :depends-on util/set-conj :shared) | |
(cond-> | |
(and (not= :chrome/content-script output-type) | |
(not= :chrome/content-inject output-type)) | |
(update :depends-on util/set-conj :bg-shared) | |
(and (nil? output-type) (= :background mod-id)) | |
(assoc :output-type :chrome/background) | |
(and (nil? output-type) (= :content-script mod-id)) | |
(assoc :output-type :chrome/content-script) | |
(and (nil? output-type) (= :browser-action mod-id)) | |
(assoc :output-type :chrome/browser-action) | |
(and (nil? output-type) (= :page-action mod-id)) | |
(assoc :output-type :chrome/page-action) | |
)))) | |
modules | |
outputs)) | |
(defn configure | |
[state mode {:keys [outputs extension-dir manifest-file] :as config}] | |
(let [extension-dir | |
(io/file extension-dir) | |
manifest-input-file | |
(or (and (seq manifest-file) (io/file manifest-file)) | |
(io/file extension-dir "manifest.edn")) | |
manifest | |
(-> manifest-input-file | |
(slurp) | |
(edn/read-string)) | |
output-dir | |
(io/file extension-dir "out") | |
manifest-output-file | |
(io/file extension-dir "manifest.json") | |
outputs | |
(or outputs | |
(:shadow/outputs manifest)) | |
modules | |
(-> {:shared | |
{:entries ['cljs.core] | |
:default true} | |
:bg-shared | |
{:entries [] | |
:output-type :chrome/shared | |
:depends-on #{:shared}}} | |
(extract-outputs outputs)) | |
config | |
(update config :devtools merge | |
{:use-document-host false | |
:autoload true})] | |
(-> state | |
(assoc ::b/config config) | |
(update :extra-config-files conj manifest-input-file) | |
(build-api/merge-build-options | |
{:output-dir output-dir | |
:asset-path "out"}) | |
(build-api/with-js-options | |
{:js-provider :shadow}) | |
(assoc ::manifest-output-file manifest-output-file | |
::manifest manifest | |
::extension-dir extension-dir) | |
(cond-> | |
(and (= :dev mode) (:worker-info state)) | |
(shared/merge-repl-defines config)) | |
(browser/configure-modules mode (assoc config :modules modules))))) | |
(defn dev-header [state] | |
(str "var shadow$provide = {};\n" | |
(let [{:keys [polyfill-js]} state] | |
(when (seq polyfill-js) | |
(str "\n" polyfill-js))) | |
(output/closure-defines-and-base state) | |
"var $CLJS = this;\n" | |
"goog.global[\"$CLJS\"] = $CLJS;\n")) | |
(defn eval-load-sources [state sources] | |
(->> sources | |
(remove #{output/goog-base-id}) | |
(map #(data/get-source-by-id state %)) | |
(map (fn [{:keys [output-name] :as rc}] | |
(let [{:keys [js] :as output} (data/get-output! state rc) | |
source-map? | |
(output/has-source-map? output)] | |
(str "SHADOW_ENV.evalLoad(\"" output-name "\", " source-map? " , \"" (.escape browser/js-escaper ^String js) "\");") | |
))) | |
(str/join "\n"))) | |
(defn flush-dev-module [state {:keys [output-type output-name] :as mod}] | |
(spit (data/output-file state output-name) | |
(case output-type | |
;; these are controlled via the scripts/js properties and | |
;; support loading multiple files so no loader support is required | |
(:chrome/background :chrome/content-script) | |
(str (dev-header state) | |
(slurp (io/resource "shadow/boot/static.js"))) | |
;; special case for thing that require on isolated file | |
;; specifically chrome.tabs.executeScript(id, {file: "something.js"}) | |
:chrome/single-file | |
(str (dev-header state) | |
(slurp (io/resource "shadow/boot/static.js")) | |
(let [mods (-> (browser/get-all-module-deps state mod) | |
(conj mod))] | |
(->> mods | |
(mapcat :sources) | |
(remove #{output/goog-base-id}) | |
(map #(data/get-source-by-id state %)) | |
(map #(data/get-output! state %)) | |
(map :js) | |
(str/join "\n")))) | |
:chrome/shared | |
(eval-load-sources state (:sources mod)) | |
;; anything else assumes that can load files via normal browser methods | |
(str (dev-header state) | |
(slurp (io/resource "shadow/boot/browser.js")) | |
(let [mods (-> (browser/get-all-module-deps state mod) | |
(conj mod))] | |
(->> mods | |
(mapcat :sources) | |
(eval-load-sources state)) | |
))))) | |
(defn mod-files [{::b/keys [mode] :as state} {:keys [output-name sources] :as mod}] | |
(let [mods (browser/get-all-module-deps state mod)] | |
(case mode | |
:release | |
(-> [] | |
(into (map #(str "out/" (:output-name %))) mods) | |
(conj (str "out/" output-name))) | |
:dev | |
(->> (-> [] | |
(into (mapcat :sources) mods) | |
(into sources)) | |
(remove #{output/goog-base-id}) | |
(map #(data/get-source-by-id state %)) | |
(map :output-name) | |
(map #(str "out/cljs-runtime/" %)) | |
(into [(str "out/" output-name)]))))) | |
(defn flush-manifest | |
[{::keys [manifest-output-file manifest] :keys [build-modules] :as state}] | |
(let [manifest | |
(-> manifest | |
(dissoc :shadow/outputs) | |
(update :content-security-policy (fn [x] (if (string? x) x (str/join " " x)))) | |
(util/reduce-> | |
(fn [manifest {:keys [output-type] :as mod}] | |
(case output-type | |
:chrome/background | |
(update manifest :background | |
#(merge | |
{:scripts (mod-files state mod) | |
:persistent false} | |
%)) | |
:chrome/content-script | |
(-> manifest | |
(update :content-scripts util/vec-conj | |
(merge | |
{:js (mod-files state mod)} | |
(:chrome/options mod)))) | |
;; nothing to do for other output-types | |
manifest)) | |
;; skip shared which is always first | |
(rest build-modules)))] | |
#_(pprint manifest) | |
(spit manifest-output-file | |
(with-out-str | |
(binding [*print-length* nil] ;; grr cider for setting *print-length* in nrepl | |
(json/pprint manifest | |
:escape-slash false | |
:key-fn (fn [key] | |
(-> key name (str/replace #"-" "_"))))))) | |
state)) | |
(defn flush-single-file-for-module | |
[state {:keys [output-name] :as mod}] | |
(let [mods (-> (browser/get-all-module-deps state mod) | |
(conj mod)) | |
;; FIXME: create index source map properly | |
js | |
(str "var shadow$provide = {};\n" | |
(->> mods | |
(mapcat :sources) | |
(map #(data/get-source-by-id state %)) | |
(filter #(= :shadow-js (:type %))) | |
(map #(data/get-output! state %)) | |
(map :js) | |
(map #(str % ";")) | |
(str/join "\n"))) | |
{:keys [js source-map]} | |
(reduce | |
(fn [m {:keys [prepend output append] :as mod}] | |
(update m :js str prepend output append)) | |
{:js js | |
:source-map {}} | |
mods) | |
mod-file | |
(data/output-file state output-name)] | |
(spit mod-file js) | |
)) | |
(defmethod output/flush-optimized-module :chrome/single-file | |
[state mod] | |
(flush-single-file-for-module state mod)) | |
(defmethod output/flush-optimized-module :chrome/content-inject | |
[state mod] | |
(flush-single-file-for-module state mod)) | |
(defmethod output/flush-optimized-module :chrome/background | |
[state mod] | |
(output/flush-optimized-module state (assoc mod :output-type ::output/default))) | |
(defmethod output/flush-optimized-module :chrome/content-script | |
[state mod] | |
(output/flush-optimized-module state (assoc mod :output-type ::output/default))) | |
(defmethod output/flush-optimized-module :chrome/shared | |
[state mod] | |
(output/flush-optimized-module state (assoc mod :output-type ::output/default))) | |
(defn flush-dev | |
[{:keys [build-modules] :as state}] | |
(output/flush-sources state) | |
(doseq [mod build-modules] | |
(flush-dev-module state mod)) | |
(flush-manifest state)) | |
(defn process | |
[{::b/keys [stage mode config] :as state}] | |
state | |
(cond | |
(= :configure stage) | |
(configure state mode config) | |
(and (= :dev mode) (= :flush stage)) | |
(-> state | |
(flush-dev)) | |
(and (= :release mode) (= :flush stage)) | |
(-> state | |
(output/flush-optimized) | |
(flush-manifest)) | |
:else | |
state | |
)) | |
(comment | |
(shadow.cljs.devtools.api/compile :chrome-ext)) | |
(comment | |
(shadow.cljs.devtools.api/release :chrome-ext)) | |
(comment | |
(shadow.cljs.devtools.api/watch :chrome-ext {:verbose true})) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment