22 comments

[ 5.4 ms ] story [ 70.0 ms ] thread
Interesting, but I get worried every time I see that code refer to specific DOM id. It's probably ok for simple programs where there is only a handful of elements. When the program gets larger, you will still need architectural pieces to coordinate data.

You've put a lot of work into this, and there are a lot of examples that are helpful to anyone who wants to evaluate it. I'm curious what is the motivation behind this design - besides zero framework ? - is this in-use in production any where?

Thanks! Zero framework is just one of the distinct traits that makes Cell special.

Here's why I started working on Cell: Aside from Cell, I am working on a project called Jasonette http://jasonette.com/ which lets you build build cross platform iOS/Android native mobile apps by writing just a JSON markup. I have recently started working on a web version of Jasonette, so that a single JSON markup can run exactly the same on iOS, Android, and the Web. I tried implementing it with most of the existing popular JS frameworks but found that they don't fit the bill. They are too complex to fit Jasonette's philosophy of placing simplicity and ease of use as top priority. Also Jasonette is great for decentralized apps, but the centralized nature of all existing frameworks and approaches wasn't compatible with what I was trying to achieve.

Cell doesn't just make things easier, but is designed fundamentally different from traditional MVC frameworks. I think this image does a good job of explaining: https://s3-us-west-2.amazonaws.com/fm.ethan.jason/domtree.jp... Instead of creating a centralized control mechanism, Cell lets you inject M-V-C (or anything else you want to) directly into each HTML element, thereby decentralizing the control. I think this will enable a lot of creative things and design patterns going forward (which I'll demonstrate first with Jasonette-Web).

As for using querySelectors, I totally get it, because that was my initial feeling as well. Initially I also felt like accessing elements directly was a step backwards (because we've become accustomed to the "new" approach where we keep a separate data structure that binds with the DOM, and directly accessing the DOM feels like what we used to do with jQuery)

But Cell's approach brings its own benefits. First, as I mentioned the control logic can be decentralized. Second, all the complexities of model-view binding goes away because the very concept of binding existed because they were separate. In case of Cell, the element can "contain" its own model/view/controller, and because it contains them there's no need for binding, which is one reason why Cell can stay simple. Another factor is this architecture effectively turns each element into an app execution container of its own, so these components can be extremely modular and portable.

You mentioned when the app becomes larger we'll need some architectural pieces to coordinate data, and you are right, except that the same logic applies here too. The "architecture" is the DOM tree itself. So the root element can contain the root model, and the descendants can access it as well as keep their own version of the model, so forth. I tried my best to explain all these concepts on the homepage but I do realize it's a long read, so I hope this explanation makes enough sense. TLDR: it requires a bit of stepping back and reframing what we've been accustomed to but I'm confident this new approach brings a lot of benefits that weren't easy to implement before.

How does it scale? Anti pattern is sometimes useful & obvious, but going with anti patterns for everything is a stupid decision IMO. Frameworks are for greater good. If you don't need it, don't use any of them. But when "No framework" is listed as a key Mantra for yet another js library, I simply find it as dumb & full of madness. :-/
What I meant by "No framework" was that there is no "Framework API" to implement. Cell itself is a framework, so I agree that frameworks are for greater good.

As for your question on how it scales, I don't see a reason why it shouldn't scale. In fact I think this approach is more scalable than any existing centralized approaches to building web apps because you can create complexity out of simplicity.

p.s. The "No framework" part is just one of the benefits that arise as a side effect of Cell's decentralized architecture. I did my best to explain why I built this on the homepage. I know it's long but I hope you take a look at it once more, it's worth a read even if you don't use the framework because Cell does bring something new to the table. Hope this makes sense!

JS frameworks and SEO have a rough history. Pre-rendering tends to be the workaround, but Cell's lack of hierarchy might make that more difficult than normal.

How would you work around it?

Cell itself does not have a structure. But the point is that you can easily create a structure by composing cells. The DOM tree itself is a structure, and Cell is designed to let developers take advantage of that instead of creating a virtual DOM or stuff like that (without side effects).

Put it another way, you can create a fully centralized architecture using Cell. One way of doing it is taking advantage of Cell's context inheritance feature https://github.com/intercellular/tutorial#b-context-inherita... but there can be many different ways. The whole point of Cell is to exist as a minimal decentralized framework which you can utilize to do things your way.

The benefit is you have the ability to create a completely decentralized DOM tree, as well as create a centralized one.

As for the SEO issue, I have two answers:

1. Pre-rendering on Cell is just a matter of transforming JSON into HTML (but instead of using something like JSDOM on to create an entire DOM on the server-side, you could just directly map the JSON into HTML. I actually have a piece of code that does that as well but haven't released it yet.

2. Handling pre-render is simpler than other frameworks because Cell can seamlessly plug into an existing DOM tree https://github.com/intercellular/cell#b-plug-into-existing-w... (it's actually one of the selling points of Cell because no other framework makes this easy. If you have a static website and just want to integrate a dynamic widget, there's no simple way to do so using most of the popular frameworks because all of them basically take over the entire frontend and you have to switch to that framework entirely just to add that widget, or you need to use webpack, browserify, etc. to package them up just so you can use it on the frontend as a bundle.js. This is also not the most user-friendly way. Cell makes this super easy because there is no dependency and no code packing process)

Hope this answers your question!

Let's say you pre-render the DOM nodes on the server. At that point, there's no need to create them again in the browser from the JSON object. How would you augment them to become Cells?
After having looked through the tutorials, I am unclear about the following:

1) How does one cell send messages to another. I suppose this is left to the application?

2) How do you test code written in Cell.js? It appears to me that this is going to be hard to do?

