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 🙏

© 2024 – Pkg Stats / Ryan Hefner

@egst/metalsmith

v1.0.12

Published

Light weight client-side JavaScript web tools including a template engine and more.

Downloads

8

Readme

MetalSmith

npm (scoped) GitHub GitHub top language

GitHub last commit David

MetalSmith provides client-side JavaScript web tools. It includes a template engine, design components and native JavaScript objects and primitives extensions - all carefully homebrewed with love and passion and no third party ingredients. The libraries provided are lightweight, simple, yet powerfull and modern.

MetalSmith is usefull for building small websites, especially when one needs all the basic tools without loosing the control over the code like when using complex frameworks.

All the libraries provided are curently under active development. They still lack many important features, but they will be added as soon as possible. Many of the missing features, that will be added are mentioned in this README as To be done.

Dependencies and installation

As mentioned, MetalSmith is a set of several libraries, that work together, but may be used separately, when needed. This repository contains only the index.js file to export all the components of the libraries provided and some meta regarding the source of those libraries. All the libraries are published on GitHub and npm.

To use the complete MetalSmith package, install npm and in the directory of your project use:

npm install @egst/metalsmith

This will install the provided libraries too.

Template engine - BlackSmith

Generating dynamic webpages on server-side is usually easier. However, as soon as you get to programming the client-side stuff (complex animations and styles etc.) you realize, that separating those two just makes it a complete mess. On the other hand, generating them on client-side may be more complicated, as there is no direct, native support for that in JavaScript. Even though native JavaScript provides some solid and powerfull tools to work with DOM, there's just nothing to simplify it and make it as easy as writing a simple HTML markup. So that's exactly what BlackSmith, the MetalSmith's template engine, does. It uses the native JavaScript tools and makes them easier to use in an environment of modified HTML markup.

You pick your project's diretory layout. The default is:

...
view/
    tpl/ [ The modified HTML templates ]
        base.html
        header.html
        ...
    data/ [ The data linked to the templates ]
        base.en.json
        header.en.json
        header.eo.json
        ...
    style/ [ CSS styles relating to the templates ]
        base.css
        header.css
        ...
...

That's how the "visible" part of the webpage may be layed out. You can choose different layouts, and different file extensions.

[ You choose your default language - en for english in the example. You can then add some data to the templates in different languages - eo for esperanto in the example. The data are loaded from the current language and when not found, they are searched in the default one. ] -- To be done. For now, use one language - english by default.

So let's make a base template, that will include a header template. Let's start with the modified HTML markup inside the base.html file:

<div class="base">
    <tpl tpl-include="header"></tpl>
    <p> Lorem ipsum dolor sit amet... </p>
</div>

As you can see, there's a special custom HTML tag provided - <tpl>. It doesn't follow the hyphen rule, but don't worry, these tags are removed ("unwrapped") before the page loads and won't make your page non-standard in any way. So <tpl> is basiaclly a wrapper tag, that serves as a container for other stuff as you will see later.

Also, there are some custom attributes provided - like the tpl-include in the example. Those also don't follow the data- rule, but they are removed as well, so no worries. These attributes work on the usual HTML tags as well, it's just that in this case we want to use the <tpl> tag as a wrapper for the content that comes inside and then unwraps into the parent element in place of the <tpl> tag. The tpl-include=[name] attribute implcitly creates a new template named name, which means that it is linked to the name.html, name.[lang].json, and name.css files, and includes the parsed content inside the tag with such an attribute.

So how to include the first (base) template into your page? You simply create a index.html file as usual with contents:

<html>
    <head>
        <!-- ... -->
        <script type="module" src="index.js"></script>
        <!-- ... -->
    </head>
</html>

You need no <body>. The whole content of <body> can be generated with BlackSmith. We'll do that in the index.js file:

import { Template } from '/node_modules/@egst/metalsmith/index.js'

let tpl_base = new Template('base')

tpl_base.loadTo(document.querySelector('body'))

Remember how the tpl-include creates a template implicitly? That's how you create it explicitly. You say new Template('name'). This creates a template linked to files named name.* in the default directory layout and language. To change the directory layout or language use:

tpl_base.tpl_dir = 'my/tpl/dir'`
tpl_base.data_dir = 'my/data/dir'`
tpl_base.tpl_format = 'my_extension'`
tpl_base.data_lang = 'my_language'`

Then to insert the first template somewhere in the page you just say tpl_base.loadTo(HTMLElement), where the HTMLElement may be aquired in any way you like - I preffer the native tools, you may prefare jQuery or such, but BlackSmith tries to be as native as possible.

That's it. The loadTo method returns a promise, that resolves after the content is parsed and loaded (including all the includes), so you can wait for the promise to resolve and continue doing some amazing stuff after it.

Now let's do the header template. Let's say you need there to be some <nav> with links to other pages, some with text, some with icons, some with both. We'll use the Font Awesome icons for simplicity.

You specify all the information needed for the nav in the header.json file:

{
    "nav_items": [
        {
            "icon": "home",
            "link": "link/to/home"
        },
        {
            "label": "about",
            "icon": "info",
            "link": "link/to/about"
        },
        {
            "label": "projects",
            "link": "link/to/projects"
        }
        },
        {
            "label": "hidden",
            "link": "link/to/hidden"
        }
    ]
}

