Fork me on GitHub

Just pushed an update to fulcro 2.4.0-SNAPSHOT to clojars. The new alpha DOM and CSS localized DOM stuff is all cleaned up and ready to play with. In my trials everything looks pretty good, but there are definitely some differences, so it isn’t (yet, perhaps) quite a drop-in replacement. The big difference is the old DOM are low-level functions, and the new stuff is macros. This means using things like apply with them doesn’t work. Not sure what to do about that just yet. Seems like including a function version of them is useful, so the API is still open to extension and perhaps even change. See the dev cards for demos:


@tony.kay fulcro-inspect is unstyled with the latest fulcro snapshot.


True. 2.4 moves colocated css to a new namespace


I should put in a compatibility layer perhaps


There actually seem to be a few regressions with 2.4.0-SNAPSHOT. I’ll have to look through and see what I did. The compatibility might also not work, so I might end up undoing my rename.


I should actually be tagging @wilkerlucio for this instead


& if so is a request-middleware the place to add extra headers that should get passed to a backend remote?

Daniel Hines15:03:37

I'm pitching Fulcro for a project at work, and could use some help forming my case. What would you guys articulate as the chief benefits of Fulcro over a GraphQL server with a client like Relay or Apollo? Am I correct in thinking the architectures are similar, maybe even equivalent from an "incidental complexity" perspective? Am I also correct in thinking that what Fulcro offers is a very specific implementation of this architecture such that a lot of the code required by those other libraries is already written in Fulcro?


Hi @d4hines. So, first, for the back-end there is no problem using a GraphQL server, or any backend for that matter. You get some benefits by having the primary back-end be in Clojure (the queries and mutations are all then very trivial to implement), but it is a simple matter (esp if you use the pathom library) to transform queries to run against any back-end database. If you have clients (external) that want to consume something like GraphQL, then there is nothing stopping you, but if you’re just using it to get to Apollo, Fulcro will save you a lot of headaches, boilerplate, and architecture design. I’ve not personally used Apollo, so others can chime in on their experience. Any mainstream thing always benefits from “numbers in adoption”. So, you will find things like more answers in stack overflow. Also, those projects will move and morph more quickly because of the large community. It’s best to be honest about the downside of not choosing “popular”. You’ll also get resistance from engineers that think using clj/cljs will “hurt their careers” (I think this is laughable, but it has been said to me). So, all of that said, here are the things Fulcro does well: 1. The client-side normalized database is tightly integrated with the server interaction architecture. This means that all I/O is very much simpler. Since this is where a lot of things go wrong, it is a very big advantage over something like Redux. 2. The programming language is a super-power. You have a single tool that gets you all the way to optimized minimal compiles 3. All of the main features you need are right there: React, a CQRS architecture that uses the same transactional data locally and on the wire, an extensible on-the-wire data protocol. Co-located, localized CSS. A language that lets you write first-class macros (extend the compiler). Much less boilerplate. Hot code reload that works out of the box. A support viewer that lets you debug issues from the field with hot code reload against a real error state from a real client. First-class immutable data structures. A real core library that does excellent data manipulation. Automatic use of Google Closure’s advanced js compiler. 4. Fulcro has a lot of documentation and training videos. Probably as much or more than most mainstream things.


My design goal from the beginning was: sustained production velocity. Keep the boilerplate and complexity out of what the engineers write as much as possible. I’ve had reports from more than one commercial client that they are having success in that area.


The current improvements to the DOM API are just an example of a further refinement aimed at making it even tighter. Instead of writing this:

