←back to thread

224 points vanviegen | 1 comments | | HN request time: 0.215s | source

Yes, another reactive UI framework for JavaScript. Bear with me, please... :-)

I 'invented' the concept for this back in 2011, and it was used (as a proprietary lib) in various startups. Even though many similar open source libs have been released since, and boy have I tried a lot of them, none have been able to capture the elegance and DX of what we had back then. I might be biased though. :-)

So I started creating a cleaned-up, modern, TypeScript, open source implementation for the concept about five years ago. After many iterations, working on the project on and off, I'm finally happy with its API and the developer experience it offers. I'm calling it 1.0!

The concept: It uses many small, anonymous functions for emitting DOM elements, and automatically reruns them when their underlying proxied data changes. This proxied data can be anything from simple values to complex, typed, and deeply nested data structures.

As I'm currently free to spend my time on labors of love like this, I'm planning to expand the ecosystem around this to include synchronizing data with a remote server/database, and to make CRUD apps very rapid and perhaps even pleasurable to implement.

I've celebrated 1.0 by creating a tutorial with editable interactive examples! https://aberdeenjs.org/Tutorial/

I would love to hear your feedback. The first few people to actually give Aberdeen a shot can expect fanatical support from me! :-)

Show context
catlifeonmars ◴[] No.43936868[source]
Why not JSX? There’s no real cost to making the API JSX compatible, from what I can tell, and tsc has builtin support for transpiling JSX. It would also make porting code a lot easier. I’m only saying this because the type signature of $ is so similar to createElement.

As an aside, I really like the class name and text content ergonomics (e.g div.someclass, span:some content). Reminiscent of pug/jade

replies(3): >>43937210 #>>43937389 #>>43937465 #
vanviegen ◴[] No.43937210[source]
I don't particularly like how control logic needs to be embedded within JSX using ?ternary : operators and .map(() => stuff) within the HTML.

Also, in order to transform JSX into individual rerunnable functions, we'd need a whole different transpiler. I like being able to code browser-runnable JavaScript directly.

To each their own. :-)

replies(5): >>43937239 #>>43937991 #>>43938712 #>>43939757 #>>43941298 #
i_dont_know_any ◴[] No.43939757[source]
re: syntax, I agree, it's stopped me from ever trying React/JSX-based frameworks, which I am sure is an over-reaction.

I have a POC syntax extension (babel parser fork) I named JSXG where I introduced "generator elements" which treats the body of the element as a JS generator function that yields JSX elements.

The simple/naive implementation of just running the generator was okay, but I (perhaps prematurely) worried that it would be not ideal to have the resulting list of child elements be actually dynamic-- as opposed to being fixed size but have false/null in place of "empty" slots and also using arrays for lists made by loops.

So, I also had a transform that followed conditional branching and loops etc. and made a template of "slots" and that resulted in a stable count of children, and that improved things a whole lot.

It's been a while since I revisited that, I should try and find it!

Comparisons below.

Aberdeen:

    $('div', () => {
      if (user.loggedIn) {
        $('button.outline:Logout', {
          click: () => user.loggedIn = false
        });
      } else {
        $('button:Login', {
          click: () => user.loggedIn = true
        });
      }
    });

    $('div.row.wide', {$marginTop: '1em'}, () => {
        $('div.box:By key', () => {
            onEach(pairs, (value, key) => {
                $(`li:${key}: ${value}`)
            });
        })
        $('div.box:By desc value', () => {
            onEach(pairs, (value, key) => {
                $(`li:${key}: ${value}`)
            }, value => invertString(value));
        })
    })
JSX:

    <div>
      {user.loggedIn ? (
        <button
          className="outline"
          onClick={() => user.loggedIn = false}
        >
          Logout
        </button>
      ) : (
        <button onClick={() => user.loggedIn = true}>
          Login
        </button>
      )}
    </div>

    <div
      className="row wide"
      style={{ marginTop: '1em' }}
    >
      <div className="box">
        By key
        <ul>
          {Object.entries(pairs).map(([key, value]) => (
            <li key={key}>
              {key}: {value}
            </li>
          ))}
        </ul>
      </div>

      <div className="box">
        By desc value
        <ul>
          {Object.entries(pairs).map(([key, value]) => (
            <li key={key}>
              {key}: {invertString(value)}
            </li>
          ))}
        </ul>
      </div>
    </div>
JSXG:

    <*div>
      if (user.loggedIn) {
        yield <button
                className="outline"
                onClick={() => user.loggedIn = false}
              >
                Logout
              </button>
      } else {
        yield <button onClick={() => user.loggedIn = true}>
                Login
              </button>
      }
    </*div>

    <div
      className="row wide"
      style={{ marginTop: '1em' }}
    >
      <div className="box">
        By key
        <*ul>
          for (const [key, value] of Object.entries(pairs)) {
            yield <li key={key}>
                    {key}: {value}
                  </li>
          }
        </*ul>
      </div>

      <div className="box">
        By desc value
        <*ul>
          for (const [key, value] of Object.entries(pairs)) {
            yield <li key={key}>
                    {key}: {invertString(value)}
                  </li>
          }
        </*ul>
      </div>
    </div>
Edit:

Come to think of it, I think it may have been <div*>...</div*> or even (:O gasp) <div*>...</div>.

replies(2): >>43939835 #>>43940432 #
1. vanviegen ◴[] No.43940432[source]
That looks cool!

I think it would be pretty easy to transpile this (or something like it) to code Aberdeen can consume. In fact, it would probably be a lot easier than transpiling for React, as Aberdeen uses immediate mode the `for` problem in your comment below wouldn't be a problem at all, so no return values to worry about.

I'd use something like this myself for larger projects, I think. But asking other developers to use a different programming language just to use your library usually doesn't fly well. :-) Coming to think of it, I find it actually kind of surprising that JSX succeeded.