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 🙏

© 2026 – Pkg Stats / Ryan Hefner

sommark

v5.3.0

Published

SomMark is a template language that compiles to multiple output formats — HTML, JSON, YAML, TOML, CSV, Markdown, XML, and more.

Readme

SomMark

npm version Browser Support Node.js license

SomMark is a template language that compiles to multiple output formats — HTML, JSON, YAML, TOML, CSV, Markdown, XML, and more.

Write your content once with a single consistent block syntax. Add loops, compile-time logic, and file imports. Compile to whatever format your project needs.

v5 is the official stable release and the recommended version to start with. SomMark has reached its main goal — a single consistent block syntax that compiles to any output format. v5 is the last major version. Future releases will be minor updates or patches.


Install

# CLI
npm install -g sommark

# Use it with your project
npm install sommark

Showcase

team.smark (HTML mapper)

[import = Card: "./components/Card.smark" !]

${
  const team = [
    { name: "Adam",  role: "Founder",  avatar: "adam.jpg"  },
    { name: "Hawa",  role: "Engineer", avatar: "hawa.jpg"  },
    { name: "Ilham", role: "Designer", avatar: "ilham.jpg" },
  ];
  const year = new Date().getFullYear();
}$

[main = class: "team-page"]
  [h1]Our Team[end:h1]

  [section = class: "grid"]
    [for-each = ${ team }$, as: "member"]
      [Card = name: ${ member.name }$, role: ${ member.role }$]
        [img = src: ${ member.avatar }$, alt: ${ member.name }$ !]
      [end:Card]
    [end:for-each]
  [end:section]

  [footer]© ${ year }$ SomMark[end:footer]
[end:main]

components/Card.smark

[div = class: "card"]
  [h2]v{name}[end:h2]
  [p]v{role}[end:p]
  [slot!]
[end:div]
sommark --html team.smark
<main class="team-page">
  <h1>Our Team</h1>
  <section class="grid">
    <div class="card">
      <h2>Adam</h2>
      <p>Founder</p>
      <img src="adam.jpg" alt="Adam">
    </div>
    <div class="card">
      <h2>Hawa</h2>
      <p>Engineer</p>
      <img src="hawa.jpg" alt="Hawa">
    </div>
    <div class="card">
      <h2>Ilham</h2>
      <p>Designer</p>
      <img src="ilham.jpg" alt="Ilham">
    </div>
  </section>
  <footer>© 2026 SomMark</footer>
</main>

Output formats

| Format | CLI Flag | | -------- | -------------- | | HTML | --html | | Markdown | --markdown | | MDX | --mdx | | JSON | --json | | JSONC | --jsonc | | YAML | --yaml | | TOML | --toml | | CSV | --csv | | XML | --xml | | Text | --text |


Features

Blocks

Every tag is a block. Open with [name], close with [end:name]. A plain [end] also works, but [end:name] is recommended for readability.

[article = class: "post"]
  [h1]Hello, SomMark[end:h1]
  [p]Structured content, zero guesswork.[end:p]
  [hr!]
[end:article]

Self-closing blocks carry no body — append ! inside the tag:

[br!]
[img = src: "logo.png", alt: "Logo" !]
[input = type: "text", name: "email" !]

Compile-time JavaScript

${ }$ runs JavaScript at build time inside a sandboxed QuickJS VM. The static keyword is optional — ${ expr }$ and static ${ expr }$ are identical.

The example below targets the HTML mapper:

${
  import pkg from "./package.json";
  const built = new Date().toISOString();
}$

[footer]
  ${ pkg.name }$ v${ pkg.version }$ — built ${ built }$
[end:footer]

Variables declared in one ${ }$ block are available in all blocks that follow in the same file.

Loops

[for-each] iterates over any array. The current item is accessed via the as: alias. ${ i }$ gives the zero-based index.

The example below targets the HTML mapper:

${
  const links = ["Home", "Docs", "Blog", "Contact"];
}$

[nav]
  [ul]
    [for-each = ${ links }$, as: "link"]
      [li][a = href: "#"]${ link }$[end:a][end:li]
    [end:for-each]
  [end:ul]
[end:nav]
<nav>
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">Docs</a></li>
    <li><a href="#">Blog</a></li>
    <li><a href="#">Contact</a></li>
  </ul>
</nav>

Shorthand key-value for data formats

When targeting JSON, YAML, or TOML, write key-value pairs using the tag name as the key. Type is inferred automatically — numbers stay numbers, true/false become booleans, everything else becomes a string.

The example below targets the JSON mapper:

[Object]
  [username = "Adam" !]
  [age = 25 !]
  [score = 9.8 !]
  [isAdmin = false !]
  [deletedAt = null !]
[end:Object]
{
  "username": "Adam",
  "age": 25,
  "score": 9.8,
  "isAdmin": false,
  "deletedAt": null
}

Body form also works — useful for longer values:

[Object]
  [bio]Software developer based in Hargeisa.[end:bio]
[end:Object]

Generate config from data with a loop. The example below targets the YAML mapper:

${
  const services = [
    { name: "api",    port: 8080, replicas: 3 },
    { name: "worker", port: 8081, replicas: 2 },
  ];
}$

[seq = "services"]
  [for-each = ${ services }$, as: "s"]
    [map-item]
      [name = ${ s.name }$ !]
      [port = ${ s.port }$ !]
      [replicas = ${ s.replicas }$ !]
    [end:map-item]
  [end:for-each]
