How to Set Up a Shadow CLJS Project From Scratch
Enough talking, let's create a new repository and set up a project from scratch, using Shadow CLJS.
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo 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.
Get unlimited access to Tinycanva: Clojure for React Developers, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

[00:00 - 00:06] In the last few lessons we learned about the language and build tools. We are now ready to create our first project.
[00:07 - 00:17] You might find typing out S expressions or that first. But don't worry, we'll soon learn how to use PAR edit, a tool to help manage S expressions.
[00:18 - 00:24] All projects start with the configuration file. The name and syntax of this file is different for different build tools.
[00:25 - 00:30] However, the ideas remain the same. If you understand one build tool, you can easily use all others.
[00:31 - 00:39] For shadow CLJS or shadow, the file is called shadowCLJS.edn. EDN stands for extensible data notation.
[00:40 - 00:46] It's a format similar to JSON. Shadow projects are a mix of npm and closure script.
[00:47 - 00:53] Hence, you'll need a package JSON file as well. This allows us to install npm packages directly.
[00:54 - 01:09] There are multiple tools to scaffold your project, but to gain insight into how things work, we'll scaffold the shadow project manually. Getting used to shadow configuration will help us later when we start using advanced features like code splitting.
[01:10 - 01:18] After we work through setting up projects manually, we learn about the tools that can help automate this process. Let's create an empty directory.
[01:19 - 01:24] This directory will hold our project. In our case, the name is going to be first project.
[01:25 - 01:34] The name of the top post directory is conventionally kabab-cased. Most closure libraries hosted on CLJS follow this convention.
[01:35 - 01:46] Now let's go inside this directory and create the shadow configuration file. You can now open this folder in an editor of your choice and we can start building our initial configuration.
[01:47 - 01:55] Source parts literally specify the directories your source code lives in. In our example, we have added two directories, SRC and Resources.
[01:56 - 02:13] The SRC or Source folder is for closure script code, whereas the Resources folder holds supporting assets like configuration files, images, style sheets, etc. These names could be anything, however having an SRC folder is a very strong convention.
[02:14 - 02:29] The directory named resources is used by Java's I/O module and by extension it is also used by Closier's I/O module. It is highly recommended to use conventional names until and unless you know what you are doing.
[02:30 - 02:39] Some large projects might have code in multiple dialects of closure. One popular combination is closure script on the front end and closure on the back end.
[02:40 - 02:50] In cases where multiple dialects exist simultaneously, the SRC part could be set to SRC CLJS and SRC CLJS. Again, this is just a convention.
[02:51 - 03:02] If you follow the convention, your closure code goes to SRC CLJS folder and closure script code goes to SRC CLJS folder. However, you can break the convention if you wish to.
[03:03 - 03:14] It is legal to define the source part as SRC CLJS and put closure script code inside that folder. The build tool will not complain as long as the file exists.
[03:15 - 03:20] We have defined the source parts, but these folders do not exist yet. Let's create them.
[03:21 - 03:34] The dependencies key value pair is used to define the JVM dependencies of the project. You might be wondering why Shadow, a build tool for closure script, has support for JVM dependencies.
[03:35 - 03:50] This is because you can write closure macros in JVM closure and invoke them in closure script. It also helps with libraries that are written in both closure and closure script and are distributed via Maven or closures.
[03:51 - 04:05] Shadow also has support for NPM dependencies, but those go to the standard package JSON file which we will create in a while. The dependencies key in the case of Shadow refers to JVM dependencies hosted on Maven.
[04:06 - 04:19] Since we don't have any external library for our first project, the value is an empty vector. In case we had some dependencies, each element of the vector would be a two element vector where the first element is the name of the package.
[04:20 - 04:25] And second element is the version number. N-repel stands for Network Repel.
[04:26 - 04:34] The term Repel stands for read, eval, print and loop. Repel is often used interchangeably with the term shell.
[04:35 - 04:45] And in some sense they are similar, but in terms of implementation, the Python shell for example is very different from the closure Repel. You have seen a Repel already.
[04:46 - 04:54] When you type CLJ in your terminal, you start the default closure Repel in the username space. This Repel is shipped with depth CLI.
[04:55 - 05:04] Shadow too ships with a Repel, so does every other build tool. Shadow's Repel runs as a service and can be connected to an interface.
[05:05 - 05:12] In most cases, the text editor connects to this Repel. This connection offers multiple IDE-like features.
[05:13 - 05:23] The N-repel aspect of closure applies only to LISPs and might not make sense immediately. But it's one of the tools that make closure development rapid.
[05:24 - 05:34] It is so important that we have dedicated a full chapter to it. For now, we just need to know that the example configuration will start the N- repel on port 9000.
[05:35 - 05:41] And any capable client can connect to it. Do you remember installing the N-repel packages for your editor?
[05:42 - 05:50] Calva for VS Code, cursive for IntelliJ and CIDR for emacs? These are the clients that will connect to the N-repel.
[05:51 - 06:02] Shadow can be used to target multiple JavaScript outputs. You can produce code suitable to be shipped as an NPM module, code suitable to be used in a browser or something else.
[06:03 - 06:11] The build option helps you configure just that. In our example, we use Shadow to produce code targeting the Node runtime.
[06:12 - 06:19] That is NodeScript. We also configured to output the final build file to build NodeScript/code.js.
[06:20 - 06:29] Shadow will automatically create this directory if it doesn't exist. The NodeScript target is a good option for tasks that need to run from the command line.
[06:30 - 06:44] The main keyword takes a namespace qualified function and executes it when the script is run in the Node runtime. NSqualified is just a fancy way to refer to a function defined in a specific namespace.
[06:45 - 06:52] The output produced by Shadow can be used directly by Node. We can call the output script like so.
[06:53 - 07:06] The arguments passed in the CLI will be passed to the main function defined in Shadow configuration. Shadow's build mechanism is versatile and that configuration is just the tip of the iceberg.
[07:07 - 07:15] We will explore this more as our projects grow in complexity. We configured Shadow's main but we haven't created the namespace yet.
[07:16 - 07:25] Let's create the file to hold this namespace and define the main function in it . There are some rules to determine the location of this file and we will get to them in a moment.
[07:26 - 07:39] For now, we just need to create a file named code.cljs in src/firstproject directory. The NS operation lets you define a new namespace.
[07:40 - 07:49] There are certain rules that we need to keep in mind while defining namespaces. For now, we just need to know that the namespace for this file is firstproject.
[07:50 - 07:59] core. Namespaces let you refer and import functionality defined in other files. In our config, we refer to the main function.
[08:00 - 08:24] If we had another namespace, say firstproject.utils, we can use the functions defined there in the code namespace by using the require argument for this macro. With the require statement, any function x defined in firstprojects.utils will be available as u.x in the firstproject.core namespace.
[08:25 - 08:30] Namespaces are closely connected to source parts. They also need to follow some naming rules.
[08:31 - 08:58] In our example, when we refer to firstproject.core/main, we are referring to a directory named first_project that exists on the source part, i.e. at src/firstproject. We also expect a file named code.cljs that exists inside the firstproject directory and a function named main defined in code.cljs file.
[08:59 - 09:13] The namespace definition using the ns function uses kabab case, but the respective file or folder uses snakecase. Here are some examples of namespaces and their respective paths on disk.
[09:14 - 09:29] The namespaces rules for namespace are common for all build tools. Since namespaces expect a file to exist on a particular path, can you think of another location that is a valid path for firstproject.core?
[09:30 - 09:39] If you thought resources/firstproject/code.cljs, you are right. The build tools just need the code to be available on the source path.
[09:40 - 09:52] Since resources is a source path, shadow won't complain. However, this will break the convention of using src directory for source code and confuse everyone on your team, so please don't do it.
[09:53 - 09:57] We have defined the namespace already. Now let us also define the main function.
[09:58 - 10:11] This main function will take a variable number of arguments and just return the string hello world. The ampersand character in the argument list denotes the fact that this function can accept any number of arguments.
[10:12 - 10:18] Now let's set up the packet JSON and a JavaScript project. Shadow allows you to import NPM modules directly.
[10:19 - 10:29] In fact, shadow itself is distributed as an NPM module. We can use the yarn init or NPM init command to create a packet JSON for us.
[10:30 - 10:43] Once done, we can install shadow by using yarn as shadowCLjs or NPM installed shadowCLjs. At the time of recording this video, the latest table version is 2.10.15.
[10:44 - 10:55] To check the installation, you can pull up shadow's health menu and you should be presented with the list of API methods. In this lesson, we learned how to set up a shadow project from scratch.
[10:56 - 11:05] We walked through some shadow configuration parameters and understood the rules of namespacing. We also set up an NPM project and installed shadow.
[11:06 - 11:11] In the next chapter, we learned more about shadow's API and run our first project.