npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@markout-js/cli

v0.1.4

Published

HTML-First Reactive Web Framework - CLI and Development Server

Downloads

18

Readme

Markout

npm version CI CodeQL Node.js codecov License: MIT

HTML-based reactive web framework for Node.js and the browser — for devs who despise needless complexity.

🚧 Alpha Release (v0.1.4) - Core features working, some features still in development. See ROADMAP.md for details.

Markout is three things:

  • an Express-based web server (and Express middleware)
  • an HTML-based reactive language for web presentation logic
  • a CLI development tool.

Compared to mainstream JS-based frameworks like React:

  • it doesn't need a complex project setup
  • it does away with ceremonies and boilerplate code
  • it's more accessible to designers, testers, and non-technical roles
  • it makes componentization and code reuse a breeze
  • it provides server side rendering by default.

This is the canonical "click counter" example which is a traditional "hello world" for reactive frameworks. Markout supports both quoted and unquoted expression syntaxes:

<!-- Quoted syntax (HTML-native with interpolation) -->
<html>
  <body>
    <button :count="${0}" :on-click="${() => count++}">
      Clicks: ${count}
    </button>
  </body>
</html>

<!-- Unquoted syntax (direct JavaScript expressions) -->
<html>
  <body>
    <button :count=${0} :on-click=${() => count++}>
      Clicks: ${count}
    </button>
  </body>
</html>