[end:seq]
services:
  - name: "api"
    port: 8080
    replicas: 3
  - name: "worker"
    port: 8081
    replicas: 2

Placeholders

Inject values at build time from your JavaScript config using p{}. Add a fallback with | in case a value is not set.

The example below targets the HTML mapper:

[div = class: "env-badge"]
  Environment: p{NODE_ENV}
[end:div]

[footer]Built with p{engine | "SomMark"}[end:footer]
new SomMark({
  src,
  format: "html",
  placeholders: { NODE_ENV: "production" }
});

Module system

Split templates into reusable .smark files. All [import] declarations must appear at the top of the file.

Static injection — insert a module with no props or body content:

[import = Nav: "./components/Nav.smark" !]
[import = Footer: "./components/Footer.smark" !]

[body]
  [$use-module = Nav !]
  [main]
    [p]Page content.[end:p]
  [end:main]
  [$use-module = Footer !]
[end:body]

Component block — pass props and body content. Inside the component, v{} reads the props and [slot] marks where the body goes:

[import = Card: "./components/Card.smark" !]

[Card = title: "Featured Post"]
  This content fills the slot inside the card.
[end:Card]

Card.smark:

[div = class: "card"]
  [h2]v{title}[end:h2]
  [div = class: "body"]
    [slot!]
  [end:div]
[end:div]

Comments

Completely removed at build time — never appear in the output:

# single-line comment

###
  multi-line comment
  write as much as you need
###

CLI

Global

sommark -h, --help                        # show help message
sommark -v, --version                     # show version
sommark init                              # generate smark.config.js
sommark show config [file]                # show resolved config
sommark show --path-config [file]         # show path to active config file
sommark color on|off                      # help on enabling terminal colors

Transpile

sommark --html input.smark                # compile to HTML
sommark --markdown input.smark            # compile to Markdown
sommark --mdx input.smark                 # compile to MDX
sommark --xml input.smark                 # compile to XML
sommark --json input.smark                # compile to JSON
sommark --jsonc input.smark               # compile to JSONC
sommark --toml input.smark                # compile to TOML
sommark --yaml input.smark                # compile to YAML
sommark --csv input.smark                 # compile to CSV
sommark --text input.smark                # compile to plain text
sommark --lex input.smark                 # print lexer token stream
sommark --parse input.smark               # print parser AST

Output

sommark --html -p input.smark             # print output to console
sommark --html input.smark -o name ./dir/ # set output filename and directory

Browser bundle

sommark bundle ./dist/                    # copy full bundle (JS + WASM)
sommark bundle ./dist/ --lite             # lite bundle — no WASM
sommark bundle ./dist/ --only-lexer       # lexer only
sommark bundle ./dist/ --only-parser      # lexer + parser only

Programmatic API

Node.js

import SomMark from "sommark";

const sm = new SomMark({
  src,
  format: "html",
  placeholders: { title: "My App" }
});

const output = await sm.transpile();

Browser

import SomMark, { resolveBaseDir, renderCompiledHTML } from "sommark/browser";

const src = await fetch("./main.smark").then(r => r.text());

const sm = new SomMark({
  src,
  format: "html",
  baseDir: resolveBaseDir("./templates/"),
});

renderCompiledHTML(document.getElementById("app"), await sm.transpile());

Configuration

Run sommark init to generate smark.config.js:

export default {
  format: "html",
  removeComments: true,
  placeholders: {},
  importAliases: { "@": "./" },
  fallbackTarget: true,
  outputValidator: null,
  baseDir: null,
  showSpinner: true,
  security: {
    allowRaw: true,
    maxDepth: 5,
    timeout: 5000,
    allowFetch: true,
    allowHttp: false,
    allowedOrigins: [],
    allowedExtensions: [],
    sanitize: null,
  },
  outputDir: "./",
  outputFile: "output",
};

Security

All ${ }$ expressions run inside a QuickJS sandbox — isolated from the host process:

| Setting | Default | Description | | ------------------- | ------- | ----------------------------------------- | | allowRaw | true | Allow JavaScript in compile-time blocks | | maxDepth | 5 | Maximum import nesting depth | | timeout | 5000 | Max execution time per script (ms) | | allowFetch | true | Allow network requests from scripts | | allowHttp | false | Block plain HTTP — HTTPS only by default | | allowedOrigins | [] | Restrict fetch to specific domains | | allowedExtensions | [] | Restrict which file types can be imported | | sanitize | null | Custom function to sanitize HTML output |


Custom mappers

Define your own tags and rendering logic with the Mapper API:

import SomMark, { Mapper } from "sommark";

const mapper = new Mapper();

mapper.register("alert", function({ content, props }) {
  const type = props.type || "info";
  return `<div class="alert alert-${type}">${content}</div>`;
});

const sm = new SomMark({
  src: `[alert = type: "warning"]Check your config.[end:alert]`,
  format: "html",
  mapperFile: mapper,
});

Full reference: docs/api/Mapper


Documentation

| Topic | Location | | ---------------- | ------------------------------------------ | | Syntax Reference | docs/syntax/ | | Output Formats | docs/languages/ | | Core API | docs/api/Core/ | | Browser API | docs/api/Browser/ | | Mapper API | docs/api/Mapper/ | | Sandbox API | docs/api/Sandbox/ | | CLI Guide | docs/cli/ | | Configuration | docs/cli/config.md |


License

MIT — Adam Elmi