Skip to content

Adam-Elmi/SomMark

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,669 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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

About

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

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages