Clean Javascript modules for front-end development
Go to file
Tissevert d6553047d6 Release
* Upgrade code to run with base 4.16
* Stop using cabal-generated module
* Fix sjw not returning with exit code 1 on errors
2023-03-15 12:36:38 +01:00
benchmark Release 2023-03-15 12:36:38 +01:00
demo Fix indentation typo 2020-01-01 19:46:32 +01:00
src Release 2023-03-15 12:36:38 +01:00
test Release 2022-10-29 17:16:09 +02:00
.gitignore First draft defining the structure of the program, needs to implement «imports» and the actual compilation 2019-12-30 12:16:05 +01:00 Release 2023-03-15 12:36:38 +01:00
LICENSE Release 2022-10-29 17:16:09 +02:00 Release 2022-10-29 17:16:09 +02:00
SJW.cabal Release 2023-03-15 12:36:38 +01:00
Setup.hs First draft defining the structure of the program, needs to implement «imports» and the actual compilation 2019-12-30 12:16:05 +01:00
guix.scm Release 2022-10-29 17:16:09 +02:00


The Simple Javascript Wrench is a tool made to "compile" a set of independant Javascript modules into a single "executable" Javascript file which can then be loaded and executed by a browser to animate your web pages. Since no conversion to a different language is performed in the process, "compile" is intended here with the meaning of "assembling together". We will keep using it with this meaning by convenience.

This allows one to write (relatively) clean Javascript and to design libraries to reuse code accross projects. SJW aims at maximal backwards compatibility and requires as little browser extensions as possible. In particular, SJW's modules are not related to ES6' ones. They provide isolation, hiding all the internals of a module from the others, except what the module exposes through the use of a global-scope return statement. The output SJW produces is a single pure-Javascript file wich code runs once the browser has finished loading the page (load event).

To achieve this, it slightly extends the syntax of Javascript to add import statements at the top of the modules to describe their relations. These statements disappear after the files are compiled by SJW and are completely invisible to the web browser eventually loading the script. In this regard, SJW can be thought as "yet another language-specific package manager".

