SJW/README.md

271 lines
10 KiB
Markdown

# SJW
- [Usage](#usage)
- Install
+ [How *not* to install](#how-not-to-install)
+ [How to install](#how-to-install)
- [Contributing](#contributing)
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](https://developer.mozilla.org/en-US/docs/Web/API/Window/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`](https://guix.gnu.org/). 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.
## Usage
### 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
```sh
sjw demo/src/
```
By default it prints the resulting Javascript code on its standard output.
```javascript
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.
```sh
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:
```sh
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:
```sh
sjw demo/src -I someAdditionalLib
```
### Libraries
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.
```sh
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.
```sh
export SJW_PACKAGE_DB="somewhere/else"
ls $SJW_PACKAGE_DB
# 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.
```sh
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.
```sh
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:
```sh
find ~/.sjw/myLib
# prints:
# ~/.sjw/myLib
# ~/.sjw/myLib/MyLib
# ~/.sjw/myLib/MyLib/Util.sh
```
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.
## Install
### 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](https://guix.gnu.org/fr/manual/devel/en/html_node/package-Reference.html#package-Reference),
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:
```guile
(let
((SJW (load "path/to/where/you/cloned/this/repos/guix.scm")))
(package
))
```
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](https://guix.gnu.org/fr/manual/devel/en/html_node/Search-Paths.html#Search-Paths),
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](#about-modules-and-paths) 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:
```sh
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:
```sh
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](https://guix.gnu.org/fr/manual/devel/en/html_node/Writing-Manifests.html#Writing-Manifests)
or even an [operating system
declaration](https://guix.gnu.org/fr/manual/devel/en/html_node/operating_002dsystem-Reference.html#operating_002dsystem-Reference)
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`](https://www.haskell.org/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:
```sh
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:
```sh
cp -R path/to/myLib/src ~/.sjw/myLib
```
## Contributing
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`](https://git-scm.com/docs/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).