(dom nil (p #js {:className "a"} "Hello"))
The upcoming APIs will allow optional parameters and classname shorthand:
(dom (p :.a "Hello"))


with no loss in speed or generality. Localized CSS is getting it’s own new DOM API (which is slower, which is why it is separate) that supports that for localized class names (munged to the component ns and name). Another thing to realize if you have not looked at co-located CSS before: It allows you to embed CSS with your components, emit that to the DOM (via React), and prevent name collisions. The syntax is evaluated as data/code. Thus, you end up with the power of a programming language in your CSS, no need for any external CSS tools (or build process), no extra files to distribute, and the DOM isolation of Shadow DOM without the browser incompatibilities.


Also, the notation of CLJS for DOM/CSS is so similar to the regular notations (CSS/HTML) that I’ve had no problem training CSS/HTML UI devs to work in it, and they find the devcard development experience a lot better than what they normally do (e.g. they can have the same UI element up in 10 diff browsers all hot code reloading, with component-local CSS, etc).


The biggest down-side of the component-local CSS (IMO) is that the notation could be better documented. The Garden library (which is what Fulcro uses for it) has somewhat terse documentation…but that is easily fixable.


@tony.kay this is not the first time we are answering this question, maybe we should have a wiki with some info like these, to help people support adoption cases, what you think? (can save you a lot of typing ;P)


Yes, I keep meaning to write something up. The thing it, I need to make time to actually go use the latest “mainstream” stuff, so I can write an accurate A/B guide. There is info on the website, but it helps me to refine it when ppl ask 🙂


e.g. something pre-written would miss the “latest news” like I just added…but yes, I very much feel the need (my hands are tired after typing that 😉 )


@wilkerlucio Also, hoping people like you will chime in 😉

Daniel Hines16:03:13

@tony.kay thank you for the thorough answer. One thing I really value about what you guys are doing is your willingness to help! And I agree that the documentation you've put together is really excellent and thorough. I've payed a lot for video courses/etc. that were inferior to your video guides, so I'm very grateful you've put them out there. But to my question, we wrote our project in Angular, with a Redux implementation that helped us achieve fairly pure components. This was really awesome, but what I didn't realize we were missing was the normalized data piece. It seems so obvious now, but with a normalized data store, a lot of the complexity of our frontend app would just go away. I first heard about normalizing the client data from you (though Eric Normand's blog, actually), and it seems like the missing piece to everything; however, I believe both Relay and Apollo offer their own normalized data stores, and then let you define the server-side mutations in the schema, and automatically handle running them at the right time.

Daniel Hines16:03:09

It seems to me then that the two key selling points are 1. Clojure itself, which makes doing the right thing easy, and 2. The fact that you've already put all of these different components into one package that let's me not worry about how to pull them all together in my plain React or Angular application. Am I getting that right?


nice, interesting to see the point on normalization, I agree this makes life easier 🙂


the next big one that I think a lot of people miss is the namespaced keywords


having long, context-free names, is something truly powerful once you understand how to incorporate on daily basis


this allows you to be free from rigid schemas (like on GraphQL), on GraphQL, when you model the schemas, you are "forced" to think about all possible uses of that, how to break it down, and how to evolve it


after you software is growing, you get more and more in a position where you are afraid of changing the schemas, because by doing so you can screw up every thing that depends on it


and all this complexity can go away if you instead make the attributes your primary unity of composition (instead of "composites", like, make :user/id a primary thing, not an User, because :user/id is very stable, User is not)

Daniel Hines17:03:48

Right! And this is the point Datomic's "universal schema", right?


yeah, exactly 🙂


also why Clojure suggest so much namespaced keywords, they are just a better primary building block than composites, first datomic, and now spec


I’m parsing the code to understand how client networking and middleware works. Got a bit confused over (which to me looks like the cljs code path). It’s using ct which is required in the clj code path at What am I missing?


@d4hines So, I would hope what you end up with in Fulcro is much simpler to grow and manage, which goes beyond it just being a “one-stop” shop. Certainly the tooling makes a big difference. Apollo looks really cool, and I’d have to say the the pre-written integration with GraphQL is appealing. The competition is tough 🙂 The need to write an interfacing layer for Fulcro to get to GraphQL is a distinct disadvantage if you’re needing a GraphQL server. There’s just more work there. On the other hand: do you need a graphql server? If not, Fulcro has a huge advantage in concision, reduced boilerplate with respect to queries, and a database you can reason about over time. In Apollo, your data store isn’t the data store for everything. It’s the data store for your server data. This means you’re still left managing the rest of your client application state without a normalization engine, etc. Doesn’t it? To me, you lose all sorts of things as a result of that: - No support viewer. - No debugging against real client state - No time travel, which can be used for interesting optimistic update/network error handling modes. - Are the queries of components true data in the data store? This is also part of database time travel (again, error handling and debugging power) - True pure rendering: if some of your data is component local hidden state, the less you have pure rendering, and the more subtle mutation-related bugs can occur. - mutations: Compare their optimistic UI example with Fulcro: No, I think there are a lot more differences than tooling and language 🙂


Ignore my earlier comment regarding reader litterals. I was confused by parens 🙂


@kardan so you’re all set?


Yes, it was just the ordering and indentation of the reader literals that confused me a bit. Thanks!


@levitanong I pushed a new snapshot of 2.4.0. I just admitted defeat and undid the rename of fulcro-css 🙂


you might need to put an exclusion on inspec (of fulcro-css) so you don’t end up in a battle between the library and integrated version…though they are pretty much identical, so it probably won’t hurt


2.4.0-SNAPSHOT has a bug I just discovered, though. I’ve been trying to fix something related to initial state templating on defsc, and the new version still isn’t quite right. FYI


(e.g. the i18n selector breaks in the demo app from the template)


@tony.kay aww, I liked the rename. Can’t fulcro-inspect be updated to use the new namespace?


it can, but it’s going to break other existing code as well


ah, yes that sounds like a major version change, doesn’t it


I generally try to follow the rule of “expansion only”. It’s unfortunate that the name got chosen the way it did


like a 3.0.0


yeah, but such a silly reason to go that large


it’s a minor annoyance


c’est la vie!


i’m sure a larger change can be made in the near future as an excuse to bump the major version number. 😉


not planning on anything that breaks stuff


the new DOM stuff is invasive, but it should be 100% bw compatible


i’m curious, what makes the rename break it?


macro stuff?


wouldn’t changing the protocol names work?


I tried putting in a ns that did def from one to the other, but that still seems to break the defui usages


like instead of css, something like styles


the problem is the defsc macro. the :css option has to emit something


and if there are 2, it gets funky


I don’t want to maintain 2 different APIs just to get a rename…that makes things much worse


and protocols are really fully namespaced things.


oh well! I can live with fulcro-css


me too 🙂


I mean, I could ask everyone to do a global search/replace on their code…it isn’t that big of a deal, but these kinds of “companion library” breakages can be really annoying


I think you are picking the right approach @tony.kay really means a lot to us that since 2.0.0 everything is very stable


And there is a new network released, but we can still use the old one since we wrote a custom one. And the old network impl is still there


That’s good to hear. Did that spec version bump fix your testing issues?


Yes I think so.. not sure yet because it happened sometimes... 😛


good. keep me apprised. It is important to me that the testing story be good.

Daniel Hines18:03:19

@tony.kay I didn't realize Apollo doesn't provide storage for UI-only concerns - I guess I assumed there was an equivalent to :ui/name, because I don't see why there wouldn't be! 😛 That's a huge hit. I suppose one could keep using Redux, normalize the state somehow, and build in instrumentation for time-travel, etc., but I do see what you're saying about losing the magic if you compromise on UI state even a little.


I don’t know Apollo, so I may be wrong. The problem with cobbling it all together from parts is you often lose important things like that. The strength of cobbling it all together is you have more flexibility. Fulcro is trying to aim at the middle ground: supply the stuff you really really want to be tightly coupled for good reasons, without restricting your choices too much.


And Redux is similar in arch, but is a big bag of boilerplate, and doesn’t supply the “normalized store”…so, again you’re left inventing the integration when what you want to do is write software features.


IMO Redux’s reducers are also more complexity than Fulcro’s normalized state + mutation model…and the way you write Redux….switch case? Really? Advanced GOTO? No code nav from the place where you invoke it to where you wrote it. There;s a lot to be said for tools that can navigate your code, and constructs you can reason about in isolation.


The problem with dev is that to really evaluate something you need to look at the overall dev experience over time. That’s expensive because to really do it means you need to write something non-trivial in each of the candidates and see where the hard edges are. So, most ppl look at surface-level features and miss all of these little details that make, IMO, most of the difference.


The biggest “hard edges” in Fulcro are: - You have to write the integration with your data store. It’s a tractable problem, but isn’t convenient. Libraries are rapidly appearing that solve this, but it’s all young. - The data model isn’t validated for you (fixable, and @cjmurphy has written some tools to help with it), so problems are almost always things like mis-typed keywords or mis-shaped data. In a way this is nice: the problems are much more focused…but it can be an eyesore sorting through the data. - Error messages from clj/cljs could be better. Macros, in particular, can really leave you scratching you head.


@tony.kay glad to hear we are not renaming the css ns actually, we can live with the annoyance, better than breaking things IMO 🙂


@cjmurphy Thank you, I used :brutal just to be shure when :keyframe didn't work. The :company/_offices is the reverse of :company/offices in Datomic. Could Fulcro be choking on the underscore in the keyword? I spend the day setting up shadow-clj and building a rudimentary thing again on top of the html5-routing-demo itself instead of just using it as an example. This one does not have the problem so far so I now have a working example to work from.

Daniel Hines20:03:13

@tony.kay, thanks, you've given some great ammunition and food for thought.