However, SJW is not actually a package manager and only focuses on assembling the modules into one script. To handle packages and dependencies in themselves, it relies instead on guix. In addition to the benefit of not reinventing the proverbial wheel, this brings cool features like reproducibility, controlled environments (making sure your package isn't actually broken but only seems to work because of that file you have locally but forgot to version) and the ability to keep as many versions of the same library as your various projects need while keeping the packages database unduplicated. Finally, it makes it very simple to develop and deploy your own packages without needing to rely on a central repository.


Command-line arguments

The input expected by SJW is the path to a directory containing the modules of the web application to compile. For instance, the demo project distributed in this repository, which Javascript sources are found in the subdirectory src/ can be assembled from the root of this repository with

sjw demo/src/

By default it prints the resulting Javascript code on its standard output.

window.addEventListener('load', function() {

This can be overridden as usual in compilers by using the -o (long form --output) option to set the path of the file to create.

sjw demo/src/ -o main.js

Since the web application SJW compiles is an executable script, it needs an entry point. By default SJW expects to find a module named Main at the root of the web application (as is the case in the demo project). The name of the main module can be set with the -m (--main-is) option like this:

sjw demo/src/ -m MyCustomMain

Libraries don't require any compilation, they are merely copied until being needed in a particular web application. Once they are properly made available to SJW as we will next discuss, they can be included into a project, as with most compilers, with the -I (--include) option:

sjw demo/src -I someAdditionalLib


The arguments to the previous -I option can be any path to a directory where you have a SJW library installed. However, it is common to want to keep all your libraries in a single place and refer to them only by their relative path from this package database. By default, SJW will look for a directory named .sjw in your home directory and use it as the package database when it exists.

ls ~/.sjw
# prints: myLib/
sjw -I myLib src # will work

This can be overridden by setting the $SJW_PACKAGE_DB environment variable to the path of the directory where you keep your libraries. In both cases, the package database consists of only one directory where the librarie's subdirectories are kept.

export SJW_PACKAGE_DB="somewhere/else"
# prints: anotherLib/
sjw -I anotherLib src # will work
sjw -I myLib # no longer works because SJW_PACKAGE_DB hides ~/.sjw

Additionally you may keep your libraries in different, unrelated directories and set $SJW_PATH to the colon-separated (:) list of paths to search.

export SJW_PATH="somewhere/else:${HOME}/.sjw"
sjw -I anotherLib -I myLib src # will work

Note that these act as the roots of separate package databases, they do not point directly to the directories of each individual library. That is, in the previous example, setting SJW_PATH to "somewhere/else/anotherLib:${HOME}/.sjw/myLib" would not have worked. We will call directories like anotherLib and myLib the "name" directories of a library.

About modules and paths

Module names must contain only letters and digits and start with a letter which should by convention be uppercase.

A module defined in file A.js will be available for import from other source files from the symbolic path A (no .js extension). Within a directory named A, a file called B.js is available as the symbolic A.B module. Each subdirectory level adds a new component to the module's path with the exact same name as the subdirectory.

ls demo/src/Math/
# prints: Fibonacci.js
grep Fibonacci demo/src/Main.js
# prints: import get as nth from Math.Fibonacci;

The directory containing a library and used to include it with -I is not the first component of the modules' paths it defines. Going back to the first myLib example, assuming that this library defines a module MyLib.Utils it will have the following structure:

find ~/.sjw/myLib
# prints:
# ~/.sjw/myLib
# ~/.sjw/myLib/MyLib
# ~/.sjw/myLib/MyLib/

By contrast with the "name" directory, we will call the first directory in the modules' path the "modules" directory. In the example right above, myLib is the name directory, and MyLib is its modules directory.


How not to install

Thanks to the way guix works, you don't really need to have SJW installed in your system or user profile to use it. Instead, you need only to package your web application with guix and make sure to add SJW to the native-inputs field, along with the SJW libraries your project uses.

Until SJW makes it to the official guix packages, you'll still need to declare it locally in your file, for instance by wrapping your web application's package within a let block:

  ((SJW (load "path/to/where/you/cloned/this/repos/guix.scm")))

Then you can use absolutely any building software you like, and the sjw command will be available in the build's context (custom script, Makefile… SJW's purpose is not to tell you how to build your web application).

And since the guix package for SJW handles the $SJW_PATH variable, you do not even have to worry about your libraries and can simply add them to the native-inputs for them to be available to the -I option. All a library has to do to for this to work is to have its guix package install its name directory to the path lib/SJW in the resulting guix store directory.

Trying it

You may still want to interact directly with the sjw command on your favorite shell's command line. It's useful when you're still discovering SJW and want to play with it while writing the building rules of your web application (for instance, a Makefile). You can simply test SJW from this repository by issuing:

guix shell -f guix.scm

How to install

Putting SJW in your permanent guix profile

If you want SJW installed "once and for all" to have it available whenever you open a new shell, you can use the regular guix commands, such as:

guix install -f guix.scm

for instance, or to retain a more declarative approach, use the load function as above to make the package available within a manifest or even an operating system declaration if you need it installed system-wide for some reason.

When guix is not an option

SJW can still be compiled and installed as a regular haskell package with cabal:

$ cabal new-update
$ cabal new-install SJW

As in that case guix won't be there to take care of your SJW libraries either, you'll have to handle them yourself. But since no special action is needed on them before compiling a web application, this requires only the most simple file-management tools available such as ls, cp or rm from your usual shell or even moving things around in your graphical file browser. Make sure you copy the name directory and not the modules directory to ~/.sjw (or $SJW_PACKAGE_DB). If for instance the myLib package above was distributed in a repository with the following structure:

ls path/to/myLib
# prints: src/ README LICENSE CHANGELOG guix.scm
ls path/to/myLib/src
# prints: MyLib/

Then in this case myLib would simply installed by doing:

cp -R path/to/myLib/src ~/.sjw/myLib


Your contribution is welcome ! Since this is a private forge where you don't have an account, just send a git patch (see git format-patch) to my email address (look at the author's address of the first commit in the history from your local clone of this repos).