Now you can iterate over these items and apply some conditions right in the modfied HTML markup inside the header.html file:

<nav>
    <a tpl-for="item in nav_items" tpl-if="${item.label} != 'hidden'" href="${item.link}">
        <tpl tpl-if="'icon' in item">
            <i class="fas fa-${item.icon}"></i>
        <tpl>
        <tpl tpl-if="'label' in item">
            ${item.label}
        <tpl>
    </a>
</nav>

This will be parsed into regular HTML and will be included in the base:

<div class="base">
    <nav>
        <a href="link/to/home">
            <i class="fas fa-home">
        </a>
        <a href="link/to/about">
            <i class="fas fa-info"> about
        </a>
        <a href="link/to/projects">
            projects
        </a>
    </nav>
    <p> Lorem ipsum dolor sit amet... </p>
</div>

The tpl-for loop currently works only with the tpl-for..in syntax, which is actually equivalent to the JavaScript for..of loop. This will be probably changed to match the JavaScript style, but for now it stays - just use the tpl-for="elem in elems" syntax to get the members of the elems array as the elem variable in each iteration. Only arrays work for now, e.g. objects with numeric keys. (To be done...)

To explain what was done here, first let's take a look at how BlackSmith substitutes variables. You define some variable (json object key) "foo" in the data file. It may be a primitive or an object (or an array). It can then be accessed from the template file as ${foo}. Any occurrance of ${foo} will be replaced by the (implicit string) value of the foo variable. You can access its sub-objects/variables by ${foo.bar} and even ${foo[2]} or ${foo["baz"]}.

If you want a variable to be named with spaces, or any other characters, that would be valid as an object key, but not as a regular variable (e.g. accessible via [], not .), there's no problem: ${baz["foo bar"]}. But when it's a first-level key of the json object, you need to use the brackets too: ${["foo bar"]}. It is possible to combine the dots and brackets in any way: ${["foo"].bar[3].baz} will be interpreted exactly as data["foo"].bar[3].baz.

The tpl-for is used to generate similar HTML markup for every element in an array defined in the data file. The syntax is:

`tpl-for="elem in elems"`

It modifies the ${} expressions in the whole outerHTML of the affected element in the following way: When ${elem.*} is found, it is replaced by ${elems[i].*} in the i-th iteration. (...To be done: The same thing for non-numeric keys.) This is done before the variable substitution, so no variable substitution in the tpl-for. (To be done.)

The variable substitution comes right after parsing the loops, then come the conditions. tpl-if attribute accepts one or two operands with an operator. If the condition is false, the whole element affected is removed. The syntax is:

`tpl-if="operand1 operator operand2"`

The spaces are optional for non-alphabetic operators. The available operators are:

  • = or ==: Both mean strict equality. (===)
  • !=: Strict inequality. (!==)
  • <, >, <=, >=: The same meaning as in JavaScript.
  • in: operand2.includes(operand1) for arrays and operand1 in operand2 for other objects.

The operands may be literals such as numbers, arrays or objects:

tpl-if="3.5 in [1, 3.5, 5]"
tpl-if="5 in {1: 0, 5: 2}"
tpl-if="5 > 3"

or even strings explicitly enclosed in additional quotes:

tpl-if="'foo' in {'foo': 'bar', 'boo': 'baz'}"
tpl-if="'foo' != 'boo'"
tpl-if="'foo' > 'boo'"

or variables from the data file:

tpl-if="foo in bar"
tpl-if="5 in bar"
tpl-if="foo = 5"

It is even possible to use variable substitution as the variables are substituted before the conditions are parsed. Like in the example above:

tpl-if="${item.label} != 'hidden'"

So that's basically all you can do in the template files. The example above should now make sense.

More goodness is yet to come, like declaring or changing existing variables. (To be done) Adding more variables besides the ones in the data file is possible right in the JavaScript loading the explicit templates:

tpl_base.addData({foo: 'bar', boo: 'baz'})

Which uses simple Object.assign.

Or if you have multiple templates, you may link those with:

tpl_sidebar.linkData(tpl_base)

This allows access to the tpl_base.[lang].json (and the data manually added to tpl_base) from the tpl_sidebar.html as base.foo. In fact, this is done implicitly when using tpl-include. So you could have the "nav_items" stored in the base.[lang].json file and access them from header.html as base.nav_items. If you had yet another template included in header.html, it could be accessed in the same way along with the data in header.[lang].json as header.foo.

There's one more way to add more data:

tpl_sidebar.data.inherit(tpl_base.data)

This method is not yet implemented as a regular feature, so it has to be done explicitly on the underlying Data object. It does exactly what it says. tpl_sidebar data now inherit from the tpl_base data in the same way a JavaScript object would inherit from its [[Prototype]] (__proto__). So any variables defined in the base.[lang].json file would be accessible from the sidebar.html file as if they were defined in the sidebar.[lang].json file and once the same variables are defined in the sidebar.[lang].json file, they "shadow" the original ones.

Design components - WhiteSmith

README overview to be done.

Natives extensions - MetalForge

README overview to be done.