{"id":21696202,"url":"https://github.com/bendyworks/conwip-modules","last_synced_at":"2025-04-12T12:07:07.666Z","repository":{"id":62432139,"uuid":"86177645","full_name":"bendyworks/conwip-modules","owner":"bendyworks","description":"Conwip Modules helps automate dynamically loading ClojureScript modules ","archived":false,"fork":false,"pushed_at":"2017-09-25T16:08:33.000Z","size":26,"stargazers_count":18,"open_issues_count":0,"forks_count":1,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-03-26T06:51:06.455Z","etag":null,"topics":["clojurescript","modules"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bendyworks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-03-25T18:00:51.000Z","updated_at":"2024-11-25T03:08:59.000Z","dependencies_parsed_at":"2022-11-01T20:46:41.210Z","dependency_job_id":null,"html_url":"https://github.com/bendyworks/conwip-modules","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendyworks%2Fconwip-modules","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendyworks%2Fconwip-modules/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendyworks%2Fconwip-modules/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bendyworks%2Fconwip-modules/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bendyworks","download_url":"https://codeload.github.com/bendyworks/conwip-modules/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248565062,"owners_count":21125416,"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":["clojurescript","modules"],"created_at":"2024-11-25T19:18:53.021Z","updated_at":"2025-04-12T12:07:07.643Z","avatar_url":"https://github.com/bendyworks.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Conwip Modules\n## Dynamic Module Loading for ClojureScript\n\nConwip Modules allows you to dynamically load ClojureScript modules from the client side. This is a wrapper around the\nGoogle Closure Library's Module Manager `goog.module.Manager`.\n\n### Leiningen / Boot\n\n```clojure\n[conwip.modules \"0.1.0\"]\n```\n\n### Terminology\n\n**Development** refers to ClojureScript compiled with `:none` or `:whitespace` optimizations\n\n**Production** refers to ClojureScript compiled with `:simple` or `:advanced` optimizations\n\n### Basic Example\n\nOur example application has two modules `:dev` the root module and `:extra` the module to be loaded dynamically\n\n```clojure\n:modules {:extra {:output-to \"path/to/extra.js\"\n                  :entries #{\"my.app.extra\"}\n                  :depends-on #{:dev}}\n          :dev   {:output-to \"path/to/dev.js\"\n                  :entries #{\"my.app.core\"}}}\n```\n\nThe `my.app.core` namespace looks like this\n\n```clojure\n(ns my.app.core\n  (:require [conwip.modules :as cm]))\n\n(cm/load-module \"extra\" (fn [] (.log js/console \"The extra module has loaded\")))\n```\n\nIt loads the `extra` module without any extra boiler plate and can dynamically load `extra` in both Development and\nProduction\n\nBelow is the ClojureScript and compiler options plumbing needed to get this working\n\n**ClojureScript Side**\n\nClojureScript modules need to be marked as loaded with `conwip.modules/set-loaded!`. To do this go into a namespace in\none of your modules, in our example case the `my.app.extra` namespace of the `extra` module.\n\n```clojure\n(ns my.app.extra\n  (:require [conwip.modules :as cm]))\n\n(cm/set-loaded! \"extra\")\n````\nThe `set-loaded!` call is needed to relay that a module has been loaded to the Google Closure Library module manager `goog.module.ModuleManager`\n\n**Module Information Compiler Option**\n\nConwip Modules adds a new compiler option `:module-info` to pass in ClojureScript module dependencies.  To dynamically\nload ClojureScript Modules the module URI and dependency information is needed.  Module URI information is passed in\nvia `:module/uris` and module dependencies via `:module/deps`. Module id's need to be in passed as strings not  keywords.\n\n```clojure\n:module-info {:module/uris {\"extra\" \"path/to/extra.js\"\n                            \"dev\" \"path/to/dev.js\"}\n              :module/deps {\"extra\" []\n                            \"dev\" []}}\n```\n\n**Development Compiler Options**\n\nIn development we need to add all of the dynamically loaded module namespaces to the `:preloads` compiler option. There\nis only the `extra` module so our `:preloads` needs to have `my.app.extra` in it.\n\n```clojure\n:preloads '[my.app.extra]\n```\n\nPutting the dynamically loaded module namespaces in `:preloads` allows development to work the same as production\nwithout having to add unnecessary boilerplate to our ClojureScript code.\n\n**Production**\n\nTo enable Conwip Modules in production set `conwip.modules.PRODUCTION` to `true` through [`:closure-defines`](https://clojurescript.org/reference/compiler-options#closure-defines)\n\n```clojure\n:closure-defines {'conwip.modules.PRODUCTION true}\n```\n\n### Usage\n\nAll functionality is in the `conwip.modules` namespace\n\n**`(loaded? module-id)`**\n\n```clojure\n(if  (loaded? \"my-module\")\n  \"module loaded\"\n  \"module has not loaded\")\n```\n\nChecks if a module has been loaded or not\n\n**`(get-module-info module-id)`**\n\n```clojure\n(let [module-info (get-module-info \"my-module\")\n  {:loaded? (.isLoaded module-info)\n   :uris (.getUris module-info)})\n```\n\nReturns a `goog.module.ModuleInfo` object for the ClojureScript module\n\n**`(load-module module-id callback)`**\n\n```clojure\n(load-module \"my-module\" (fn [] (.log js/console \"The module has loaded\")))\n```\n\nLoads a ClojureScript module and fires a callback function when finished. No arguments are passed to the callback function\n\n**`(set-loaded! module-id)`**\n\n```clojure\n(set-loaded! \"my-module\")\n```\n\nMark a module as loaded. If this is not done then `loaded?` will never return `true` and the callback for `load-module`\nwill never fire.\n\nFor a module with multiple namespaces like\n\n```clojure\n:modules {:colors {:output-to \"path/to/colors.js\"\n                   :entries #{\"my.app.red\" \"my.app.blue\"}}}\n```\n\n`set-loaded!` only needs to be called in one of the namespaces like so\n\n```clojure\n(ns [my.app.blue]\n  (:require [conwip.modules :as cm]))\n\n(cm/set-loaded! \"colors\")\n```\n\nadding `set-loaded!` to the `my.app.red` namespace will not cause any harm it will just be redundant.\n\n### Compiler settings\n\n**`:module-info`**\n\nThe `:module-info` compiler option is required for Conwip Modules to work. Module URI and dependency information is needed\nfor the Google Closure Library Module Manager to work properly. Both module URI's (through `:module/uris` and module\ndependencies (through `:module/deps` are required\n\n```clojure\n:module-info {:module/uris {\"red\"    \"path/to/red.js\"\n                            \"colors\" \"path/to/colors.js\"\n                            \"core\"   \"path/to/core.js\"}\n              :module/deps {\"red\"    [\"colors\"]\n                            \"colors\" []\n                            \"core\"   []}}\n```\n\n**`:preloads`**\n\nIn development all namespaces in modules need to be in the `:preloads` compiler option.\n\nFor these modules\n\n```clojure\n:modules {:colors {:output-to \"path/to/colors.js\"\n                   :entries #{\"my.app.red\" \"my.app.blue\"}}\n          :fruit   {:output-to \"path/to/fruit.js\"\n                    :entries #{\"my.app.apple\" \"my.app.orange\"}}}\n```\n\nThe following `:preloads` are needed\n\n```clojure\n:preloads '[my.app.red my.app.blue my.app.apple my.app.orange]\n```\n\n**`:closure-defines`**\n\nSetting the define `conwip.modules.PRODUCTION` to true turns module loading from development to production\n\n```clojure\n:closure-defines {'conwip.modules.PRODUCTION true}\n```\n\n### Code Splitting ClojureScript vs Webpack\n\nDividing your application into modules, aka Code Splitting, is handled very differently in Google Closure than in Webpack.\n\nIn Webpack code splits are defined inside the code through, `import` (or deprecated commands `System.import` or `require.ensure`). The `import` command dynamically loads the requested module and returns a promise. For more information see the [Webpack Code Splitting Async Guide](https://webpack.js.org/guides/code-splitting-async/).\n\nClojureScript Code Splitting uses Google Closure Code Modules which does not require any split points to be defined inside the code. Instead you define what namespaces will be in each modules and the dependency graph and Google Closure takes care of the rest (see [here](https://clojurescript.org/reference/compiler-options#modules) details). Google Closure moves code between modules for optimal splits using a technique called [cross moudle code motion](http://swannodette.github.io/2015/02/23/hello-google-closure-modules).  ClojureScript has been [tuned](http://swannodette.github.io/2015/04/07/in-stillness-movement) to take full advantage of this.\n\n### Node Support\n\nDynamically loading modules inside a node application is not feasible with the current Google Closure Library [Module Loader](https://google.github.io/closure-library/api/goog.module.Loader.html). The module loader was designed for dynamic loading in the browser and follows this algorithm to load modules\n\n- Get all the uris (in dependecy order) for a given module\n- Retrieve the raw JavaScript (text) of all the files pointed to via the uris\n- Load the JavaScript into the global scope through the `eval` function\n\nThis is incompatible with the way Node's module scope works. Each module would need to correctly import it's dependencies and export all variables it created. See this Google Closure issue for more details google/closure-compiler#2406\n\n### Potential Deprecation Notice\n\nThe current functionality of `conwip-modules` may be getting rolled into ClojureScript under a compiler option of `:module-loader` (see ClojureScript ticket [2077](https://dev.clojure.org/jira/browse/CLJS-2077)). This *may* be similar to how [Shadow CLJS](https://github.com/thheller/shadow-cljs/wiki/ClojureScript-for-the-browser) currently works.\n\nSee [cljs-dev 2017-06-09](https://clojurians-log.clojureverse.org/cljs-dev/2017-06-09.html) and [cljs-dev 2017-06-10](https://clojurians-log.clojureverse.org/cljs-dev/2017-06-10.html) for the relevant discussions. The ClojureScript tickets that will make this possible are [2076](https://dev.clojure.org/jira/browse/CLJS-2076), [2077](https://dev.clojure.org/jira/browse/CLJS-2077), and\n[2078](https://dev.clojure.org/jira/browse/CLJS-2078).\n\n### FAQ\n\n**Why is Module X not loading?**\n\nThere could be several reasons why a module is not loading\n- The module's information is not in the `:module-info` compiler option\n- The module's URI is incorrect in `:modules/uris`\n- `set-loaded!` was not called in any of the module's namespaces or was called with the incorrect module id\n- You are working in development and did not add the module's namespaces to `:preloads`\n- You are working in production and did not set the define `conwip.modules.PRODUCTION` to `true`\n\n### Future Features\n\nThe `:module-info` compiler option could be completely removed by using information from the `:modules` compiler option.\nThere are several edge cases to consider for this to be a viable option.\n\nRemoving the need for `set-loaded!`would require integration with ClojureScript.\n\n### Thanks\n\n[Bendyworks](http://bendyworks.com/) for supporting the development of Conwip Modules\n\n[Allen Rohner](https://github.com/arohner) for doing much of the ground work for [dynamic modules](https://rasterize.io/blog/cljs-dynamic-module-loading.html)\n\n[Antonin Hildebrand](https://github.com/binaryage) for his ideas on how to import [arbitrary compiler options](https://github.com/binaryage/cljs-devtools/blob/master/src/lib/devtools/prefs.clj)\n\n### Copyright and  License\n\nCopyright © 2017 Bendyworks\n\nDistributed under the Eclipse Public License either version 1.0 or (at your option) any later version.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbendyworks%2Fconwip-modules","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbendyworks%2Fconwip-modules","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbendyworks%2Fconwip-modules/lists"}