0.4.1 • Published 5 years ago

@yagni-js/yagni-parser v0.4.1

Weekly downloads
1
License
Unlicense
Repository
github
Last release
5 years ago

yagni-parser

HTML to Javascript compiler. Generated code is compatible with yagni-dom hyperscript dialect.

Library code is linted using eslint-plugin-fp and eslint-plugin-better. The code is almost purely functional with the following exceptions:

  • it throws an error if source html has multiple root elements
  • it throws an error if opening/closing tags differ somewhere in html
  • it throws an error if unclosed tags exist in html
  • it uses new keyword to create new tokenizer to parse html
  • it uses node path module to extract partial filename

The library uses Tokenizer from an excellent parse5 library to tokenize source html and convert it to yagni-dom compatible ES6 module.

This library is not expected to be used on it's own, please use rollup plugin or webpack loader to convert html to ES6 module.

Features

  • transforms source html to javascript function (so called view function), which can be called later to produce a factory function which when called will generate proper DOM tree
  • generates unary view function - it accepts only one argument named ctx which is expected to be an object holding context to render view (perfectly suited for yagni-dom library)
  • supports concept of partials using partial tag (see below)
  • supports {{ ctx.foo }} syntax in attribute/property value definition and in static text to allow access to passed into view function context (this syntax will be transpiled to ES6 template literal syntax - be aware of supported browsers)
  • allows only single root tag in html template
  • checks for dom tree structure simple errors
  • strips whitespace from html
  • allows to set tag properties values (use prop-foo attribute to set foo property value on tag)
  • allows to set tag attribute or property value by reference using @ as a prefix of attribute/property name: @prop-onclick="ctx.onlick" (useful to set tag event handlers)
  • allows to use newline in attribute value definition (useful for readability sometimes)
  • normalizes whitespace in tag attributes/properties values and in static text (replaces newlines to single whitespace, replaces multiple consecutive whitespace characters to single whitespace character)

Partials

Partial is an html template which can be reused by html templates or other partials.

To include partial in template you should use partial tag and specify path to source using src attribute:

<partial src="./foo.html"></partial>

Generated javascript module will be the following:

import { view as fooView } from "./foo.html";

export function view(ctx) {
  return fooView(ctx);
}

Tag partial doesn't support any nested declarations (such as text or other tags inside), they will be silently dropped.

Partial can be conditionally included using p-if or p-if-not tag attributes:

<div class="user-menu">
    <partial src="./login-form.html" p-if-not="ctx.user.isLoggedIn"></partial>
    <partial src="./logout-form.html" p-if="ctx.user.isLoggedIn"></partial>
</div>

Partial can me mapped over an array of items using p-map tag attribute:

<nav class="mainmenu">
    <partial src="./menu/item.html" p-map="ctx.mainmenu"></partial>
</nav>

Partial can use p-map and p-if/p-if-not attributes simultaneously, which means partial will be mapped over an array of items only if p-if/p-if-not condition evaluates to true:

<nav class="relatedmenu">
    <partial src="./menu/item.html" p-if="ctx.showRelatedMenu" p-map="ctx.relatedmenu" related="yes"></partial>
</nav>

Installation

Using npm:

$ npm install --save-dev @yagni-js/yagni-parser @yagni-js/yagni-dom @yagni-js/yagni

Using yarn:

$ yarn add -D @yagni-js/yagni-parser @yagni-js/yagni-dom @yagni-js/yagni

Usage

Source code is written using ES6 modules, built using rollup and distributed in two formats - as CommonJS module and as ES6 module.

CommonJS usage:

const yp = require('@yagni-js/yagni-parser');

ES6 module usage:

import * as yp from '@yagni-js/yagni-parser';
// or
import { parse } from '@yagni-js/yagni-parser';

Documentation

Not yet available, please check sources.

Example

Suppose we have the following HTML template:

<div class="root">
    <div class="header">
        <h1>Header</h1>
        <partial src="./login-form.html" p-if-not="ctx.user.isLoggedIn"></partial>
        <partial src="./logout-form.html" p-if="ctx.user.isLoggedIn"></partial>
    </div>
    <div class="main">
        <div class="sidebar">
            <nav class="mainmenu">
                <partial src="./menu/item.html" p-map="ctx.mainmenu"></partial>
            </nav>
        </div>
        <div class="content" id="content">
            <div class="content-body" id="content-body">Content</div>
            <nav class="relatedmenu">
                <partial src="./menu/item.html" p-if="ctx.showRelatedMenu" p-map="ctx.relatedmenu" related="yes"></partial>
            </nav>
        </div>
    </div>
    <div class="footer">Footer</div>
</div>

After compilation it will be translated into the following code:

import { isArray, merge, pipe } from "@yagni-js/yagni";
import { h, hText, hSkip } from "@yagni-js/yagni-dom";
import { view as loginFormView } from "./login-form.html";
import { view as logoutFormView } from "./logout-form.html";
import { view as itemView } from "./menu/item.html";


export function view(ctx) {
  return h("div", {"class": "root"}, {}, [
    h("div", {"class": "header"}, {}, [
      h("h1", {}, {}, [
        hText("Header")
      ]),
      !(ctx.user.isLoggedIn) ? (loginFormView(ctx)) : hSkip(),
      (ctx.user.isLoggedIn) ? (logoutFormView(ctx)) : hSkip()
    ]),
    h("div", {"class": "main"}, {}, [
      h("div", {"class": "sidebar"}, {}, [
        h("nav", {"class": "mainmenu"}, {}, [
          isArray(ctx.mainmenu) ? ctx.mainmenu.map(itemView) : hSkip()
        ])
      ]),
      h("div", {"class": "content", "id": "content"}, {}, [
        h("div", {"class": "content-body", "id": "content-body"}, {}, [
          hText("Content")
        ]),
        h("nav", {"class": "relatedmenu"}, {}, [
          (ctx.showRelatedMenu) ? (isArray(ctx.relatedmenu) ? ctx.relatedmenu.map(pipe([merge({"related": "yes"}), itemView])) : hSkip()) : hSkip()
        ])
      ])
    ]),
    h("div", {"class": "footer"}, {}, [
      hText("Footer")
    ])
  ]);
}

License

Unlicense

0.4.1

5 years ago

0.4.0

5 years ago

0.3.0

6 years ago

0.2.1

6 years ago

0.2.0

6 years ago

0.1.1

6 years ago

0.1.0

6 years ago