3) Is it intended that the entire webapp code be a single JS file? I guess one would need a server side build step to "assemble" multiple Cell.js components into a single one?

1) How does one cell send messages to another. I suppose this is left to the application?

There are several ways to do this https://github.com/intercellular/tutorial#3-inter-cellular-c...

I'm sure there are other ways of doing this in more creative ways, which was my intention when I was designing this to be as simple and modular as possible.

Another thing is, I say Cell has a decentralized architecture, but that doesn't mean it only lets you build apps that way. The point is you can construct these decentralized cells in any way you desire to build apps. So you could build a perfectly centralized architecture with Cell as well.

2) How do you test code written in Cell.js? It appears to me that this is going to be hard to do?

What I think is really cool about this approach is you are effectively expressing an entire app as a piece of data https://github.com/intercellular/cell#3-app-as-data I think this concept needs some time to sink it at first, but once you get it, it opens door to a lot of creative ways of structuring your app.

For starters, any Javascript object can be composed/manipulated/constructed with a function. And since you can express not just data but also the app logic itself as data, you can do the same with stateless functions.

So YES, you can unit test your Cell apps. In fact it's easier than any other existing frameworks since the entire app is built with functional programming (no stateful class objects). You can write a whole bunch of functions to construct/manipulate these "Genotype" objects and unit test those functions to make sure they behave correctly. Hope this makes sense.

3) Is it intended that the entire webapp code be a single JS file? I guess one would need a server side build step to "assemble" multiple Cell.js components into a single one?

Nope, the only reason I packaged them as a single file at https://play.celljs.org was just to make it easier to understand as a demo (because splitting out into multiple files makes you jump back and forth and is not ideal when you're just trying to have a quick overview of the app structure).

But the beauty of this approach is, at the end of the day, all you need is a JSON-like object that defines the looks and behaviors of your HTML nodes throughout the DOM tree. Which means in real life, you would be splitting these out into as many functions as you want (In fact this is one of the strongest selling points of Cell. you can create as many of these functional components as you want without overhead compared to other class based frameworks) Really at the end of the day all you need is a function that generates the Javascript object structure you need, which means you can map/filter/reduce anything from Model to View to Controller logic.

Here's an example I was working on yesterday https://github.com/intercellular/jsonschema It's a JSON schema validator, still work in progress since I just started it yesterday but you get the point.

Hope this was helpful. Please feel free to ask more questions!

Thanks for taking the time to respond.

From everything you've said, most of what cell.js does is implement yet another templating engine.

I think you said as much in your reply: "Really at the end of the day all you need is a function that generates the Javascript object structure you need, which means you can map/filter/reduce anything from Model to View to Controller logic."

The additional work it does is injecting member variables for each of the cell components (i.e. the "_" params).

Am I missing something?

This is a thought-provoking new framework, thank you! It's novel and easy to understand.

Having worked on sophisticated React apps, I've come to understand the benefits and shortcomings of its architecture.

One insight is that properly encapsulating UI components and their behaviors leads directly and naturally to nested state machines. React is cleanest if you have one state machine, or N independent global state machines, that drive your app. Once you start trying to compose state machines to make a sophisticated UI, you end up in setState/callback hell, and then you try the Redux/Elm model, in which states are composed to make bigger states. I think it's fruitful to experiment with ways of composing state machines in which you don't just glue the states together, but you let the machines interact. Of course you still want to keep your program easy to reason about and avoid spaghetti, but I'm sure there are interesting patterns lurking there.

Another issue is components in React aren't supposed to address each other. You can easily call methods on your children using refs, though this is discouraged and interacts badly with the update cycle and higher-order components. Components not talking directly to each other is a great feature right up until it's not, such as when you have to deal with focus and selection changes. You're forced to contemplate taking what you thought of as pure UI state -- local to a component and tied to that component's lifecycle -- and moving it outside of React into a store whose structure parallels the component tree (which in turn parallels the DOM), just so that you can traverse it in a reasonable fashion and update it synchronously while DOM updates remain asynchronous. In Cell.js, these three trees become one.

Thank you, I think you pretty much nailed it!

