Getting Familiar With The Shadow CLJS API

Our first project is now in place. In this chapter, we'll learn how to build and run it. We'll also explore some other build targets and explore Shadow with Reagent, a ClojureScript wrapper to React.

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

This lesson preview is part of the Tinycanva: Clojure for React Developers course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

This video is available to students only
Unlock This Course

Get unlimited access to Tinycanva: Clojure for React Developers, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Tinycanva: Clojure for React Developers
  • [00:00 - 00:08] Shadow offers many helpers to compile and develop your app, but we only need to care about three methods. These are compile, watch and release.

    [00:09 - 00:21] Compile and watch are similar in scope. Both of them convert closure script code to JavaScript code based on the build target. The only difference is that watch recompiles each time a file on source part changes.

    [00:22 - 00:31] The JavaScript code produced by both compile and watch is inspectable. That means it is not magnified or minified and can be read by you.

    [00:32 - 00:39] The release method bundles the code for production. It applies advanced optimizations, minification and aglification.

    [00:40 - 00:53] Closure script relies on Google's closure compiler GCP to transform CLJS to JavaScript. GCP is Google's in-house alternative for Webpack and was open sourced over 10 years ago.

    [00:54 - 01:00] All major Google Apps including Gmail rely on GCP. We are ready to start our first build.

    [01:01 - 01:09] We have set up our configuration, install Shadow and have created our namespace . Our main function is designed to print hello world.

    [01:10 - 01:18] To start ShadowWatch, we need to issue the watch command with a build target. This can be done using Yarn ShadowCLJS watch script.

    [01:19 - 01:25] Script is the build that we have defined in shadow configuration. The colon before script is optional.

    [01:26 - 01:38] When you run the watch command for the first time, Shadow will install the required dependencies and start a server. You can see that Shadow compiled 76 files and started an end-repel on port 9000 .

    [01:39 - 01:44] It also started a server on port 9630. We have learned about end-repel already.

    [01:45 - 01:58] This other server is a web-based dashboard that gives you an overview of the builds and runtimes. At this point, your closure script code is compiled to a node script in build node script slash code.js.

    [01:59 - 02:11] You can inspect this code as it's just plain JavaScript, but it might not make sense. The node script target bundles everything into a single file, which allows for easy distribution at the cost of readability.

    [02:12 - 02:18] However, we can simply run the compiled node script. You might notice that the process doesn't terminate.

    [02:19 - 02:28] This is because Shadow is in watch mode. Watch mode allows for some hooks that can execute your code in node environment each time you make a change.

    [02:29 - 02:37] For now, we can just close this process by pressing Ctrl C or Command C. Now let's go ahead and change course CLJS.

    [02:38 - 02:45] Let's print hello world 2 instead of just hello world. You should notice that as soon as you save the file, Shadow rebuilds the script .

    [02:46 - 02:54] So far, the scripts we have built with watch were for development environment. The release command builds the script in production mode.

    [02:55 - 03:04] It uglyifies, minifies and applies optimizations to JavaScript output. To build our script target, we can run the release command with script argument .

    [03:05 - 03:11] Again, the colon is optional. If you try and run the script in node again, the process will terminate.

    [03:12 - 03:19] This is because the script was packaged for production use. Typing the entire build command can sometimes feel tedious.

    [03:20 - 03:29] It's my personal practice to add these commands to npm scripts so they become easier to use. We can add the following short hands to package JSON script configuration.

    [03:30 - 03:44] Now instead of using yarn, shadow, watch script, we can use yarn w colon script , which is more pleasant than the original version. Setting up a new closure script project with shadow is simple but not easy.

    [03:45 - 03:53] You need to create a configuration, set up a directory structure and set up some build targets. Create CLJS app can help ease this process.

    [03:54 - 04:05] This tool is similar in scope to create React app. To create a new project with the tool, you can use it with yarn as yarn create CLJS app and name of the project.

    [04:06 - 04:26] This will create a shadow config and npm config, install all the dependencies, create a directory structure and also set up a structure for tests for you. In the default configuration, the project created by create CLJS app targets the browser and includes the reagent, a closure script wrapper for React.

    [04:27 - 04:36] Our example so far only used the node script target. The other notable difference is usage of a public directory instead of resources.

    [04:37 - 04:49] This is a good way to get started once you understand the ecosystem and want to bootstrap projects quickly. The app generated using create CLJS app is a React application and runs in the browser.

    [04:50 - 05:05] It uses reagent under the hood and sets up a basic SPA. If you run yarn start in your newly minted second project, shadow will start a dev server for you on port 3000 and end repel on port 3333, assuming you didn't change any config.

    [05:06 - 05:18] Now if you navigate to localhost 3000, you would see a simple React component. The following configuration is responsible for compiling the app to run in the browser.

    [05:19 - 05:28] Feel free to poke around the source and check app.core/mainfunction. We will walk through the configuration next but learn about code in later sections.

    [05:29 - 05:44] The keyword browser as target requests shadow to produce JavaScript code suitable for running in a browser. Shadow relies on GCP which can produce ES3, ES5, ES6 and a few other versions of browser ready JavaScript.

    [05:45 - 06:00] The output directory is where all your compiled files reside. Unlike the node script target where we specify an output file, the browser target needs us to specify a directory because more than one files are created.

    [06:01 - 06:25] The exact path defines the location of compiled resources in relation to the web server. For example, if your web server is configured to server static website on https example.com from root example.com public and you compiled your code to public/js directory, then your asset path relative to the web server is /js.

    [06:26 - 06:41] If a file called public index.html needs to include a compiled script, then it can do so at example.com/js/main.js. Modules configuration is a map of outputs that need to be produced.

    [06:42 - 06:49] This helps with code splitting. In the example above, we have configured shadow to generate a single module main.

    [06:50 - 07:03] This will lead to generation of a file called main.js in the output directory. The iNet-FN option specifies a namespace qualified function that should run as soon as the module is loaded.

    [07:04 - 07:15] For example, in a React app, we might want to call render as soon as the upload s. As the size of your app grows, you can split your output into multiple modules.

    [07:16 - 07:24] Modules can also depend on each other. Shadow will compute the dependency graph and will try to push most code to the edges.

    [07:25 - 07:33] The module configuration will become more easy to grasp as we use it. For now, just consider it as an option that puts your code into different baskets.

    [07:34 - 07:47] Browser-based JavaScript needs some HTML. It might not be evident from the configuration, but if you check the public folder generated by create CLJSA, you'd find an index.html file.

    [07:48 - 07:59] This file includes main.js which was generated as a result of main module configuration. Did you notice the HTTP server running on port 3000?

    [08:00 - 08:12] This was the code C of dev-http-shadow-config. Dev-http takes a port and a root directory and serves the content of that directory with index.html as the entry point.

    [08:13 - 08:18] This ties the entire process in a loop. You start watching the app target using yarn-start.

    [08:19 - 08:28] Shadow compiles CLJSA's code to JavaScript and outputs to public JS directory. Dev server serves this directory on port 3000.

    [08:29 - 08:42] We open the browser to check the output and when you make changes, shadow recompiles our application. In conclusion, we ran our first application and understood Shadow's fundamental APIs.

    [08:43 - 08:58] We learned about automating the bootstrap process using create ShadowCLJSA app and studied Shadow's browser target. We also saw an auto-generated React application built using reagent and established a dev workflow to tie everything together.