It must be noted that:

  • you can simply place this code in a .html file, install Markout with npm install -g @markout-js/cli, and serve it with markout serve . (serves the current directory)
  • at first request, the page will be (very quickly) compiled and executed in the server (on subsequent requests the compiled version will be reused)
  • the resulting page will be pre-rendered (meaning button's text will already contain "Clicks: 0") plus it will contain page-specific code to continue execution in the browser
  • the browser will instantly show the pre-rendered page and continue execution in the client-side (meaning it will increment the count when users click the button)
  • indexing engines which don't support JavaScript execution will get the actual page content anyway, which is perfect for SEO

As you can see:

  • no complex project setup is needed
  • rather than a code snippet as is customary, this is a complete, self contained code example which doesn't need any hidden parts to actually work
  • it only includes what's actually needed and zero boilerplate code
  • Markout is polymorphic by design, meaning it runs page logic in the server by default before passing the ball to the browser (rather than having a retrofitted SSR feature like in JS-based frameworks).

Make no mistake: Markout doesn't have a simplistic design, it actually has a more sophisticated design which, by making thoughtful choices and putting things in their right place, greatly simplifies developer experience without sacrificing expressiveness and power.

With this approach to reactive code you get four big wins:

  • ✅ Simplicity - No ceremonies, no boilerplate
  • ✅ Familiarity - It's still HTML, just with added powers
  • ✅ Reactivity - Start with HTML, add presentation logic
  • ✅ Indexability - SEO-ready by default.

In addition, Markout supports different types of deployment:

NOTE: Of course you can still have a full blown project setup when needed — just simpler than what JS-based frameworks require.

Quick Start

Want to try Markout right now? Here's the fastest way to get started:

1. Install and Run

# Install Markout CLI globally
npm install -g @markout-js/cli

# Create a simple HTML file
echo '<html><body><button :count="${0}" :on-click="${() => count++}">Clicks: ${count}</button></body></html>' > counter.html

# Serve it immediately
markout serve .

Open http://localhost:3000/counter.html and click the button - it works!

2. Try More Features

Create app.html:

<!DOCTYPE html>
<html>
<head>
  <title>My Markout App</title>
  <style>
    .danger { background: red; color: white; }
    .safe { background: green; color: white; }
  </style>
</head>
<body>
  <h1>Reactive Counter</h1>
  
  <button 
    :count="${0}" 
    :on-click="${() => count++}"
    :class-danger="${count > 5}"
    :class-safe="${count <= 5}">
    Clicks: ${count}
  </button>
  
  <p>
    ${count === 0 ? 'Click the button!' : 
      count <= 5 ? `Nice! You clicked ${count} times.` : 
      'Whoa, slow down there!'}
  </p>
  
  <button :on-click="${() => count = 0}">Reset</button>
</body>
</html>

3. Add Components

Create lib/greeting.htm:

<lib>
  <:define 
    :tag="greeting-card" 
    :name="${'World'}" 
    :mood="${'happy'}"
    :style-color="${mood === 'happy' ? 'green' : 'red'}">
    
    <div style="padding: 1rem; border: 1px solid #ccc;">
      <h2 style="color: ${mood === 'happy' ? 'green' : 'red'}">
        Hello, ${name}!
      </h2>
      <p>I'm feeling ${mood} today.</p>
    </div>
  </:define>
</lib>

Update your app.html:

<!DOCTYPE html>
<html>
<head>
  <:import :src="lib/greeting.htm" />
  <title>My Markout App</title>
</head>
<body>
  <:greeting-card :name="Markout" :mood="excited" />
  <!-- Your counter code here -->
</body>
</html>

4. What's Next?

  • Learn the concepts: Read the sections below for deep understanding
  • Try the ecosystem: Add Bootstrap or Shoelace components
  • Build something real: Markout scales from simple pages to complex apps
  • ⭐ Star the repo: If Markout sparks joy, give us a star on GitHub - it really helps!
  • Join the community: Check out the GitHub repo and contribute!

No build step needed - Markout compiles on-the-fly during development and pre-renders for production automatically.

Alpha Status & Limitations

Current Version: 0.1.3 (Alpha)

Markout is currently in alpha development. While the core reactive system is functional and many features work well, some features are still being implemented:

✅ Working Features:

  • Logic values (:count, :on-click, :class-, :style-, etc.)
  • Reactive expressions (${...})
  • Looping (:foreach attribute and <template :foreach>)
  • Components (<:define> with slots)
  • Fragments (<:import> for modular HTML)
  • Server-side rendering with client-side hydration
  • Development server with hot reload

🚧 In Development:

  • Runtime component system improvements
  • Advanced fragment features

❌ Not Yet Implemented:

  • Conditionals (<template :if>, :else, :elseif)
  • Data services (<:data> directive)
  • Islands (<:island> for isolated web components)
  • VS Code extension

See ROADMAP.md for complete development timeline and feature status.

API Stability: During alpha, APIs may change. We'll provide migration guides for any breaking changes.

Motivation

Life as a web developer has become unduly complex. Even the simplest projects require a complex setup these days. And complexity extends to application code: modern reactive frameworks force us to pollute our code with obscure ceremonies (like useState, useEffect and so on) because of their own implementation details.

In my opinion, although they are clearly very useful, they obfuscate our code for no good reason: reactivity should really make things simpler, not more complex. And, to make things worse, they keep changing! Even if you're perfectly fine with your code, you have to keep updating just to keep them happy.

Let's be clear: this constant state of radical change is not natural evolution, it's a consequence of rushed and ill considered design — coupled with hype-driven marketing. I can think of no other industry where this would be considered standard practice. This approach should be confined to bleeding edge projects, not used in every single web project no matter what.

Markout is an attempt to solve these problems, or at least to prove that solutions can be found. And in keeping with another of our industry's great ironies, here we are trying to simplify things by proposing yet another solution.

A final note about Markout's development process: this is the culmination of a long series of explorations, proofs of concept, and ~~failed attempts~~ learning experiences. In no other project I felt so clearly why indeed simplicity is the ultimate sophistication: keeping the framework out of the way of application code does in fact require a lot of consideration.

I think I can proudly say that, compared to frameworks which move fast and break (other people's) stuff, I actually thought it out before I pushed it out. There, that's a revolutionary idea! 🤯

Concepts

First, what I think is wrong with JS-based frameworks:

  • if you try to hide or replace HTML you get a Frankenstein monster like JSX
  • ditto if you try to add declarative reactive logic to JavaScript, which is mainly an imperative language
  • reactivity should work intuitively and automatically and it should actually simplify application code: useState() and useEffect(), for example, shouldn't exist
  • you should't need to learn the dark art of keeping the framework happy; the framework should work for you, not the other way around: useContext() and useMemo(), for example, shouldn't exist either.

Then, what I think can be done about it:

  • given that reactivity works best in a declarative context
  • and that HTML is already a widely used and well known declarative language
  • it's clear that making HTML itself reactive makes a lot more sense than adding reactivity to JavaScript (and then having to reinvent the markup syntax in some proprietary form)
  • and that, to keep it clean, additions to HTML should be unobstrusive and easy to spot.

As a result I came up with these additions to standard HTML:

  • logic values, added with :-prefixed attributes
  • reactive expressions, added with the familiar ${...} syntax
  • directives, added with :-prefixed tags and augmented <template> tags.

Logic values

Logic values are the foundation of reactivity in Markout. They can be used to add presentation logic to any HTML tag. They're expressed as :-prefixed attributes to keep them apart from HTML's own attributes. Compared to normal attributes, they don't appear in output pages: they are used to generate page-specific code which is added as a script to the output.

In our click counter example we have two logic values:

  • :count which stores a number
  • :on-click which declares an event handler.

Logic value names must be either valid JavaScript identifiers or *--prefixed names, which have special meaning for the framework. You can use:

By adding logic values to HTML tags you're conceptually adding variables and methods to them. There's no need to use <script> tags to define interactive presentation logic: it becomes an integral part of Markout's reactive DOM model.

To make this approach practical, tag attributes in Markout accept multiline values, can be commented out, and can have comments added to them. This makes it feel like you're defining a proper reactive visual object — because that's what you're actually doing:

<button 
  :count="${0}" 
  :on-click="${() => count++}" 
  // highlight dangerous state
  :class-danger="${count > 3}"
>
  <!--- button text can display either the count or an error -->
  ${count < 6 ? `Clicks: ${count}` : `Oh my, you clicked too much and broke the Web!`}
</button>

As you can see, inside a tag and between attributes you can use C-style comments (both single- and multi-line). In HTML text you can use the "triple dash" <!--- comments to have them removed from the output, or normal HTML comments to have them maintained. Finally, to simplify things any tag can be self closing — output pages always contain standard HTML regardless.

Reactive Attribute Syntax

Markout supports dual expression syntax for maximum developer flexibility:

Quoted Expression Syntax (HTML-native with interpolation)

<button :count="${0}" :on-click="${() => count++}">Clicks: ${count}</button>

Simple and predictable rules:

  • Single expressions: :count="${42}" → number 42 (preserves type)
  • String interpolation: :title="Welcome ${user.name}!" → interpolated string
  • Literal strings: :name="John" → string "John" (no ${} needed for literals)

Perfect syntax highlighting and editor support:

  • Works in any editor - VS Code, Vim, Sublime, generic HTML highlighters all work perfectly
  • No broken syntax - Code never looks malformed in syntax highlighters
  • Familiar to developers - Standard HTML attribute quoting that every developer understands

Unquoted Expression Syntax (direct JavaScript)

<button :count=${0} :on-click=${() => count++}>Clicks: ${count}</button>

Key advantages:

  • Mixed quotes freedom: :message=${'String with "double" quotes'} - no escaping needed
  • Template literals: :greeting=${Hello, ${name}! Welcome to "Markout".} - natural syntax
  • Complex objects: :config=${{ theme: "dark", debug: true }} - clean notation
  • Type preservation: All expressions preserve their original JavaScript types

String Interpolation Examples

<!-- Quoted syntax - perfect for string templates -->
<div :title="Welcome ${user.name}, you have ${notifications.count} messages"
     :class="btn btn-${variant} ${isActive ? 'active' : ''}"
     :style="color: ${theme.primary}; font-size: ${size}px">

<!-- Unquoted syntax - ideal for complex expressions -->
<div :config=${{ theme: user.preferences.theme, locale: "en-US" }}
     :handler=${(event) => console.log('Event:', event.type)}
     :condition=${user.isActive && notifications.count > 0}>

Use whichever syntax feels more natural - both preserve types for single expressions, and you can mix them freely within the same component.

Reactive expressions

Reactive expressions are where logic values are consumed. They adopt the familiar ${...} syntax, and can be used anywhere as attribute values and in HTML text. They're automatically re-evaluated when the logic values they reference get updated.

In our latest example we have four reactive expressions:

  • ${0} used to initialize count
  • ${() => count++} used to define the event handler's function
  • ${count > 3} used to conditionally apply the danger class to the button
  • ${count < 6 ...} used keep button's text updated.

Of these, only the last two exhibit an actual reactive behavior: they keep button's class and text updated when the value of count changes.

The first is a constant, so it doesn't depend on other values and thus it's never re-evaluated.

The second is a function, which is also never re-evaluated by design.

Optimizations

Although it's completely transparent for developers, it's worth noting that DOM updates caused by reactive expression updates are batched to prevent the so called "layout thrashing" in the browser.

This means a set of changes caused by an application state change is applied as a whole at the same time and without duplicated DOM access.

Please note that only the DOM application of changes is batched: logic values themselves are always consistent with the latest changes, giving you the best of both worlds: performance optimization and programming model consistency.

Given Markout's design where updates are surgically applied where needed, this results in better performance than the vaunted Virtual DOM adopted in other frameworks.

Cross-Scope Effects

Markout's reactive system combined with lexical scoping enables elegant side effects that automatically respond to state changes. Here's how you can create an effect that applies dark mode across your application:

<div :darkMode="${false}">
  <button :on-click="${() => darkMode = !darkMode}">
    Toggle Theme
  </button>
  
  <!-- Component with effect that depends on parent's darkMode -->
  <section :dummy="${
    (() => {
      // Side effect: update document theme when darkMode changes
      document.body.classList.toggle('dark-theme', head.darkMode);
      localStorage.setItem('theme', head.darkMode ? 'dark' : 'light');
    })(darkMode)  // Dependency triggers re-execution
  }">
    <h2>Content automatically themed</h2>
    <p>This entire section responds to theme changes.</p>
  </section>
</div>

This pattern demonstrates several powerful features:

  • Automatic dependency tracking: The effect runs whenever head.darkMode changes
  • Cross-scope reactivity: Child components can react to parent state through lexical scoping
  • No special effect syntax: Effects leverage the existing reactive expression system
  • Batched execution: Multiple effects triggered by the same change are batched together

This approach works for any side effects: analytics tracking, API synchronization, local storage updates, or DOM manipulation outside the component tree.

Reactivity implementation

No Virtual DOM, No Re-rendering Nightmare: Unlike frameworks that re-render entire component trees and rely on diffing algorithms to figure out what changed, Markout knows exactly what needs updating because of its reactive dependency tracking. This eliminates the common performance pitfalls of Virtual DOM frameworks:

  • No unnecessary re-renders: Components don't re-execute when unrelated state changes
  • No render cascade problems: A change in one component doesn't trigger re-renders throughout the component tree
  • No need for optimization ceremonies: No React.memo, useMemo, useCallback, or similar workarounds to prevent wasteful re-renders
  • Predictable performance: Updates are surgical and proportional to actual changes, not component tree size

Directives

Markout directives are based on either <template> or custom <:...> tags:

  • <template>: conditionals and looping
  • <:define>: reusable components
  • <:import>|<:include>: source code modules
  • <:data>: data and services
  • <:island>: isolated web components (planned for 1.x)

Conditionals

Conditional rendering can be controlled with <template :if | :else | :elseif>.

For example:

<template :if="${userType === 'buyer'}">
  ...
</template>
<template :elseif="${userType === 'seller'}">
  ...
</template>
<template :else>
  ...
</template>

Of course conditionals can be nested:

<template :if="${userType === 'buyer'}">
  ...
  <template :if="${catalog.length}">
    ...
  </template>
  ...
</template>

Conditional Transitions

Like looping with :foreach, conditionals support smooth appearance and disappearance animations using the same transition attributes:

<template :if="${showWelcomeMessage}" 
  :transition-in="${fadeInUp({ ms: 300, dy: 20 })}"
  :transition-out="${fadeOutUp({ ms: 200, dy: -20 })}">
  <div class="welcome-banner">
    <h2>Welcome to our platform!</h2>
    <p>Get started with these quick actions...</p>
  </div>
</template>

<template :if="${user.hasNotifications}"
  :transition-in="${slideInDown({ ms: 250, dy: -30 })}"
  :transition-out="${slideOutUp({ ms: 200, dy: -30 })}">
  <div class="notification-panel">
    You have ${user.notifications.length} new notifications
  </div>
</template>

This provides consistent animation behavior across all <template> blocks, whether controlled by data changes (:foreach) or boolean conditions (:if/:else`).

Looping

Replication can be expressed with <template :foreach [:item] [:index]>.

For example:

<template :foreach="${[1, 2, 3]}" :item="n" :index="i">
  Item ${n} has index ${i}
</template>

The :foreach logic value is meant to receive an array, but it accepts null, undefined and non-array types as synonims of [], so if it doesn't get something valid to replicate, it simply won't replicate anything.

Note that both :item and :index are optional: if :item is missing, you won't have access to the current item, and in the same way if :index is missing you won't have access to its index.

Keep in mind that the <template> element itself is kept in output markup, preceeded by its possible replicated clones. This is important to know in case you're using CSS pseudo-classes like :last-child for styling, in which case :last-of-type is advised.

Components

Components can be declared with <:define>.

This directive lets you turn any HTML block into a reusable component. You can:

  • Declare parameters with default values using logic values like :name="Default Name"
  • Use reactive expressions ${...} to inject parameter values into the component HTML
  • Support slots just like Web Components for flexible content composition
  • Add presentation logic with event handlers, conditional styling, and reactive behavior.

The component can then be used anywhere as a simple custom tag, passing different parameters each time.

You can find a comprehensive demonstration of component creation and usage in the Bootstrap section below, which shows how to turn Bootstrap's verbose modal markup into a clean, reusable and reactive <:bs-modal> component.

Two-Tier Component Architecture (Planned for 1.x)

Markout 1.x will support an elegant dual-component system addressing different isolation needs:

🏝️ Islands = Web Components (Heavy Isolation)

<!-- Independent, fully isolated widgets -->
<:island src="/widgets/weather.htm"></:island>
<:island src="/widgets/todo-list.htm"></:island>
  • Purpose: Independent widgets requiring complete encapsulation
  • Isolation: Shadow DOM provides full CSS/DOM boundaries
  • Communication: Standard DOM events, attributes, slots and data services
  • Use Cases: Third-party widgets, micro-frontends, cross-team components

🧩 Components = Markup Scopes (Light Composition)

<!-- Logical organization within apps -->
<:import href="/components/user-profile.htm" />
<:import href="/components/notification-list.htm" />
  • Purpose: Logical organization within islands or main applications
  • Isolation: Lexical scoping for reactive values, shared styling context
  • Communication: Direct scope access, shared reactive context
  • Use Cases: UI patterns, layout components, data presentation

🔗 Service-Oriented Communication

<!-- Cart service island -->
<:island src="/services/cart.htm" name="cartService"></:island>

<!-- Product island consuming cart service -->
<:island src="/widgets/products.htm">
  <:data :aka="cart" :src="@cartService" />
  <button :on-click="${() => cart.json.addItem(product)}">
    Add to Cart (${cart.json.count})
  </button>
</:island>

Islands can expose services using <:data>, enabling async operations (local DB, API calls) while maintaining reactive data flow. Services handle complex async operations internally while exposing simple reactive interfaces to consumers.

This provides optimal performance (isolation only where needed) while maintaining clear conceptual boundaries between "what needs isolation" (islands) vs "what can share context" (components).

Fragments

Source modules can be included with <:import> and <:include>.

With these tags you can include page fragments. For example:

<html>
<head>
  <:import :src="lib/app-card.htm" />
</head>
<body>
  <:app-card>
    Hello.
  </:app-card>
</body>
</html>

Where this could be lib/app-card.htm's content:

<lib>
  <:import :src="baseline.htm" />

  <style>
    .app-card {
      border: 1px solid #ccc;
    }
  </style>

  <:define :tag="app-card" class="app-card" />
</lib>

And lib/baseline.htm's content could be:

<lib>
  <style>
    body {
      margin: 0;
    }
  </style>
</lib>

It should be noted that:

  • page fragments should use the .htm rather than .html extension (so they won't be served as pages by mistake)
  • they have an arbitrary root tag which is discarded (<lib> in this case)
  • they are normally imported in page's <head>, so their styles fall naturally into place
  • since <:define> is removed from output markup (and included in generated JS code) it doesn't pollute <head>'s content
  • page fragments can in turn import other fragments with either relative or absolute path in the document root
  • <:import> ensures each single fragment is imported only once in the whole page
  • if two imported fragments import the same other fragment, only the first one will actually have it added to page <head>
  • each component can simply import all its dependencies: you don't need to import lib/baseline.htm yourself, it will be included as soon as you import any of your library's components.

It all boils down to this: you can easily build your component libraries where each component includes its own dependencies (e.g. for baseline styling) without fearing duplications and with automatic dependency handling.

The <:include> directive can be used to explicitly include a fragment multiple time in a single page or fragment.

One note about the <style> tags: since they're tags like all others, they can include ${...} expressions and be made reactive as well! This should be used sparingly though — primarily for theming and configuration that changes infrequently (like at application launch). For dynamic styling that responds to user interactions, stick to the standard approaches: :class- logic values and :style- logic values on individual elements, which are optimized for frequent updates.

Even with a considered use, reactive <style> tags can be amazingly useful and:

  • can remove the need of CSS preprocessing
  • can let you implement switchable themes with ease
  • can help you implement adaptive styling e.g. for mobile
  • eliminates the CSS vs JS variable barrier
  • let component implementation and styling have a single source of truth.

With this approach to modularity you get four big wins:

  • ✅ Simplicity - Include fragments with a single tag, automatic dependency resolution
  • ✅ Familiarity - Still regular HTML files, just with .htm extension for fragments
  • ✅ Configurability - Even CSS can be parameterized for themes and component variants
  • ✅ Reusability - Build component libraries where each component manages its own dependencies

Data

Technically speaking this should be a paragraph of Directives dedicated to the <:data> tag. But it covers such a fundamental concern in web apps that it deserves its own section.

The <:data> directive lets you formally declare all data and service interactions, and define your own data generation and processing if needed.

For example, here is how you can connect to a REST endpoint:

<:data :aka="usersData" :src="/api/users" />

And here's how you may use the data:

<ul>
  <template :foreach="${usersData.json.list}" :item="user">
    <li>${user.name}</li>
  </template>
</ul>

Note that <:data>'s json value is always defined, at most it can be an empty object. Given that :foreach accepts undefined and null as synonyms of [], no defensive checks are needed here.

The data can be local as well:

<:data :aka="navigationData" :json="${{
  list: [
    { id: 1, url: '/dashboard', title: 'Dashboard' },
    { id: 2, url: '/activity', title: 'Activity' },
  ]
}}" />

And, because local data participates in the reactive system — :json is a logic value after all — it can automatically update too:

<:data :aka="localeData" :json="${{
  en: { dashboard: 'Dashboard', activity: 'Activity' },
  it: { dashboard: 'Panoramica', activity: 'Attività' }
}}" />
<:data :aka="navigationData" :lang="en" :_locale="${localeData.json[lang]}" :json="${{
  list: [
    { id: 1, url: '/dashboard', title: _locale.dashboard },
    { id: 2, url: '/activity', title: _locale.activity },
  ]
}}" />

Now you have a localized menu which seamlessly updates when users switch language!

In addition, again because :json is a logic attribute, you can locally generate data:

<:data :aka="totalsData" :json="${_generate()}" :_generate="${() => { return ...
}}">

You get the idea. In the same way, you can concatenate <:data> directives to build data pipelines, simply making each a function of the one before, and you can cascade API calls just by making each dependent on the previous one's data.

By leveraging source code modularization with <:import>, you can of course properly organize the data layer in your code and import it where needed.

With this approach to data handling you get four big wins:

  • ✅ Simplicity - Declare data needs directly in HTML, no separate data layers
  • ✅ Familiarity - REST endpoints and JSON data work exactly as expected
  • ✅ Reactivity - Data updates automatically flow through dependent components and pipelines
  • ✅ Composability - Chain data transformations naturally without complex state management

Advanced features

There's still a lot to say about the deceptively simple <:data> directive: things like HTTP methods, authentication, caching, error handling, retries etc. but it takes its own chapter in the docs.

GraphQL Integration

GraphQL support is implemented as reusable component libraries using Markout's fragment system - no special runtime code needed:

<!-- Import GraphQL query component -->
<:import src="/lib/graphql/query.htm" 
         :aka="users"
         :endpoint="/graphql"
         :query="${`
           query GetUsers($limit: Int) {
             users(limit: $limit) {
               id name email
               profile { avatar bio }
             }
           }
         `}"
         :variables="${{ limit: 10 }}" />

<!-- Import GraphQL mutation component -->
<:import src="/lib/graphql/mutation.htm"
         :aka="userMutations"
         :endpoint="/graphql" />

<!-- Use exactly like any other data -->
<template :foreach="${users.json.users}" :item="user">
  <div>
    ${user.name} - ${user.email}
    <button :on-click="${() => userMutations.updateUser(user.id, updates)}">
      Update
    </button>
  </div>
</template>

Key Benefits:

  • Zero Runtime Overhead: GraphQL clients are pure component libraries
  • Community Ecosystem: Anyone can build and share GraphQL integration patterns
  • Company Standards: Teams can create standardized GraphQL fragments for consistent usage
  • Selective Import: Only import GraphQL capabilities when needed, keeping bundle size minimal

WebSocket Real-time Communication

WebSocket support is implemented as reusable fragment libraries using standard browser APIs and Markout's reactive system:

<!-- Import WebSocket client component -->
<:import src="/lib/websocket/client.htm"
         :aka="chatSocket"
         :url="ws://localhost:8080/chat"
         :on-message="${(context, event) => {
           const message = JSON.parse(event.data);
           return {
             ...context,
             messages: [...context.messages, message]
           };
         }}" />

<!-- Reactive UI updates automatically -->
<div :if="${chatSocket.json.connecting}">Connecting...</div>
<div :if="${chatSocket.json.connected}">
  <template :foreach="${chatSocket.json.messages}" :item="message">
    <div><strong>${message.user}:</strong> ${message.text}</div>
  </template>
  
  <input :on-keydown="${(e) => {
    if (e.key === 'Enter') {
      chatSocket.send(JSON.stringify({
        user: currentUser.name,
        text: e.target.value
      }));
    }
  }}" />
</div>

Key Benefits:

  • Pure Library Implementation: Built using standard <:data> patterns and browser WebSocket API
  • Reusable Components: WebSocket connection management as importable fragments
  • Custom Integrations: Teams can build domain-specific WebSocket patterns (chat, notifications, collaboration)
  • No Framework Bloat: Core runtime stays minimal, WebSocket features added only when imported

Universal Async Interface

The same library-first pattern works for any async communication - all implemented as importable fragment libraries:

<!-- Server-Sent Events -->
<:import src="/lib/sse/client.htm" :aka="notifications" :url="/api/notifications/stream" />

<!-- IndexedDB -->
<:import src="/lib/indexeddb/client.htm" :aka="localDB" :database="userPreferences" />

<!-- Web Workers -->
<:import src="/lib/worker/client.htm" :aka="worker" :script="/js/data-processor.js" />

<!-- WebRTC -->
<:import src="/lib/webrtc/peer.htm" :aka="peer" :peerId="${peerId}" />

Ecosystem Architecture:

  • Community Libraries: Standard integration patterns shared as .htm fragment libraries
  • Company Libraries: Internal teams create reusable integration components for consistent patterns
  • Zero Runtime Dependencies: All integrations use existing <:data> capabilities and browser APIs
  • Selective Enhancement: Import only the async capabilities your application actually uses

Two things are important to outline straight away though.

For one, <:data> is where business logic should live: while presentation logic is more effectively scattered around in visual objects, business logic is better kept centralized in dedicated data-oriented objects. For example:

<!--- Business logic: user validation, data processing -->
<:data :aka="userService" :validate="${(user) => user.email && user.age >= 18}" />

<!--- Presentation logic: button states, form interactions -->
<form :user="${{}}"  >
  <input :value="${user.email}" :on-change="${(e) => user.email = e.target.value}" />
  <button :disabled="${!userService.validate(user)}">Submit</button>
</form>

Another important thing to clarify is: <:data> is where async/await and promise-based code, if any, should live. Markout reactivity is synchronous, but it can be triggered by events, timers, and asynchronous data operations.

The <:data> directive provides a universal async interface that works consistently across all transport layers - WebSockets, GraphQL subscriptions, Workers, IndexedDB, WebRTC, Server-Sent Events - using the same declarative patterns and reactive data flow.

Ecosystem

Most fellow devs might be thinking: "Yeah but a brand new framework means no component libraries!"

Except, Markout being a superset of HTML, what works with plain HTML + JavaScript can also work with Markout — and be made reactive too.

Bootstrap

Let's take Bootstrap for example:

<div class="modal fade" id="exampleModal" tabindex="-1">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">Modal title</h5>
        <button
          type="button"
          class="btn-close"
          data-bs-dismiss="modal"
        ></button>
      </div>
      <div class="modal-body">
        <p>Modal body text goes here.</p>
      </div>
    </div>
  </div>
</div>
<script>
  $('#exampleModal').modal('show');
</script>

This code is confusing to say the least, and you have to duplicate it everywhere you need a modal. In Markout, though, you can just define it once, and use it like this everywhere you need it:

<:bs-modal :open="${true}" :title="Greeting" :message="Hello world!" />

That's much better! Now you have a properly encapsulated component which clearly declares what it is (bs-modal) and only exposes what's meaningful for its use (open state, title and message texts).

For completeness, this is what the component definition could look like:

<:define
   :tag="bs-modal"

   // interface:
   :open="${false}"
   :title="Modal title"
   :message="Modal body text goes here."

   // implementation:
   class="modal fade"
   tabindex="-1"
   :watch-open="${() => open ? _modal.show() : _modal.hide()}"
   :_modal="${new bootstrap.Modal(this.$dom)}"
>
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title">${title}</h5>
        <button
          type="button" class="btn-close" data-bs-dismiss="modal"
          :on-click="${() => open = false}"
        />
      </div>
      <div class="modal-body">
        <p>${message}</p>
      </div>
    </div>
  </div>
</:define>

It should be noted that:

  • you can componentize a block by just turning its root tag into a <:define> and giving it a tag name
  • by default the base tag for a Markout component is a <div>, which is OK here
  • in order to parametrize the component, you can add logic values with their defaults, and use them in the appropriate places inside its code.

With this approach to components you get four big wins:

  • ✅ Simplicity - Complex UI becomes one line
  • ✅ Familiarity - Still Bootstrap underneath
  • ✅ Reactivity - Turn imperative components into reactive ones
  • ✅ Reusability - Define once, use everywhere.

NOTE: I plan to release a Markout Bootstrap library soon — I want the fun part for myself 😉

Shoelace

Markout works seamlessly with Web Component libraries. Let's take Shoelace as an example:

<!-- Traditional Shoelace usage -->
<sl-button variant="primary" size="large" disabled>
  <sl-icon slot="prefix" name="gear"></sl-icon>
  Settings
</sl-button>

<script type="module">
  import '@shoelace-style/shoelace/dist/components/button/button.js';
  import '@shoelace-style/shoelace/dist/components/icon/icon.js';
  
  const button = document.querySelector('sl-button');
  button.addEventListener('sl-click', () => {
    console.log('Button clicked!');
  });
</script>

In Markout, you can make Shoelace components reactive and eliminate the JavaScript ceremony:

<!-- Reactive Shoelace in Markout -->
<sl-button 
  :variant="primary" 
  :size="large" 
  :disabled="${!user.canEditSettings}"
  :on-sl-click="${() => openSettingsModal()}">
  <sl-icon slot="prefix" name="gear"></sl-icon>
  Settings
</sl-button>

By following Markout's simple conventions for page fragments, it's easy to consolidate the required plumbing in an importable library:

<!-- lib/shoelace.htm -->
<lib>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/themes/light.css" />
  <script type="module" src="https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/shoelace-autoloader.js"></script>
  
  <script>
    // Set the base path for Shoelace assets
    import { setBasePath } from 'https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/utilities/base-path.js';
    setBasePath('https://cdn.jsdelivr.net/npm/@shoelace-style/[email protected]/cdn/');
  </script>
</lib>

Now you can use Shoelace components reactively throughout your application by simply importing the library:

<html>
<head>
  <:import :src="lib/shoelace.htm" />
</head>
<body>
  <sl-button :loading="${isSubmitting}" :on-sl-click="${() => submitForm()}">
    Submit Form
  </sl-button>
</body>
</html>

Project components

Finally, what works for third-party libraries works just as well for your own stuff. Let's say you have a user profile card you want to use multiple times: Markout makes it trivial to turn it into a reusable parametric component.

This is an example before componentization:

<!-- Repeated across multiple pages -->
<div class="profile-card">
  <img src="/avatars/john-doe.jpg" alt="John Doe" class="avatar" />
  <div class="profile-info">
    <h3 class="name">John Doe</h3>
    <p class="title">Senior Developer</p>
    <p class="email">[email protected]</p>
    <button class="contact-btn" onclick="openChat('john-doe')">
      Contact
    </button>
  </div>
</div>

<style>
  .profile-card { /* ... styling ... */ }
  .avatar { /* ... styling ... */ }
  /* ... more CSS ... */
</style>

And this is the componentized code:

<!-- lib/profile-card.htm -->
<lib>
  <style>
    .profile-card {
      display: flex;
      gap: 1rem;
      padding: 1rem;
      border: 1px solid #ddd;
      border-radius: 8px;
      background: white;
    }
    .avatar {
      width: 64px;
      height: 64px;
      border-radius: 50%;
      object-fit: cover;
    }
    .profile-info h3 {
      margin: 0;
      color: #333;
    }
    .contact-btn {
      background: #007bff;
      color: white;
      border: none;
      padding: 0.5rem 1rem;
      border-radius: 4px;
      cursor: pointer;
    }
  </style>

  <:define 
    :tag="profile-card"
    :user="${{}}"
    :show-contact="${true}"
    class="profile-card">
    
    <img :src="${user.avatar || '/default-avatar.png'}" 
         :alt="${user.name}" 
         class="avatar" />
    
    <div class="profile-info">
      <h3 class="name">${user.name}</h3>
      <p class="title">${user.title}</p>
      <p class="email">${user.email}</p>
      
      <template :if="${showContact}">
        <button 
          class="contact-btn" 
          :on-click="${() => openChat(user.id)}">
          Contact
        </button>
      </template>
    </div>
  </:define>
</lib>

Now you can use it anywhere with just one line:

<html>
<head>
  <:import :src="lib/profile-card.htm" />
</head>
<body>
  <!-- Usage becomes simple and declarative -->
  <:profile-card :user="${johnDoe}" />
  <:profile-card :user="${janeDoe}" :show-contact="${false}" />
</body>
</html>

Your component definitions can just as easily be grouped in a set of page fragments reusable across pages as well as across different projects:

<!-- lib/ui-components.htm - Your component library -->
<lib>
  <:import :src="profile-card.htm" />
  <:import :src="data-table.htm" />
  <:import :src="modal-dialog.htm" />
  <:import :src="notification-toast.htm" />
</lib>
<!-- Any page in any project -->
<html>
<head>
  <:import :src="lib/ui-components.htm" />
</head>
<body>
  <:profile-card :user="${currentUser}" />
  <:data-table :data="${users}" :columns="${userColumns}" />
</body>
</html>

Leveraging Markout support for page fragments which can include baseline and default component styling, it's easy to define and maintain design systems and company-wide UI libraries. Even better, their implementation is easily inspectable and terse enough to serve as guidance for their use, removing the need for detailed documentation which is chronically out of date.

With this approach to project components you get four big wins:

  • Simplicity - Turn any HTML block into a reusable component with one tag change
  • Familiarity - Components are just HTML files that anyone can read and understand
  • Self-Documentation - Component implementations serve as their own usage guide
  • Scale - From individual components to enterprise design systems with the same pattern

Tooling

CLI

Markout includes a powerful CLI tool for development and deployment.

Installation:

# Install globally via npm
npm install -g @markout-js/cli

# Or run directly with npx (no installation needed)
npx @markout-js/cli --help

Development Server

Start a development server with hot reload:

# Serve current directory on default port (3000)
markout serve .

# Serve specific directory
markout serve ./my-project

# Serve on custom port
markout serve . --port 8080

# Or use with npx (no global install needed)
npx @markout-js/cli serve . --port 8080

The development server includes:

  • Hot reload: Automatically refreshes pages when files change
  • Live compilation: Markout pages are compiled on-the-fly
  • Static file serving: Serves CSS, JS, images, and other assets
  • Error reporting: Clear error messages with stack traces
  • CORS support: Configured for local development

Production Deployment

Deploy your Markout application for production:

# Start production server
npm run start:prod

# With PM2 process management (already included in start:prod)
npm run start:prod

# Custom configuration (modify ecosystem.config.js or use CLI directly)
markout serve . --port 80

Production features:

  • Process clustering: Utilizes all CPU cores
  • Automatic restarts: Crash recovery and memory leak protection
  • Compression: Gzip/Brotli compression for faster loading
  • Rate limiting: Built-in DoS protection
  • Health checks: Monitoring endpoints for load balancers
  • Graceful shutdown: Clean process termination

Project Scaffolding

Create new Markout projects with built-in templates:

# Create a new project
markout create my-app

# Create with specific template
markout create my-app --template bootstrap
markout create my-app --template shoelace
markout create my-app --template minimal

# Create component library
markout create my-components --template library

Static Site Generation

Alpha Note: Static site generation features are planned for future releases. Currently available:

# Build the project (server and client bundles)
npm run build

# Start development server  
npm run dev

# Production server with PM2
npm run start:prod

Development Tools

Additional development utilities:

# Validate Markout syntax
markout validate src/

# Format Markout files
markout format src/

# Analyze bundle size
markout analyze

# Generate component documentation
markout docs --output ./docs

VS Code Extension

The Markout VS Code extension (planned) will provide comprehensive development support for Markout projects:

Syntax Highlighting

  • Syntax highlighting for : prefixed logic attributes (:count, :on-click, :class-active)
  • Highlighting for reactive expressions ${...} within HTML
  • Support for Markout directives (<:import>, <:define>, <:data>)
  • Color coding for framework reserved identifiers ($parent, $value())

IntelliSense & Code Completion

  • Auto-completion for component names and fragment imports
  • IntelliSense for data references and reactive expressions
  • Type hints for component parameters and data pipeline inputs
  • Smart suggestions for logic attribute names and values
  • Context-aware completion within ${...} expressions

Error Detection & Validation

  • Real-time validation of Markout syntax and framework rules
  • Detection of circular dependencies in data pipelines
  • Type mismatch warnings for reactive expressions
  • Undefined reference detection for variables and components
  • Framework naming rule enforcement (no $ in user identifiers)

Development Tools

  • Dependency Graph Visualization: Interactive view of data pipelines and component relationships
  • Fragment Explorer: Navigate modular code organization with import/export tracking
  • Component Preview: Live preview of components with parameter interfaces
  • Architecture Diagrams: Visualize component hierarchy and data flow

Live Templates & Snippets

  • Code snippets for common Markout patterns
  • Live templates for reactive components, data definitions, and imports
  • Quick scaffolding for fragment structures and component definitions

Documentation Integration

  • Hover documentation for framework methods and properties
  • Inline documentation for component parameters and data schema
  • Quick access to Markout API reference and examples

Project Management

  • Project initialization templates for different use cases
  • Integration with Markout CLI commands
  • Build task integration and error reporting

The extension will significantly enhance the developer experience by providing the same level of tooling support that developers expect from modern frameworks, while maintaining Markout's philosophy of simplicity and HTML-first development.

Architecture

Markout is built on a sophisticated multi-layered architecture designed for both developer experience and production reliability. The framework consists of several key components working together:

  • CLI Tool & Server Infrastructure: Express.js-based server with PM2 clustering, rate limiting, and graceful shutdown
  • Multi-Phase Compiler Pipeline: 7-phase compilation from HTML to executable reactive structures
  • Reactive Runtime System: Pull-based reactivity with hierarchical scoping and batched DOM updates
  • HTML Preprocessor: Module loading, fragment imports, and dependency resolution

For detailed architectural documentation including C4 diagrams, system design principles, and component interactions, see the Architecture Documentation.

Key architectural innovations:

  • Polymorphic Execution: Same reactive logic runs on server and client
  • Reserved Namespace: $ prefix prevents framework/user code conflicts
  • Update Batching: Set-based deduplication eliminates redundant DOM operations
  • Hierarchical Scoping: Lexical variable lookup with proxy-based reactive access
  • Two-Tier Component System: Islands (Web Components) for isolation + Components (markup scopes) for composition with service-oriented reactive communication

Development

This section is for contributors to the Markout framework itself. If you're looking to use Markout in your projects, see the CLI and API documentation above.

Getting Started

  1. Clone and Setup:

    git clone https://github.com/fcapolini/markout.git
    cd markout
    npm install
  2. Build the Project:

    npm run build    # Build both server and client
    npm run watch    # Watch mode for development
  3. Run Tests:

    npm test                # Run all tests
    npm run test:watch      # Watch mode
    npm run test:coverage   # Generate coverage report
  4. Start Development Server:

    npm run dev     # Development server with hot reload

Project Structure

  • src/ - TypeScript source code
    • compiler/ - Multi-phase compilation pipeline
    • runtime/ - Reactive system (BaseContext, BaseScope, BaseValue)
    • html/ - HTML parser and preprocessor
    • server/ - Express.js server and middleware
  • tests/ - Comprehensive test suite (245+ tests)
  • docs/ - Architecture documentation
  • scripts/ - Build configuration (esbuild)

Development Workflow

Markout includes automated code quality checks via Git hooks powered by Husky:

  • Pre-commit Hook: Automatically runs before each commit to ensure:
    • Code formatting follows Prettier standards (npm run format:check)
    • All tests pass (npm test)

You can manually run the same validation:

npm run precommit

For emergencies only, bypass the hook with:

git commit --no-verify -m "emergency commit"

Testing

Markout has comprehensive test coverage across all components:

  • Unit Tests: Individual components (compiler phases, runtime classes)
  • Integration Tests: Compiler-runtime interaction, server middleware
  • Cross-Platform: Tests run on Windows, macOS, and Linux
  • Coverage Reporting: Detailed reports with V8 coverage provider
npm test                # Run all tests once
npm run test:watch      # Interactive watch mode
npm run test:coverage   # Generate coverage report (opens in browser)

Test files are organized to mirror the source structure:

  • tests/compiler/ - Compiler phase tests with fixtures
  • tests/runtime/ - Reactive system tests
  • tests/integration/ - End-to-end compilation and execution
  • tests/server/ - Express.js server tests

Architecture Guidelines

  • Stability First: Changes must not break existing functionality
  • TypeScript: All source code uses strict TypeScript
  • Cross-Platform: Support Windows, macOS, and Linux
  • Performance: Reactive updates use batching and deduplication
  • Developer Experience: Keep APIs intuitive and ceremony-free

Key patterns:

  • Reactive System: Pull-based with hierarchical scopes
  • Compilation: Multi-phase pipeline with AST transformation
  • DOM Updates: Batched updates with Set-based deduplication
  • Server-Client Hydration: Same reactive logic on both sides

Contributing

  1. Fork the Repository and create a feature branch
  2. Write Tests for new functionality using Vitest
  3. Follow Code Style - Prettier will format automatically
  4. Update Documentation if adding user-facing features
  5. Ensure Tests Pass across all supported Node.js versions
  6. Submit Pull Request with clear description

All contributions are welcome! See issues labeled "good first issue" for beginner-friendly tasks.

Development Scripts

# Build and Development
npm run build           # Build both server and client
npm run watch          # Watch TypeScript files
npm run dev            # Development server with nodemon
npm run clean          # Clean build directory

# Testing and Quality
npm test               # Run tests once
npm run test:watch     # Run tests in watch mode
npm run test:coverage  # Generate coverage report
npm run format         # Format code with Prettier
npm run format:check   # Check formatting

# Production
npm start              # Start production server
npm run start:prod     # Start with PM2 cluster mode
npm run logs           # View PM2 logs
npm run monit          # Open PM2 monitoring

Closing remarks

In an industry obsessed with the next big disruption—whether it's "signals" as the latest reactive primitive or yet another framework promising to revolutionize everything, without actually challenging the mainstream model—Markout takes a different path.

I believe in thoughtful engineering over marketing hype. While others chase trends and breaking changes, Markout focuses on solving real problems with minimal disruption. Markout's HTML-first approach builds on web standards that have proven their worth, enhanced with just three simple additions that make complex things possible without making simple things complicated.

Markout is for teams who value stability over novelty, productivity over ceremonies, and solutions over disruptions. It's for developers who want to build great web applications without constantly relearning their tools or migrating their codebases.

The upcoming island/component architecture exemplifies this philosophy: instead of inventing new abstractions, they'll leverage Web Components and extend Markout's existing <:data> system to enable service-oriented communication. Think of client-side microservices. Real-world benefits—like multiple independent functional modules on the same page—achieved through standards-based solutions that will still work in five years.

Revolutionary? Nah, just careful engineering, as it should be.

License

Markout is released under the MIT License.