XMark: Content Building Tool Powered by Plugins
By Shijun Wang on 04/04/2019

XMark: Content Building Tool Powered by Plugins

Brought to you by Shijun Wang

XMark: Content Building Tool Powered by Plugins


  • Motivation
  • Design
  • Implementation
  • Show Case
  • How to Write a Plugin?
  • Future Plans


  • Mixing content with structure and presentation in source code makes it hard to change
  • Existing markdown tools are too generic(remark) or rely too much on developer skills(mdx)
  • We need a generic tool that can generic web content from markdown, must be flexible and extensible

XMark: means a powerful(x) tool based on markdown


  • The core of xmark should only care about 2 things

    • Allow user to compose a processing pipeline with his selected plugins
    • Run the set of plugins against input and return the results
  • The input of the pipeline can be either markdown or hast

    • markdown is the source code
    • hast can be generated from any other tool, such as remark
  • The output of the pipeline can be either html or hast

    • html for direct page rendering
    • hast for rendering in react|vue or later processing
  • All structure transforming and style applying should be done in plugins

    • transform plugins only modifies the hast, the only format through the pipeline
    • theme plugins only return styles that can applied to certain structure

Side Note: hast: Hypertext Abstract Syntax Tree


interface Element <: Parent {
  type: "element"
  tagName: string
  properties: Properties?
  content: Root?
  children: [Element | Comment | Text]


  "type": "element",
  "tagName": "a",
  "properties": {
    "href": "http://alpha.com",
    "className": ["bravo"],
    "download": true
  "children": []


function fromMarkdownToHast(markdown) {
  let hast = typeof markdown === 'string' ? toHAST(remark.parse(markdown)) : markdown;

  // Apply transformation
    .filter(x => x.type === 'transform')
    .forEach(({ fn, config }) => {
      hast = fn(hast, config);

  // Apply styling
    .filter(x => x.type === 'theme')
    .forEach(({ fn, config }) => {
      hast.children.unshift(h('style.xmark-injected-style', { scoped: true }, fn(config)));

  return hast;

function fromMarkdownToHtml(markdown) {
  return toHTML(fromMarkdownToHast(markdown));

Implementation (ctnd.)

function usePlugin(type, plugin, config) {
  const fn = typeof plugin === 'function' ? plugin : require.resolve(fn);
  plugins.push({ type, fn, config });

return {
  toHAST: fromMarkdownToHast,
  toHTML: fromMarkdownToHtml,

  useTransform(plugin, config) {
    usePlugin('transform', plugin, config || {});
    return this;

  useTheme(plugin, config) {
    usePlugin('theme', plugin, config || {});
    return this;

Show Case

Show Case: Wrap Headings


Wrap Headings


Show Case: Class Extension


Class Extension


Show Case: Landing Page Generating


Supported Sections

  • card-list
  • horizontal-annotation-list
  • image-background
  • image-column
  • image-feature-list
  • image-row
  • image-rows
  • step-guide
  • vertical-annotation-list

Show Case: Landing Page Generating: Pipeline

const XMark = require('@xmark/core');
const page = require('@xmark/transform-landing-page');
const wrap = require('@xmark/transform-wrap-headings');
const theme = require('@xmark/theme-landing-page');

const xmark = XMark({});
xmark.useTransform(wrap, {
  wrapperTag: 'section',
  enabledHeadings: ['h2', 'h4', 'h5', 'h6'],

xmark.useTransform(page, {
  selectors: {
    sectionImage: 'p:has(.gatsby-resp-image-wrapper)',

xmark.toHAST(`## title {.section .section--hero}`);

Show Case: Landing Page Generating: Syntax

title: 'Product Name'
path: '/learning/product'
layout: 'page'
pageType: 'product'
backgroundImage: './images/bg.jpg'
disableHeader: true
logoColor: 'blue'

## Hero section from top to bottom. {.section .section--hero}

A fully decentralized wallet that put you in control of everything - digital identity, tokens, profiles and more.

[Demo App](mailto:mining-partner@arcblock.io){.action-button}


Show Case: Presentation Generating



  • This presentation is generated by xmark with above plugins

Show Case: Presentation Generating: Pipeline

const XMark = require('@xmark/core');
const shower = require('@xmark/transform-shower');
const wrap = require('@xmark/transform-wrap-headings');
const ribbon = require('@xmark/theme-shower-ribbon');

const xmark = XMark({});

xmark.useTransform(wrap, {
  enabledHeadings: ['h2'],
  wrapperClasses: {
    wrapper: 'slide',
    modifier: x => `slide--level${x}`,

xmark.useTransform(shower, {
  caption: false,
  progress: false,
  wrapper: false,

xmark.toHAST(`## title {.slide}`);

Show Case: Presentation Generating: Syntax

title: 'XMark: Content Building Tool Powered by Plugins'
author: 'Shijun Wang'
date: '04/04/2019'
path: '/bbl/xmark'
layout: 'presentation'

## Agenda {.slide}

- Motivation
- Design
- Implementation
- Show Case
- How to Write a Plugin?
- Future Plans

How to Write a Plugin?

How to Write a Plugin: Transform?

const xmark = XMark({});
xmark.useTransform(hast => {
  hast.children[0].properties.className = ['plugin-added-class'];
  return hast;

expect(xmark.toHTML('# heading 1')).toEqual('<h1 class="plugin-added-class">heading 1</h1>');
expect(xmark.toHAST('# heading 1')).toEqual({
  children: [
      children: [
          type: 'text',
          value: 'heading 1',
      properties: { className: ['plugin-added-class'] },
      tagName: 'h1',
      type: 'element',
  type: 'root',

How to Write a Plugin: Theme?

const xmark = XMark({});
xmark.useTheme(() => 'h1 { color: red; }');

expect(xmark.toHTML('# heading 1')).toEqual(`
  <style class="xmark-injected-style" scoped>h1 { color: red; }</style>
  <h1>heading 1</h1>

Future Plans


  • Write more plugins to make xmark usable when the content matters most
  • Provide @xmark/cli


  • It's possible to make use xmark processing markdown snippets
  • Snippets can be reused across pages in www repo, then we can make it completely marketing driven
  • Improve shower plugin and presentation component, allow publish slide to web easily


  • TOC on sidebar
  • Accelerator repo use xmark