Pros and cons of the code-markup hybrid approach

Previously I looked at the respective merits and shortcomings of dedicated markup and general-purpose code for authoring an application UI. In that post I alluded to some real-world approaches that didn’t fit within the definitions I adopted.

The most important style of approach that I omitted is the ‘code+markup hybrid’ approach – one where code and markup can be freely mixed in the same file/parse unit. Naturally enough this omission was noted in responses to the article:

Is it true that mixing code and markup in a shared syntax gives you the best of both worlds? Can we, in fact, have it all?

In the previous post, I enumerated the advantages of markup over code, and code over markup. That gives us a useful analytic lens to examine the hybrid approach. This article is going to be reviewing the respective advantages of markup and of pure code for UI, and seeing whether the ‘hybrid approach’ retains those advantages or loses them.

What are we talking about?

Let’s set out concrete examples: JSX, Blazor, PHP. What we see here are application syntaxes that let you mix an established markup format (almost always HTML, though the Blazor team has recently experimented with, for example, augmenting Xamarin.Forms’ XAML dialect) with code written in a general-purpose language (eg Javascript, C#, or… well, PHP). You get the full expressivity of general-purpose code, along with (allegedly) the benefits of using markup.

I’m not really sure if there’s an established term for this class of approach? Initially I thought these were ‘templating languages’, and it seems that they’re sometimes considered as such, but the term ‘templating language’ seemingly tends to encompass simpler syntaxes that are more reusable than HTML but still lack the full expressivity of non-markup code; and sometimes ‘templating language’ seems to be used to mean specifically these simpler syntaxes and not the ‘full-fat’ approach that JSX, etc demonstrate. So for this article I’ll stick to ‘code-hybrid markup’ or just ‘hybrid approach’, with the understanding that I’m talking about a ‘full-fat’ type of syntax that permits all the constructs that you would expect in a general-purpose language (conditionals, loops, arrays etc).

How does a ‘hybrid approach’ work, though?

In general, you can apply the following mental model when reading a hybrid code-markup program:

  1. Parse it according to the ‘code rules’, treating the markup bits as opaque string tokens. You should end up with something that’s nothing but markup, or at least with chunks of standalone markup and chunks of standalone code.
  2. Parse the chunks of standalone markup according to the normal rules of the base markup syntax (eg, vanilla HTML).

On a technical level, something like this must be taking place, but the exact details vary widely, particularly for step 2. Typically, older hybrid languages like PHP really do just feed the resulting HTML to the normal HTML parser (ie, the browser); with these languages, the code is typically running server-side anyway, so sending vanilla HTML over the wire is an obvious approach.

Newer hybrid approaches like React (JSX) and Blazor tend to take a more sophisticated approach, since reloading the whole page is slow and undesirable for many UI scenarios. Their runtimes instead parse the markup produced by step 2 themselves, using it to maintain and update a shadow object tree (sometimes referred to as a ‘virtual DOM’) and programmatically updating the actual DOM in increments whenever a change to the shadow tree is detected. The idea is that the shadow tree can be lightweight and this approach can be much more performant than rebuilding the whole page every time.

How do hybrids stack up?

Previously I laid out what I saw as the strengths of markup and the strengths of code. For each strength, we can ask whether the code-markup hybrid approach inherits that strength, or nullifies it.

I’m going to organise the analysis into 3 sections, first looking at where the hybrid approach gets the ‘best of both worlds’ by inheriting strengths from both pure code and pure markup; then, where it gets the worst of both worlds; and finally, the points where I’m not sure yet.

Best of both worlds

I had Midnight Oil running through my head while writing this section, you can too!

Recall that markup is a domain-specific language, tailor-made for writing UI. The hybrid approach retains that advantage, since any formulation that’s valid in the parent markup syntax is also valid in the hybrid syntax.

Turing-complete

The hybrid approach is Turing-complete, not in a theoretically-useful-only way but in the sense that any arbitrary program can be efficiently expressed in it. That’s the advantage it gains from incorporating a general-purpose code syntax.

Reusable

Because the hybrid approach brings the full expressivity of general-purpose code, it’s great for reuse, just as code is.

Worst of both worlds

IDE support

I noted previously that general-purpose languages tend to have better IDE support in the form of code completion and other productivity-enhancing features, compared to markup syntaxes, and they probably always will, because the general-purpose language community will always be a superset of the associated markup language community, and will enjoy a bigger share of the resources in terms of tooling support.

The situation is probably even worse for hybrid syntaxes than for pure markup. The smaller-userbase argument still applies, and there’s also the fact that the hybrid approach is inherently punishing on the tooling. The language server that provides code completion, automated refactorings and similar features has to essentially handle two syntaxes in one, typically with quite different parsing rules, that can be deeply interleaved and combined in many different ways.

By the same argument, the hybrid approach is probably not very amenable to WYSIWYG tools that aim to spit out compileable output (though, as I noted in the previous article, such tools seem to be increasingly falling out of favour anyway).

Don’t have to learn a new language

You’re out of luck here. Using a hybrid syntax carries the knowledge burden of the parent coding language, the parent markup language, and also the rules for how the two interact.

I’m not sure

These are some areas where I’m just not sure how the hybrid approach rates.

Easy-to-read structure?

One of the advantages of markup is that, being tailored to declaring object hierarchies, it’s much easier (for me, at least) to read a UI declaration in markup than in code.

For ease of reading, is the hybrid approach more like markup, or more like code? This is a practical question that I don’t have the experience to answer. I wouldn’t be surprised if it depends on coding style and a measure of discipline – an approach that uses code sparingly and judiciously at predictable ‘break points’, doing the bulk of the UI declaration in markup, would mostly retain the readability of markup, whereas an approach that liberally and arbitrarily mixes the two would not (and could very conceivably be harder to read than pure code).

How’s the performance?

How well do hybrid frameworks do on performance, compared to pure code or conventional markup? Here I have many questions and no answers. How does the performance of real-world apps built with hybrid frameworks hold up, particularly for large apps with complex UIs? What intrinsic performance characteristics might we infer from the constraints of the hybrid approach? I am leery of the implicit need to re-parse the intermediate ‘pure markup’ every time the state changes, but maybe it’s fine in practice – I really don’t know.

Coming from another angle

I think it’s illuminating to put on our ‘framework designer hats’ for a moment.

You’re designing a framework for authoring interactive applications. You’ve already decided that it should include a domain-specific language for authoring the UI – it should include markup. (You might not choose that of course – that would be the pure code approach – but for this exercise, let’s assume you have.)

There are, it seems to me, two broadly reasonable ways you might choose to integrate that domain-specific UI language (which we will just call ‘markup’ from here on) with the general-purpose coding language that you will certainly also need for business logic, complex interactions, etc.

There’s also a third, not so reasonable way, that you probably wouldn’t choose if you had the luxury of desigining a framework for rich interactive applications from a blank slate, but might nonetheless end up with in the real world where pesky things like imperfect foresight and backwards-compatibility requirements exist.

These three ways are:

  1. Make the standard be that markup should be defined as self-contained syntactic units, with clearly-defined contact points with general-purpose code (eg, event handlers). Provide a means of reusing markup units without having to fall back to general-purpose code.
  2. Make the standard be that markup and general-purpose code should coexist in the same syntactic unit; the app developer can interweave them in any way imaginable way according to permissive syntax rules, and it should ‘just work’. The problems of reuse, and of connecting markup to business logic in general-purpose code, are thereby automatically taken care of.
  3. Start with a markup language originally conceived for authoring static pages with limited interactivity. Retrofit it with contact points to general-purpose code. Don’t provide any standard way to reuse markup.

As a framework designer, #1 and #2 both have pros and cons. It’s not clear which is best from an API design standpoint; #2 is more flexible, but flexible is not always a good thing (the antonym of ‘flexible’ in this context is ‘opinionated’). From an implementation standpoint, #2 is most likely going to be significantly costlier – there’s going to be a lot of funky edge cases, and I’m going to have to teach the tooling to understand my weird Frankenstein syntax. And I can certainly see some individual developers preferring #1 and others preferring #2 on grounds of personal taste; in short there’s good arguments both ways.

Option #3 is bad! I would not choose that one!

But of course, for the web, option #3 is the baseline. That’s the ‘standard’ HTML/JS/CSS development experience without any additional frameworks. Little wonder that the hybrid approach, option #2, is so popular.

Would I personally go with the hybrid approach, if I really were designing a new UI framework from scratch? My gut feeling is no. Implementing and supporting the mixed syntax looks like a time suck; I’m doubtful about the performance aspects; and I’m not convinced that being able to arbitrarily mix and match markup and code is really overall a better development experience than having separate markup and code with clearly-delimited points of contact. (Or, for that matter, than simply doing away with markup completely.)

But then plenty of people are happily building applications with those frameworks, so what do I know? It’s clearly a workable approach, and it’s not about to disappear any time soon.

Author: David Oliver

I’m a developer, ex-physicist, and occasional game designer. I’m interested in history, society, and the sciences of human behaviour, as well as technology and programming.

One thought on “Pros and cons of the code-markup hybrid approach”

Comments are closed.