DocsCLIPlaygroundRoadmapAbout

Announcing the 2023-2025 Roadmap

Changelog


April 2023

May 2021

April 2021

January 2021

January 2020

November 2019

November 2018

Indigo

v5.1
April 2023

Update to bring quality of life changes to dot-notated json variable expressions and announcing the ftHTML Roadmap

Optional Chaining Operatior

Added optional chaining support ?. to dot-notated json variable expressions

#vars
    animals json('animals')
#end

each @animals {
    @this.species?.subspecies?.name
}

If the key does not exist for a given object the value returned will be that of it's parent data-type (could be an empty array or empty object, for example)

Functions

Added fallback(<function | string | variable> value, <function | string | variable> defaultvalue)

Falls back to a default value if the given value argument is 'absent'

fallback("      ", 'Hello World') //produces Hello World

Onyx

v5.0
May 2021

Update to bring quality of life changes and support additional control flow logic, like loops!

TL;DR: native dot-notation for json variables (no longer requires string interpolation), new functions, new control flow keyword, quality of life changes like being able to pass json variables literal values in property binding for imports or otherwise, extending configs from remote urls and more

Download the vscode extension here

Breaking Changes

Thejson keyword has reverted changes for dot-notation parsing. It felt cumbersome and out-of-line with the initial simplicity vision ftHTML strived for. There is no deprecation support in this case to ensure new adopters get and use the up-to-date conventions.

The TL;DR of those changes is: you longer need the keyword to use dot-notation to destructor json variables. This is natively supported now - you simply use them in the body like any normal variable. To demonstrate:

Old way

#vars
    cars json('foobar')
    bmwModels json('${ cars.bmw.models }')
#end

@bmwModels

New way with native dot-notation support:

#vars
    cars json('foobar')
#end

@cars.bmw.models[0].mpg

FTHTML render and compile functions are now asynchronous due to being able to extend remote configs. Please use proper async/await logic or then/catch statements

const ftHTML = require('fthtml');
ftHTML.renderFile('index')
      .then(console.log)
      .catch(console.log);

Native dot-notation support

JSON dot-notation syntax is now natively supported. You can now access JSON objects or any variables that resolve to an array/object per normal dot-notation conventions:

#vars
    myContacts json('contacts')
#end

p {
    'I have #' len(@myContacts) ' contacts
}
h1 @myContacts[0].name
div @myContacts[0].address[2].state
span 'zip: ${ @myContacts[0].address[2].zip }'

These types of variables (dot-notation) we refer to as 'Literal Variables' because they always return the literal value of what's assigned to it and doesn't get transposed in any way, in almost all cases

This should be a seemless transition and adoption if you are used to javascript dot-notation syntax.

You can even pass destructored variables to imports via property binding and maintain their raw data types

Functions

Added keys()

Return the keys of a given object. Throws if the provided value does not resolve to a literal object

keys(<variable> value)

Added values()

Return the values of a given object. Throws if the value provided does not resolve to a literal object

values(<variable> value)

Added range()

Returns an array of numbers within the given range provided (exclusive).

If the upper bound number is omitted, the starting value is always 0 and the value provided becomes the upper bound

range(<word | len()> start)
      <word | len()> end [optional])

Added sort()

Sort a given value. This is not a true deep sort and does slight validation of data types, but it sorts strings or arrays of strings accordingly, in addition to numbers and objects

sort(<variable> value,
     <string> sort type [optional] = { "asc", "desc" },
     <word> member name [optional])

Updated join()

Updated join to support accepting any function that returns a resolved literal array, like sort(), keys(), values(), str_split()

String Interpolation Piped Lambdas

Syntax

"... ${ <value> | <piped function name> } ..."

You can now pipe results in string templates to lambdas. Piped values can cascade and pipe into multiple piped lambdas

Supported piped lambda functions:

value        |       alias
-----------------------------------------------
asc          |  sort(<value>)
desc         |  sort(<value> 'desc')
lower        |  tcase(<value> 'lower')
upper        |  tcase(<value> 'upper')
capital      |  tcase(<value> 'capitalization')
kebab        |  tcase(<value> 'kebab')
snake        |  tcase(<value> 'snake')
camel        |  tcase(<value> 'camel')
pascal       |  tcase(<value> 'pascal')
alternating  |  tcase(<value> 'alternating')
title        |  tcase(<value> 'title')
keys         |  keys(<value>)
values       |  values(<value>)
choose       |  choose(<...values>)
reverse      |  str_reverse(<value>)
len          |  len(<value>)
trim         |  trim(<value>)
trimEnd      |  trim(<value> 'trimEnd')
trimStart    |  trim(<value> 'trimStart')
trimLeft     |  trim(<value> 'trimLeft')
trimRight    |  trim(<value> 'trimRight)

Example

Control Flow

Each

Also known as a for-each loop, this control flow element loops through a provided iterable object and outputs duplicated html, n times.

Syntax

each <variable | keys() | len() | range() | sort() | str_split() | values()> { ...ftHTML }

Requirements

  • The value provided must resolve to an iterable object, meaning an array of values to some degree

Usage Notes

  • There is currently no limit to the amount of nested control flow elements provided, but please note that there is currently no way to name your own iterable value, so @this only refers to the each loop it's scoped to
  • The @this keyword can be used anywhere and any way a normal variable can be referenced

Example

ul {
    each range(1 16) {
        li(data-value=@this) "This is example #${@this}"
    }
}
<ul>
    <li data-value="1">This is example #1</li>
    <li data-value="2">This is example #2</li>
    <li data-value="3">This is example #3</li>
    <li data-value="4">This is example #4</li>
    <li data-value="5">This is example #5</li>
    <li data-value="6">This is example #6</li>
    <li data-value="7">This is example #7</li>
    <li data-value="8">This is example #8</li>
    <li data-value="9">This is example #9</li>
    <li data-value="10">This is example #10</li>
    <li data-value="11">This is example #11</li>
    <li data-value="12">This is example #12</li>
    <li data-value="13">This is example #13</li>
    <li data-value="14">This is example #14</li>
    <li data-value="15">This is example #15</li>
</ul>

Quality of Life

  • The extends fthtmlconfig.json property now supports external (remote) urls. For example, create a config file to store global vars and templates:
    "globalvars": {
        "DEBUG" : true,
        "author" : "David Freer",
        "homepage" : "https://example.com"
    }

    Now simply reference it in your projects fthtmlconfig extend property and it will inherit the values in the global vars stored in the remote address:

    "extend": ["http://example.com/fthtml/configs/master.json"]

    This works in tandem with extending local and remote configs. Order of precedence is maintained

  • You can now include an optional fthtmlconfig path as an argument for renderFile() and compile() functions:
    const ftHTML = require("fthtml");
    ftHTML.renderFile("index", "absolute_path_to_fthtmlconfig_file.json")
          .then(html => console.log(html))
          .catch(console.log);

    If one is not provided it will work normally as it has been and assume you have some at the project level or cwd, if one is provided it will take precedence over any project config files

Heliotrope

v4.0
April 2021

Another huge, feature-rich update!!

TL;DR: json destructoring via dot-notation, extending tiny template attributes, supporting more dynamic placeholder support, new functions, date formatting support, if-elif-else pragma, ifdef pragma, specify config file path for CLI and more!

This update includes a bunch of support enhancements and user exprience upgrades for the vscode extension. You can download it here

Branding

I have been selfless in my offerings of this package and support. Initially this started as a hobby but turned into something much more rewarding. I am a 1-man show building the parser, testing integration, creating the documentation website, creating vscode extensions and much more to facilitate a good user experience, all the while attending school and going to work full time. This is why updates are not that frequent, but I do my best to ensure they are resourceful updates.

Starting with this verison every top level page that is converted with ftHTML will have a short, very very brief, DOM comment at the top of the page with a link to ftHTML website.

As I continue to passionately build and extend this wonderful package for free, all I ask is that you leave these branding comments as-is just to make it easier to spread the word and help me share this with as many developers as possible. This is free of cost to you and doesn't impact any other element of creating ftHTML files like you've learned to use to-date

I hope you understand and thank you for using ftHTML!

Tiny Templates

Extended Attributes

Previously, tiny templates would only honor attributes specified in the declaration:

#tinytemplates
    code-inline span(.code-inline)
#end

code-inline "foobar"
//produces: <span class="code-inline">foobar</span>

This update introduces the support to extend attributes from the caller, so it functions basically like every other element now:

#tinytemplates
    code-inline span(.code-inline)
#end

code-inline(.bold .italic) "foobar"
//produces: <span class="code-inline bold italic">foobar</span>

Placeholder Alternatives

This update also supports giving users the choice of where placeholders are provided when declaring a tiny template.

Now, the value doesn't have to exclusively hold a placeholder; you can now include placeholders in attribute kvps, but tiny templates still require at minimum 1 placeholder if a value is provided:

#tinytemplates
    href    a (href="${val}" target=_blank)
#end

href "www.google.com"
//produces <a href="www.google.com" target="_blank">www.google.com</a>

For a complete overview of the new additions, review the documentation here

New Pragmas

#if-el(if|se)

Big update that adds support for dynamic content with complete control via a user-defined decision tree using #if-#else directive. I am extremely excited to announce that you can now dynamically generate HTML based on the result of an expression provided:

//assume @title = Home
#if @title ie "home"
    h1 "Hello World - Home Page"
#end

//produces <h1>Hello World - Home Page</h1>

This can be extended to an unlimited number of if/else statements, and there is currently no limitations on how many nested if statements you can provide, pending performance review and community feedback:

//assume __LOCAL_DATE__ = 25 Dec 2021
#if __LOCAL_DATE__ starts "1 Jan"
    h1 "Happy New Year!"
#elif __LOCAL_DATE__ starts "4 Jul"
    h1 "Happy 4th of July!"
#elif __LOCAL_DATE__ starts "31 Oct"
    h1 "Happy Halloween"
#else
    h1 "Hello World"
#end

//produces <h1>Hello World</h1>

There is a plethora of out-of-box operators to take advantage of and the arguments are not limited to simply strings or variables like demonstrated above, please review the pragma documentation to learn more about all the flexible usages of this new pragma here


#ifdef

#ifdef affords you insight on if a variable or tiny template is defined. Then, it works similar to the #if-else pragma, where the content is generated only if the variable or tiny template exists

This is useful in this update because variables and tiny templates can be defined globally as well as locally in the current file. Additionally, with the support to extend fthtmlconfig files from other directories, it's useful to have insight if something exists or not so it's not to crash the program, throw errors or otherwise if that extended config file changes

Simply call the varibale or tiny template without any symbol prefixes or identifiers:

//assume @author = David Freer
#ifdef author
    h1 "Created by ${ @author }"
#end

Since vars can be re-instantiated at any time, you can prove something exits only under certain conditions:

#if a eq b
    #vars
        author "David Freer"
    #end
#end

#ifdef author
    h1 "Created by ${ @author }"
#end
// produces nothing since a does not equal b - therefore the variable does not get instantiated

Functions

Added len()

Returns the length of a given string, object or array dynamically

len(<string | variable | function | word> value)

Added str_split()

Split a resolved string by a given delimiter

str_split(<string | variable | macro | function> value, <string | variable> delimiter)

Added join()

Returned a joined iterable object by a given delimiter. You must only pass variables as the value argument. Throws if the object in the variable is not an array or object. When an object is provided, the keys will be joined

join(<variable> value, <string | variable> delimiter)

Updated str_format()

str_format now includes date formatting support

Review the function documentation for a complete overview of these additions here

JSON

This update adds on to the last releases native json support by provididng support to sort of destructor json in a more easy-to-consume format

You can now destructor a json element by simply calling the jsonfunction, but instead of passing a file, call the object or indice of a previously declared json variable:

/*
assume ./my-pets.json =
{
    "zeek" : {
        "type": "dog",
        "breed" : "great dane",
        "age": 4,
        "gender" : "male"
    },
    "sky" : {
        "type" : "dog",
        "breed" : "siberian husky",
        "age" : 3,
        "gender": "female"
    },
    "willie": {
        "type": "cat",
        "breed": "aegean",
        "age": 5,
        "gender" : "male"
    }
}
*/

//ftHTML
#vars
    pets      json("./my-pets")

    pet-names json("${ @pets | keys }")
    oldestPetAge json("${ @pets.willie.age }")
#end

div {
    "I have " len(@pet-names) " pets: "
    join(@pet-names ", ")
}

div "My oldest pet is ${ @oldestPetAge } years old"

//produces
//<div>I have 3 pets: zeek, sky, willie</div>
//<div>My oldest pet is 5 years old</div>

For a complete overview of the new additons see the documentation here

CLI

This update allows you to specify an fthtmlconfig file path in the CLI convert command:

fthtml convert ./ --dest ./www -config='../../foo/bar/fthtmlconfig.dev.json'

Fixes

This update just adds some more strict checks for declaring global keywords or global tiny templates with reserved keywords

The extend property of an fthtmlconfig file now extends from the originating (child) config file, instead of the cwd

Smaragdine

v3.0
January 2021

The biggest and most feature-rich update to-date!!

TL;DR: global variables, native json, macros (like __NOW__, __DATE__, __LOCAL_DATE__, __UUID__, etc), functions (like random, choose, substring, tcase, trim, etc), forced relative imports, better prettify, introducing tiny templates (and global tiny templates), extendable fthtmlconfigs and ftHTML Blocks!

Breaking Changes

Thetemplate keyword was deprecated in v2.1.5. This keyword is now considered invalid syntax as it conflicts with a native HTML tag 'template'. It has become obsolete in this update. All imports and templates are now both intuitively handled by a single import keyword.

The TL;DR of those changes is: instead of using a dedicated keyword for templates and property binding, you simply add the body, { }, identifiers and place the properties there. To demonstrate:

Without property binding (a regular import, no changes here):

import "header"

With property binding (new template syntax):

import "header" {
    foo "bar"
    foobar "baz"
}

The escaping sequence has been updated to reflect a more ftHTML-like philosophy. Previously, to produce a single backslash, it required 2 like you would normally expect: \\

However, with the new update there is additional features that make this a chore and really complicates the design philosophy of ftHTML.

For example, the new replace function takes a regex pattern that uses regular JavaScript literal regex flavor. Previously, in order to simply have a pattern for whitespace it would require the following: \\\\s+ . As you can see, this can quickly get out of hand.

The changes that affect escaping is that backslashes are now parsed as-is, except when immediately before a string delimiter, \' or " otherwise it's a one-to-one output. This means you can not end a string with a backslash now.

Using the example above, the new whitespace pattern should look more familiar \s+ and 2 backslashes will produce an equivalent 2 backslashes: \\ instead of 1

Global vars

You can now identify global variables in a fthtmlconfig file. This allows you to quickly call and reference variables in multiple files, without the need to declare or assign them in their respective file.

As demonstrated above in the index.fthtml file, referencing a global variable is literally no different than calling a variable declared in a file; fthtml handles calling a global var if a local one does not exist. No special syntax, or different syntax, then what you already know.

It's important to note that globalvars cascade, much like CSS rules, these variables can be overwritten in a specific file, if so desired. There is nothing special to do here either, simply assign the variable like normal and the global variable's value will be overwritten for this specific file only:

Tiny Templates

Introducing tiny templates in v3.1.0

Tiny templates are ways to quickly create re-usable elements that only take a single value argument. These are different than templates that use the import keyword. Templates are set up in a way in which you define the property names and bind to those names. Tiny templates expect a single tag or string value that you don't bind to, you simply just call the tag name per normal.

You can think of tiny templates as aliases to elements

ftHTML Blocks

Previously, variable and property binding values could only be strings. This update incorporates a powerful alternative to that by supporting ftHTML Blocks. There is now no need to type awkward HTML inside string values anymore!

For example, previously, if you wanted to have a <head> element as a template, it would look something similar to:

head {
  meta(charset=utf-8)
  meta(http-equiv=X-UA-Compatible content="IE=edge")
  meta(name=viewport content="width=device-width, initial-scale=1")
  title "${ title }"

  "${ scripts? }"
  "${ stylesheets? }"
}

In that example, if you wanted to bind to the optional stylesheets or scripts property and include links or scripts, you would have to type those in raw HTML:

html {
  import "head" {
    title "Contact Page"
    stylesheets
      "
      <link rel="stylesheet" href="/css/components.css" />
      <link rel="stylesheet" href="/css/main.min.css" />
      "
    scripts
      "
      <script src="/js/components.js" defer></script>
      <script src="/js/main.js" defer></script>
      "
  }

  body
  {
    h1 "Hello World"
  }
}
<html>

  <head>

    <title>Contact Page</title>

    <link rel="stylesheet" href="/css/components.css" />
    <link rel="stylesheet" href="/css/main.min.css" />

    <script src="/js/components.js" defer></script>
    <script src="/js/main.js" defer></script>

  </head>

  <body>

    <h1>Hello World</h1>

  </body>

</html>

To eliminate this awkward scenario and other similar scenarios, ftHTML Blocks can now be used by simply adding the children identifier, curly braces { }, and continue typing in ftHTML syntax per normal:

html {
  import "head" {
    title "Contact Page"
    stylesheets {
      link(rel=stylesheet href="/css/components.css")
      link(rel=stylesheet href="/css/main.min.css")
    }
    scripts {
      script(src="/js/components.js" defer)
      script(src="/js/main.js" defer)
    }
  }

  body
  {
    h1 "Hello World"
  }
}
<html>
  <head>

    <title>Contact Page</title>

    <link rel="stylesheet" href="/css/components.css" />
    <link rel="stylesheet" href="/css/main.min.css" />

    <script src="/js/components.js" defer></script>
    <script src="/js/main.js" defer></script>

  </head>

  <body>

    <h1>Hello World</h1>

  </body>

</html>

It feels more comfortable and fluid and ftHTML Blocks are consumed like any other ftHTML child element. The variables, and scopes of variables, are maintained and require no specific consideration.

I am elated to demonstrate, as discussed, this works with variable declarations as well:

html {
  #vars
     myFavoriteLink {
        a(href="www.example.com" target=_blank) "Foo bar"
     }
  #end

  body @myFavoriteLink
}
<html>

  <body>

    <a href="www.example.com" target="_blank">Foo bar</a>

  </body>

</html>

Native json

An interesting addition in this update is native support for json binding. You can now link a json file's contents to a variable and access its properties using dot notation syntax.

A high-level demonstration:

JSON binding only works as a variable assignment, and by using the 'json' identifier.

Functions

This update introduces functions. ftHTML functions are fundamentally all the same syntactically; the syntax looks like the following:

func_name(...args)

Some functions have specific parameters, like specifying the case type for 'tcase':

div tcase("hello world" "upper")

Most function value parameters can be strings, variables, macros or other functions. Please see the documentation for a complete overview and thorough description of each function and its requirements.

Macros

This update introduces macros. ftHTML macros are effectively a way to quickly insert some common fragments of code or data. Whenever the name is used it is replaced by the contents of the macro.

Macros are considered reserved keywords and can not be used as an element name.

Macros are case sensitive and can only be used as values of elements. The syntax is the name of the macro prefixed and suffixed with 2 underscores:

__MACRO_NAME__

Some macros can even be used to insert commonly queried javascript code, like displaying the local date of the end user: div __JS_LOCAL_DATE__; which will output:

<div>
   <script>
      const [day, month, date, year] = new Date().toDateString().split(' ');
      document.write(`${date} ${month} ${year}`);
   </script>
</div>

Please see the documentation for a complete overview and thorough description of each macro.

Forced relative imports

This update adds support for relative imports

If you are using an fthtmlconfig.json file to convert your files, the import directory you set in that config file can be ignored selectively now by importing by reference, prefixing a given path with an & symbol:

Misc

  • Prettify
    • The functionality of the CLI --pretty flag remains the same, although it has been decided in the best interest of time and value to use the js-beautify npm package for beautifying HTML output
    • The default output is still minified
  • Added globalTinyTemplates property to fthtmlconfig
  • Added extend property to fthtmlconfig
  • Variables as property binding values are now supported
  • Added .fthtml/imports to CLI excluded defaults

Amaranth

v2.1
January 2020

Deprecated

The+ symbol has been deprecated. This symbol is now considered invalid syntax. It provided no value as most users perferred not to use it. To concatenate elements now, simply make them siblings to another element
Thetemplate keyword has been deprecated. This keyword is now considered invalid syntax as it conflicts with a native HTML keyword 'template'. It will become obsolete in the next release where imports and templates will both be intuitively handled by theimport keyword.

Typescript

Developement moved to typescript

No changes to how ftHTML syntax works but more enforcements for some use cases have been implemented to better support future features. This shouldn't have changed much, if anything at all, but it's best to always test your project with this new version

End users don't even need typescript installed to use since the js counterparts are included with the lib

Parser Redone

Overall better parser and lexer techniques

  • Compile time is now 90% faster! A file using imports with the old version clocked in at ~100ms to compile. A file with imports now is ~10ms, depending on the content of course. Building the ftHTML website, with a bunch of imports and templates (over 50 files) now only takes a whopping 103ms!

Changes to errors and error messages

CLI

Removed useless dependencies ('cli-spinner' was just for show, provided no value)

Moved away from 'node-dir' package to the 'glob' package because the features are more valuable

  • You can now use glob patterns for excluding directories. See examples here
  • NOTE YOU WILL NEED TO UPDATE YOUR EXCLUDED ARRAY if you have saved them with some kind of task or script for building/converting

Introducing the fthtmlconfig.json file (out of beta). Review how it works here

  • Easily convert a file or directory to html by saving your configurations to a json file
  • When this config file is in your project root dir and you configure it to your liking, all you have to do is execute the `fthtml convert` CLI command from your root dir and that's it!

Glaucous

v2.0
November 2019

Error handling

Overall better error handling & reports

Templates

Added support for basic templates

  • Use the new 'template' keyword
  • Every template requires an import statement
  • Set a binding properties value

String Interpolation

Added support for string interpolation. String interpolation currently supports variables and property binding

${ <@variable/title> }

Embedded Languages

Added support for embedded language tags

Current support for css, js and php

Enter raw input inside the tag for as-is parsing

Misc

  • Attribute values are now automatically converted to be wrapped in double quotes at time of parsing.
  • Updated VSCode extension to reflect error handling changes and new keywords
  • Added support (beta) for a config file to VSCode & CLI. Add 'fthtmlconfig.json' to your projects root directory for easier converting and exporting. The VSCode extenstion will automatically convert based on the settings on every save. The CLI will only need to use 'fthtml convert', depending on your configs. (Again, in beta).

V1.0

v1.0
November 2018

Hello World.