Robin Marillia
Back to home

Creating jSimple

See on GitHub

I created a small javascript library that aims to ease the creation of user experiences while using fullstack framworks such as Django or Laravel.

Why jSimple?

It is mostly an experiment. First, I wanted to learn how to create, publish and maintain a javascript package.

The initial goal was to create a modern jQuery as I came to the realisation that it did a lot of nice things. For example I still find its way of querying dom element more elegant than the standard document.querySelector()

I also wanted to facilitate the way we add interactions in templates in frameworks such as Django which we use heavely at Hello watt. For codebases that are not fully javascript it seems that the current approach is either:

I thought that we might need something to add some fun in the second case.

However the current result is just a fun experiment and should not be used in production.

How is it orchestrated

The repository is a monorepo that uses pnpm workspaces and Turborepo for the build step. I chose them because I heard a lot of good things about them but I found that it is pretty similar to npm workspaces for a small project like that.

Get started

To install the core library enter the following command:

npm install @jsimple/core

To install the additional dom rendering library enter the following command:

npm install @jsimple/dom-render

To install the additional custom element library enter the following command:

npm install @jsimple/custom-element

If you need to fetch data and cache it the fetcher package is here for you:

npm install @jsimple/fetcher

Example

Core

The core library includes a reactive system that makes building dom interaction easier.

// Javascript file
import $ from "@jsimple/core";

const btn = $.select("#btn");
const container = $.select("#container");
const [isOpen, setIsOpen] = $.signal(false);

$.effect(() => {
  container?.html(`
    <div style='display: ${isOpen() ? "block" : "none"}'>
      Lorem ipsum
    </div>
  `);
});

btn?.onClick(() => {
  setIsOpen(!isOpen());
});
<!-- HTML file-->
<button id="btn" type="button">open</button>
<div id="container"></div>

You can read more on this package here: @jsimple/core

DOM-render

Using the dom-render package you can simplify this markup like the following example. It is heavily inspired by Alpinejs.

<!-- HTML file-->
<div data-define="app-component">
  <button id="btn" type="button" $click="setIsOpen(!isOpen())">open</button>
  <div $display="isOpen()">lorem ipsum</div>
</div>
// Javascript file
import $ from "@jsimple/core";
import { DOMRender } from "@jsimple/dom-render";

function AppComponent() {
  const [isOpen, setIsOpen] = $.signal(false);
  return { isOpen, setIsOpen };
}

run([AppComponent]);

or without the run function:

// Javascript file
import $ from "@jsimple/core";
import { DOMRender } from "@jsimple/dom-render";

const [isOpen, setIsOpen] = $.signal(false);
DOMRender({ isOpen, setIsOpen }, $.select("[data-define='app']"));

You can read more on this package here: @jsimple/dom-render

Custom element

If you’d like to use the dom-render APIs in custom elements we provide decorators wrappers to abstract some of the plumbing:

<!-- HTML file-->
<fancy-button></fancy-button>
// Javascript file
import { define, s } from "@jsimple/custom-element";

export
@define("fancy-button")
class FancyButton extends HTMLElement {
  @s(false) isOpenSignal: TSignal<boolean>;

  connectedCallback() {
    this.html(`
    <div>
      <button type="button" $click="setIsOpen(!isOpen())">open</button>
      <div $display="isOpen()">lorem ipsum</div>
    </div>
    `);
  }
}

If you’d like to use it without decorators you can do as follow:

import $ from "@jsimple/core";
import { DOMRender } from "@jsimple/dom-render";

export class FancyButton extends HTMLElement {
  isOpen: TSignal<boolean>[0];
  setIsOpen: TSignal<boolean>[1];

  constructor() {
    super();
    [this.isOpen, this.setIsOpen] = $.signal(true);
  }

  connectedCallback() {
    this.html(`
    <div>
    <button type="button" $click="setIsOpen(!isOpen())">open</button>
    <div $display="isOpen()">lorem ipsum</div>
    </div>
    `);
    DOMRender(
      {
        isOpen: this.isOpen,
        setIsOpen: this.setIsOpen,
      },
      this
    );
  }
}
customElements.define("fancy-button", FancyButton);

You can read more on this package here: @jsimple/custom-element

Fetcher

Using the fetcher package you can fetch and cache data easily.

<!-- HTML file-->
<div data-define="component-with-data" class="component">
  <img
    src="./spinner.svg"
    $display="isLoading()"
    class="spinner"
    width="24"
    height="24"
    alt="spinner"
  />
  <div $display="!isLoading()" class="card">
    <p class="font-bold uppercase text-14 text-slate-900">{data()?.name}</p>
    <span>{data()?.body}</span>
  </div>
</div>
// Javascript file
import { run } from "@jsimple/dom-render";
import { GET, load } from "@jsimple/fetcher";

const getTodos =
  GET < TodoData > "https://jsonplaceholder.typicode.com/comments/1";

function ComponentWithData() {
  const { data, isLoading } = load(getTodos, ["keyString"]);
  return {
    data,
    isLoading,
  };
}

run([ComponentWithData]);