Capsules vs. components

With all this capsules stuff, you might be wondering whether you should be using capsules for everything: here's the short answer, don't. This page will go through the differences between normal Sycamore components, which don't integrate with the Perseus state platform, and full-blown capsules.

View generation

The first major similarity between capsules and components is that they both generate Sycamore views, but they do this at different times. If you use a component for something, then Perseus will render it immediately, no matter what, whereas capsules need to know what their state is, which has to be retrieved from the engine, meaning their renders will be delayed until a request has been completed. Note however that this isn't the case with initial loads (when a user comes to your app from the outside internet), and, then, all capsules will be served together as one HTML bundle.

This difference might seem minor, but it can cause major problems if you start to use capsules for everything. For example, let's say you create a styling library with capsules for buttons, checkboxes, etc., thinking it's a great idea because you can generate state for them in advance, perhaps using some advanced usage of incremental generation as a property-parsing system. However, if you then have a capsule for a sidebar, with a capsule inside that for a section, and another capsule inside that for a button, you'll end up with three layers of nested capsules. Importantly, Perseus doesn't know what capsules something (either a page or a widget) depends on until it renders it. That means Perseus has to render your page, putting the sidebar in a loading state until we have its state, and then it can render the sidebar, but then it finds that that's dependent on a section widget, so it goes and gets that one's state, and then it finds the button capsule, and has to go and get its state. Now, if a capsule has no state, then no server trip is necessary, but capsules without state are always better as Sycamore components, since all a capsule is is a component with access to the Perseus state platform.

The main thing to take away from all this rendering complexity is that the more levels of capsule nesting you have, the slower your page will be. Specifically, if it takes n seconds to get a single widget's state from the server, and you have l levels of nesting, your entire page will need to be re-rendered l + 1 times on the engine-side to create an initial load, and it will take (n + 1) * l seconds to render your entire page for a subsequent load. If a single server trip takes even one second, and you have three levels of capsule nesting, that's four seconds to render your whole page, during which the user will be put through a slew of loading states. To our knowledge, this is the most efficient way we can do this while maintaining the ergonomics of Perseus (such as making sure you don't have to define which widgets a page uses in advance, which would severely limit the utility of the capsules system), but it has substantial tradeoffs when you start to use capsules in an overly nested way. Again, if it can be implemented as a component, it probably should be.

State and properties

The second big difference between capsules and components is how flexible they are. A capsule is a full-on template that can be filled in with the Perseus state platform, and then modified by some properties the calling page/widget provides, while a component just has those properties. This doesn't mean widgets are always better, it means they're a great tool when you need state. For example, there is absolutely no point whatsoever in making a button an independent capsule, because it doesn't need state. Any customization of it can (and should) be performed using the properties system, and therefore it shoudl be a component, not a capsule.

This is the reason why Perseus deliberately does not support adding Sycamore's Children<'_> type to the properties of a capsule, or passing through HTML properties like class or style through, since capsules are intended to be sections of pages, not atomic units. Despite the next version of Sycamore supporting this kind of property passthrough, Perseus will not support this with capsules to remove ambiguity about their purpose.

The intuition

By now, you're probably waiting for some kind of general rule to decide whether you should use a capsule or a component, and here it is: do not use a capsule where a component would do, because, chances are, it will just slow down both your development cycle and your app. If you need the state platform though, and you avoid high levels of capsule nesting, capsules can be an incredibly powerful tool to greatly improve the speed of your app, while simplifying complex workflows and enabling previously impossible coding patterns.

But, from here, there's still more to be said. If you apply what you've learned so far in this section to capsules, you'll probably use them only very rarely, but this isn't the intention. Capsules fit very nicely with any of the versions of Brad Frost's Atomic Design system, whereby you split your design up into atoms, molecules, organisms, templates, and pages. The atoms would be things like buttons, etc., for which you should use components, since these will be customized largely by properties, and won't need state of their own. Molecules are small units created with atoms, like a search bar. Generally, these too are better as components, but sometimes they'll need state, and they should be implemented as capsules (this is certainly the fuzziest category). Organisms are sections of your page, comprised of a number of molecules to form a functioning interface, like a header or sidebar. These should nearly always be implemented as capsules if they have any parts that need state, since this will allow persisting things like the entry typed in a search bar in a header across pages, due to Perseus' unique state caching system. When you have reactive molecules within organisms, make the reactive parts the state of the organism, and make it a capsule. Finally, you have templates and pages, which, as you might notice, already have a fairly prominent place in Perseus! (And no, we didn't design Perseus on the back of atomic design, it's just a methodology that happens to make a heck of a lot of sense when applied to Perseus specifically, but the naming similarity in the last two is purely coincidental, and they do mean slightly different things in the atomic method.)

The rules

So, since we're programmers, and we like to have a nice methodology to follow, here's one for you. By all means, use this, don't use it, rewrite it yourself, do whatever you like. There will be many cases that will not be covered by these rules, and there will be others that are poorly covered. They are made to be broken, and you should break them if it makes sense to do so! Nonetheless, they will be helpful to some, especially those starting out with Perseus, so here they are:

  1. If it doesn't have state, it should be a component.
  2. If it can be implemented as a component, it should be a component.
  3. If it's a small, composable unit of a larger interface, it should be a component (even if it might have reactivity of its own, like a search bar; think instead about how that reactivity should be cached, e.g. you probably don't want all the search bars for completely difference things on your site synchronizing their states).
  4. If it's a somewhat self-contained interface on your pages, like a sidebar or header, that has state of its own, it should be a capsule.
  5. If it's something like in number 4, but it doesn't have any state, it should be a component.
  6. If it has a constant form, but many versions (e.g. a product display), it should be a capsule so it can use incremental generation.
  7. Avoid nesting capsules wherever possible! A nesting of two or three is fine, but any more than that and you'll likely start to see performance problems!
  8. If you want to delay loading a heavy part of your page, make it a capsule, and use .delayed_widget().