{"id":18847126,"url":"https://github.com/mhuebert/shadow-env","last_synced_at":"2025-04-14T08:09:23.613Z","repository":{"id":50192620,"uuid":"242745373","full_name":"mhuebert/shadow-env","owner":"mhuebert","description":"cljc environment variables with shadow-cljs","archived":false,"fork":false,"pushed_at":"2020-02-25T17:19:37.000Z","size":6,"stargazers_count":20,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-14T08:09:09.507Z","etag":null,"topics":["clojure","shadow-cljs"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mhuebert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-02-24T13:37:29.000Z","updated_at":"2024-02-20T17:37:49.000Z","dependencies_parsed_at":"2022-08-25T10:30:54.553Z","dependency_job_id":null,"html_url":"https://github.com/mhuebert/shadow-env","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhuebert%2Fshadow-env","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhuebert%2Fshadow-env/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhuebert%2Fshadow-env/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mhuebert%2Fshadow-env/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mhuebert","download_url":"https://codeload.github.com/mhuebert/shadow-env/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248843856,"owners_count":21170492,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","shadow-cljs"],"created_at":"2024-11-08T03:05:23.289Z","updated_at":"2025-04-14T08:09:23.581Z","avatar_url":"https://github.com/mhuebert.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# shadow-env\n\n![Version Badge](https://img.shields.io/clojars/v/mhuebert/shadow-env)\n\nClojure(Script) environment injection with shadow-cljs live-reload support\n\n----\n\nI often set up applications to read config from EDN files using something like [juxt/aero](https://github.com/juxt/aero), and run into a couple minor issues:\n\n- how to get my cljs app to pick up changes in these EDN files without recompiling from scratch\n- how to cleanly expose environment to both Clojure and ClojureScript while avoiding secrets being exposed in the wrong places\n\nThis small library provides:\n\n- automatic propagation of changes made to environment files on each recompile\n- explicit \u0026 simple control of what is exposed to ClojureScript vs Clojure\n\n## Usage\n\nin a shadow-cljs.edn build:\n\n```clj\n:build-hooks [(shadow-env.core/hook)]\n```\n\nin your app's env namespace:\n```clj\n(ns my-app.env\n  (:refer-clojure :exclude [get])\n  (:require [shadow-env.core :as env]))\n\n;; write a Clojure function that returns variables to expose to :clj and :cljs.\n;; the function must accept one variable, the shadow-cljs build-state\n;; (which will be `nil` initially, before compile starts)\n#?(:clj\n    (defn read-env [build-state]\n       {:common \u003cexposed everywhere\u003e\n        :clj    \u003cexposed to Clojure\u003e\n        :cljs   \u003cexposed to ClojureScript\u003e})\n\n;; define \u0026 link a new var to your reader function.\n;; you must pass a fully qualified symbol here, so syntax-quote (`) is useful.\n;; in this example I use `get` as the name, because we can call Clojure maps\n;; as functions, I like to alias my env namespace as `env`, and (env/get :some-key)\n;; is very readable.\n(env/link get `read-env)\n```\n\nusage elsewhere:\n```clj\n(ns my-app.client\n  (:require [my-app.env :as env]))\n\n(env/get :my/attribute)\n\n```\n\n## How it works\n\nEvery time your app recompiles, we\n\n1) re-bind the `:clj` environment to the var you create using `env/link` (using `alter-var-root`), and\n2) if the `:cljs` environment has changed, we invalidate the env's namespace so that it + its dependents will recompile.\n\n## Example usage with aero\n\nI like to use [juxt/aero](https://github.com/juxt/aero) to read EDN files from disk.\nI use a handful of different files to separate config that is server-only, client-only,\ncommon, and secret (ie. not in source control).\n\n```clj\n#?(:clj\n   (defn read-env [build-state]\n     (let [aero-config {:profile (or (System/getenv \"ENV\") :dev)}\n           [common\n            server\n            secret\n            client] (-\u003e\u003e [\"common.edn\"\n                          \"server.edn\"\n                          \".secret.server.edn\"\n                          \"client.edn\"]\n                         (map #(some-\u003e (io/resource %)\n                                       (aero/read-config aero-config))))]\n       {:common common\n        :clj (merge server secret)\n        :cljs client})))\n\n(env/link get `read-env)\n```\n\n## Dead code elimination (DCE)\n\nClojureScript can remove unused code automatically based on compile-time constants.\nFor this to work, we can't read from our environment map at runtime - instead, we can\nwrite a tiny macro that runs at compile-time and reads from our env:\n\n```clj\n(env/link get `read-env)\n\n(defmacro get-static [k]\n  (clojure.core/get get k))\n\n;; ClojureScript code can call (get-static :some/key) and the value will be replaced\n;; at compile-time, enabling DCE\n```\n\n# Dev\n\n## Releases\n\nTo tag a version for release:\n\n```\nclj -A:release tag \u003cpatch, minor, or major\u003e\n```\n\nTo deploy:\n\n```\nclj -A:release\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhuebert%2Fshadow-env","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmhuebert%2Fshadow-env","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmhuebert%2Fshadow-env/lists"}