A revised recommended directory structure for use by Janet bundle authors.
Recommended Directory Structure for Janet’s Modern Bundles
In January 2025, I recommended a directory structure for what I would now call Janet’s legacy bundles1. This post is a revised version for modern bundles.
I like making bundles for the Janet programming language. I also like things to be neat and consistent. This has led to me spending too much time thinking about a directory structure I can use for Janet bundles.
For a bundle called <bundle>
2, here’s what I’ve come up with:
<bundle>
├─ bin/
├─ bundle/
│ └─ init.janet
├─ deps/
├─ lib/
├─ res/
├─ src/
├─ test/
├─ info.jdn
├─ init.janet
├─ LICENSE
└─ README.md
To explain:
bin/
: This directory contains the binscripts3 that your bundle will install. In Lemongrass, for example, I provide a CLI utility,lg
, that is installed as part of the bundle.
bundle/
: This directory contains your bundle script (init.janet
) and any helper scripts necessary for managing your bundle. In Predoc, for example, I include a bunch of files from the Spork library so that you can compile thepredoc
binary without anything other than thejanet
binary.4
deps/
: This directory contains vendored dependencies that your bundle uses. I’ve moved to using vendored dependencies as a way to avoid some of the issues that arise when you’re trying to share dependencies across all bundles. If that sounds interesting, check out my bundle manager Jeep. It makes vendoring dependencies as easy as can be.
-
lib/
: This directory contains the Janet code that is used in your bundle. -
res/
: This directory contains resources (images, testing fixtures, helper scripts) that are used by your bundle. -
src/
: This directory contains code (typically C code) that is compiled for use in your bundle. -
test/
: This directory contains tests for your bundle. If you’re using a bundle manager like Jeep it comes with ajeep test
command that runs all the tests in thetest
directory. -
info.jdn
: This file contains metadata about your bundle written as a struct or table in Janet Data Notation (or JDN). The only mandatory key is:name
but you can include other information that might be useful to your bundle script or to consumers of your bundle. -
init.janet
: This file allows a consumer of your bundle to import the top level bindings by simply writing(import <bundle>
) in their Janet file. This is becauseinit.janet
is a ‘magic file’ that Janet’s module loader looks for when it tries to resolve an import call. So using Lemongrass as an example again, a consumer of the bundle can write:(import lemongrass :as lg) (lg/markup->janet "<p>Hello world</p>")
This works even though the
mark-up->janet
function is defined inlib/to-janet.janet
becauseinit.janet
imports this module file and then exports its bindings. -
LICENSE
: The licence that you apply to your bundle. -
README.md
: Your README in Markdown format.
If your bundle doesn’t need any of the above directories, feel free to leave it out.
I’ve been using this structure for the last six months or so and have found it works well. I hope others find it helpful, too! ✺
-
This post uses terminology like legacy bundle and modern bundle that I introduced in this post. ↩
-
Because of how Janet works internally, bundles should not be called ‘bin’, ‘bundle’ or ‘man’. ↩
-
A binscript is a text file that is marked executable and begins with
#!/usr/bin/env janet
. This will cause the OS to run the Janet code in the file through thejanet
binary installed on the user’s system. ↩ -
Seriously, just clone the repo,
cd
into the directory and then runjanet -e '(import ./bundle) (bundle/build (table :info (-> (slurp "info.jdn") parse)))'.
I think that’s pretty fab. ↩