This architecture may not be very obvious to many people at this point but I believe it can enable a lot of interesting use cases (as well as being able to deal with even traditional app structures in more efficient and cleaner ways)

One example is my own use case, where I'm working on a web version of Jasonette https://www.jasonette.com I couldn't find any existing framework that can achieve what I was trying to without becoming very complex.

Thank you for the encouragement, you made my day :D

Sure thing. :) One thing I'm not completely clear on is the reactivity model. It seems like setting $components (for example) triggers a setter that updates the DOM? What about other DOM properties? What about underscore properties? Is there a dependency graph under the hood, and can you set up a property to depend on a calculation based on another property? Or is all data propagation explicit/imperative/"push"?
Yup, it works for all $ variables.

As for the "_" variables they are meant to have no side effect on the DOM directly and instead only trigger a function called $update() automatically, which is where you can deal with all the view stuff.

So the only thing you need to remember is to:

1. Declare whaterver "_" or "$" attribute you want to monitor initially on the genotype.

2. Once you have done that, every time you make a change to the underscore or $ variables, they automatically get queued up (via Nucleus) and trigger the $update() function right before the next render frame so they can be drawn all at once in a single frame.

There are different ways of doing this, you could directly touch the DOM attributes (without any prefix) or the $ variables and they will update the view from anywhere.

But personally my pattern when using Cell to write apps is to NOT directly touch the $ variables everywhere because it can be confusing, but instead keep the underscore variables as sort of a "model", and only touch those. And because updating a _ variable triggers an $update(), I can deal with all the view related stuff (changing the DOM attributes and $ attributes) in one place, inside $update(), which is the "reactive" feature. (But it's really up to you how you do it) You can learn more here https://github.com/intercellular/tutorial#2-update

Hope this helps!

p.s.

This document may be helpful in understanding the source https://github.com/intercellular/cell/blob/develop/GENESIS.m...

Got it.

I immediately had a bunch more questions, some of which I tried to answer by playing around with the demo.

Is "this" the actual DOM element? (Yes.) Then how is "context inheritance" implemented? The section named "How Cell implements 'context inheritance' does not ever say how it's implemented ;) (Getters and setters are set up by the framework.) It feels like inheriting everything would be a little messy compared to specifying what goes in context.

Does changing something in "context" trigger updates in nested components that read that context? Apparently, no.

Does assigning to $components always give you brand-new components, or is there any sort of diff/patch as in React? Apparently, always new components and new DOM nodes, so don't recalculate your children; you should probably access them via the DOM API and call methods on them instead. So you don't get "props flowing down" as in React.

How are assignments to underscored props coalesced, e.g. using what notion of equality? What should you call props that you don't want to cause an $update?

Why is it ok to call methods on other cells from $update, causing further updates, when this is frowned on in React, partly because of timing issues? I sort of wonder if making state changes synchronous is a huge win and solves a lot of issues; a lot of people are wondering if React makes a big mistake there. At the same time, it seems like there would be situations where you update underscored properties on A and B, and A is actually an ancestor of B, so updating A would destroy B. Then you wonder if the framework is going to be smart about the update order, and smart about dealing with defunct components during the update cycle. Edit: That's not even a good example of a timing issue caused by cascading updates. Can't think of a good one off the top of my head.

Also edit: I haven't read the code, but it looks really short.

I find it hilarious that Cell goes through such effort to tell you that it's not a framework, and then you refer to it as a framework immediately.

It's definitely a framework though (note that this is not an inherently bad thing)

This is neat. I built something a little more complex for work; it is a bootstrap based dynamic form engine, basically you uee JSON to define any one of about 12 form elements, even things like jsgrids or file upload boxes, and add validation rules / regex, layout rules etc. The app then builds all of the CRUD aspects automatically. It is making my life very very easy, as we have to build a lot of massive order forms at my ch ompany with over a hundred unique fields. Also built a PDF generator that pulls the form elements in as tokens to do mail mergy kind of stuff. It is the best project I have built in my career, really want to try and get it open sourced.
This is interesting. I have had a look at the examples. One question that comes to mind, how do you make a reusable component? Say I have: Part = { $cell: true, $type: "h2", $text: "Part No: ", } Part is to be instantiated multiple times in the same page, but I want different $text. How do I pass this data?
It seems to use functions for that purpose, look at the example with map.

I really like the linked to Android/iOS implementation of this concept.

Ok. I found a way using factory:

  ItemMaker = function(data){
    return {
    $cell: true,
    $type: "h1",
    style: "font-size: 100px;",
    $text: data,
  
    }
  }
  var i1 = ItemMaker('foo');
  var i2 = ItemMaker('bar')
Interesting. Though not really clear why you continuously say "no API!" when you have an API--it's just in the data itself via your "special keywords" i.e. $cell, $update, etc. That's kind of like saying React components have no API since you never actually call e.g. componentDidMount directly.