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

shaman-website-compiler

v4.0.24

Published

Compile raw HTML, CSS and Javascript into the smallest possible, SEO friendly website.

Downloads

341

Readme

Shaman Website Compiler - IoT Shaman

npm badge Build Status Coverage Status

Compile raw HTML, CSS and Javascript into the smallest possible, SEO friendly website.

Build high-quality websites that are SEO-friendly and blazing fast, without all the annoying configuration, minfication, bundling, etc. With Shaman Website Compiler you get all of these features, and more, with a simple configuration file. Some of the features include

  • Javascript file minifcation
  • CSS file minification
  • HTML file minification
  • HTML Templating (with easy-to-configure models)
  • XML Sitemap generator
  • Built-in database connectivity
  • File bundling (to reduce http requests)
  • Dynamic route generation
  • Much more!

Requirements

  • Node JS

Quick Start

To get started, we first need to create a Node JS project and install our dependencies. Create a folder to store your website code, then open a terminal window for that folder.

npm init
npm install shaman-website-compiler --save

Configuring the Website Compiler

The website compiler requires a configuration file to operate properly, so the first thing we need to do is generate this file. Create a file in your project folder called 'website.json' and add the following text:

{
  "production": false,
  "output": "./dist"
}

Once we have a configration file, we need a javascript file to import the Shaman Website Compiler and kick off the build. Create a file in your project folder called 'index.js' and add the following code:

let WebsiteFactory = require('shaman-website-compiler').WebsiteFactory;

// Configuring compiler
let config = require('./website.json');
let website = WebsiteFactory(config);

// Building sample website
website.build().then(routes => {
  // do something (optional)
}).catch(ex => {
  console.log(`Compiler error: ${ex}`);
})

Finally, we need to configure this as our start script in the package.json file. Open 'package.json' and update the scripts object to look like this:

{
  ...
  "scripts": {
    "start": "node index.js"
  }
  ...
}

Configuring your Website

In your website project folder, create a new folder called "src" and add 2 files "index.html" and "index.json" Once you have added these files, your project's folder structure should look like:

my-website
|___ src/
    |-- index.html
    |-- index.json
|-- website.json
|-- index.js
|-- package.json

Now we need to setup the "file model" for our home page. Open the "src/index.json" file and add the following text:

{
  "title": "My Awesome Website"
}

Then, open the "src/index.html" page and add the following text:

<html>
  <head>
    <title>{{model.title}}</title>
  </head>
  <body>
    Hello World!
  </body>
</html>

Notice the text that says "{{model.title}}"? This is a template variable, when the compiler runs it will look into the "file model" that corresponds to your HTML page, which in the event of "index.html" is "index.json". When the compiler renders this template, it will remove everything in between the two braces and inject the variable "title" (found in your file model), resulting in the following HTML:

<html>
  <head>
    <title>My Awesome Website</title>
  </head>
  <body>
    Hello World!
  </body>
</html>

Finally, open a terminal for your project folder and type "npm start". After some output, you should notice that a new folder "dist" was created. Also, the terminal should say "Development server listening on port: 3000"; open a web-browser (Google Chrome, Firefox, Edge) and navigate to "http://localhost:3000", you should see your website content!

Congratulations, you are now up and running with your awesome new website! This is really just the tip of the iceberg that is Shaman Website Compiler, so keep reading below to see how to configure the rest of your website, including javascript files, css files, asset files (images, videos, etc.), and much more.

Application Configuration

Application configuration is managed in the "website.json" file, in your project's root folder. While you can technically store this configuration anywhere, by using "website.json" as your configuration file you will be able to leverage other tooling (coming soon).

export interface IWebsiteConfig {
  root?: string;
  logLevel?: string;
  production?: boolean;
  pages?: string[];
  partials?: string[];
  helpers?: string[];
  scripts?: string[];
  styles?: string[];
  assets?: string[];
  output?: string;
  serve?: boolean;
  sitemap?: {
    hostname: string;
  };
  adapter?: {
    module?: string;
    name: string;
    configuration: any;
  };
}

Configuration settings are entirely optional, and each property has a default value. The below grid gives some additional default to help you configure your website:

|Property|Type|Default|Description| |---|---|---|---| |root|string|"./src"|Path (relative or absolute) to your source files. This is refered to in this document as the source folder. |logLevel|string|"info"|Set log level ("error", "warn", "info", "debug", "silly"). |production|boolean|false|When true, files will be minified and bundles injected. When false, a development server will be started and bundles will be expanded, when injected. |pages|string[]|["**/*.html", "!**/*.partial.html"]|Glob pattern to locate HTML files. |partials|string[]|["**/*.partial.html"]|Glob pattern to locate handlebars "partial" files (see Handlebars Partials). |helpers|string[]|["**/*.helper.js"]|Glob pattern to locate handlerbars "helpers" (see Handlebars Helpers). |scripts|string[]|["**/*.js", "!**/*.helper.js"]|Glob pattern to find javascript files. |styles|string[]|["**/*.css"]|Glob pattern to find css files. |assets|string[]|See Building Asset Files|Glob pattern to find asset files. |output|string|null|Folder path (relative or absolute) to output compiled files. |serve|boolean|true|Turn off development server when not in production. |sitemap|custom|default|See Sitemap Generation |adapter|custom|default|See Database Adapters

Templating Engine

The Shaman Website Compiler comes with handlebars templating engine built-in. Handlebars is a feature-rich, fast, easy-to-use and reliable templating engine, allowing you to separate the logic of your website from the layout. For guidance on how to write handlebars templates, please click here.

Handlebars Helpers

The default method of adding custom handlebars helpers to your website is to create a file with the extension ".helper.js" in your project's source folder (default "./src"); this can be configured in your website.json file. Below is a sample helper file:

handlebars.registerHelper('date', function(date, format) {
  if (!format) format = 'MMMM Do YYYY';
  return moment(date).format(format);  
});

Each helper file has access to 2 global objects (handlers.js and moment.js), and should call handlebars.registerHelper().

Handlebars Partials

The default method of adding "partial" html files is to create a file with the extension ".partial.html" in your projects source folder (default "./src"); this can be configured in your website.json file. Below is an example partial file:

<!-- filename: title.partial.html -->
<title>{{model.title}}</title>

To use the partial file in an HTML page, simply reference its path, relative to the projects source folder, like so:

<head>
  {{> title.partial.html}}
</head>

File Models

Every HTML page may have an optional JSON file, called the "file model", that can configure the HTML page. To add a file model to an HTML page, simply create a file with the same name, but a ".json" extension instead of ".html". The file model can hold any custom properties you want to be available in your template; this data can then be accessed by using the "model" property in your template variable. For example:

sample.json

{
  "foo": "bar"
}

sample.html

<div>
  {{model.foo}}
</div>

The file model can also contain compiler variables, which allow you to configure certain compiler behaviors. To configure compiler behavior, create a property in your file model with the name "shaman". Below is the interface for the compiler configuration:

export interface IFileModelConfig {
  dynamic?: { path: string, name: string };
  query?: QueryModel[];
  bundles?: Bundle[];
  private?: boolean;
}

Dynamic File Configuration

A dynamic file allows you to use 1 template to create multiple pages. To create a dynamic file template, first create an HTML page with your core template, then add a file model. Next, in your file model's "shaman.dynamic" property, add an object that contains a "path" and a "name" property

  • path: URL path for the file (for example, "blog/").
  • name: The name of the property in your query results that represents your URL (filename). For example, if you have a query that returns blog objects, and each blog object has a property called "routeUrl", you could use this for the "name" property. When the compiler runs, each blog item would create a new HTML page with the URL (filename) matching the value in "routeUrl".

Finally, add a query (see Database Queries) to your file model's "shaman.query" array, and set the "dynamic" property on the query to "true".

Database Queries

Your file model can define as many queries as you would like. This requires you to have configured a database adapter (see Database Adapters), and to have an existing storage solution, like a json-repo or Mongo DB. Once you have configured your database adapter, you can create queries. Below is the interface for queries (please note, each query adapter has different requirements for how to use these properties):

export interface IQueryModel {
  name: string;
  path: string;
  dynamic?: boolean;
  args?: any[] | {};
  limit?: number;
  sort?: { key: string, descending?: boolean };
}

For example, here is a json-repo query (see Json Repository Adapter) to fetch the last 10 newest blogs, and the HTML code to display it:

sample.json

{
  "shaman": {
    "query": [{
      "name": "recentBlogs",
      "path": "blogs",
      "args": ["*"],
      "limit": 10,
      "sort": { "key": "date", "descending": true }
    }]
  }
}

sample.html

{{#each query.recentBlogs}}
  <h2>{{this.title}}</h2>
  <small>By {{this.author}}, {{this.date}}</small>
  <br />
  <p>{{this.description}}</p>
{{/each}}

Note: When you set the "dynamic" variable to 'true', you are telling the compiler that this query should create a unique page for each object the query returns; use this in conjunction with the "shaman.dynamic" property to use 1 template to create multiple pages.

Bundles

One way to significantly improve your SEO score is to reduce the amount of different files that need to be loaded for your page to be rendered. A common strategy to accomplish this is to bundle similar files into a single file. To configure a bundle for your HTML page, use your file model's "shaman.bundles" array. Below is the interface for the bundle configuration:

export interface IBundle {
  name: string;
  type: string;
  files: string[];
}
  • name: The name of the bundle, for you to reference in your HTML page.
  • type: Either "js" for javascript bundles, or "css" for CSS bundles.
  • files: List of file paths, relative to your project's source folder.

For example, if you wanted to bundle 2 CSS files that are specific to your "sample.html" page, you could configure it like so:

sample.json

{
  "shaman": {
    "bundles": [
      {
        "name": "sample.styles.bundle",
        "type": "css",
        "files": [
          "styles/index.css",
          "styles/card.css"
        ]
      }
    ]
  }
}

sample.html

<head>
  {{{bundles model.shaman.bundles}}}
</head>

Note: When in development mode (website.json "production = false") your bundles will be generated, but will actually create individual links for each style sheet; this is to help with debugging using developer tools in your browser of choice.

Note: To access another file model's bundle, the Shaman Website Compiler has 2 special handlebars helpers, 'style' and 'script'. Below is an example implementation:

<head>
  {{{style 'name-of-exported-bundle.css'}}}
  {{{script 'name-of-exported-bundle.js'}}}
</head>

Private Files

When you set the 'private' property to true in your file model configuration, you are telling the router to not generate a route for this particular file. This can be helpful if you want to have the contents of the file available to other services, but dont want to output an HTML file.

Database Adapters

If you are simply developing a static website, you probably have no need for a database. However, for more sophisticated, dynamic websites, you will typically need something to store persistent data. The Shaman Website Compiler takes an agnostic approach to persistent storage, using the concept of a database adapter to abstract database communication. Instead of having to worry about writing code to connect to and manage your database, you can simply configure a database adapter in your 'website.json' file and configure your queries in individual file models.

There are 3 built-in database adapters (json-repo, http, and mongo-db) which you can use with minimal configuration. If these built-in adapters do not cover all of your requirements, or you need to use a persistence solution not already built-in, you can create your own implementation of a database adapter and configure it with ease.

Each database adapter implements the below interface; to add your own custom adapter, simply create an implementation of the interface.

export interface IQueryAdapter {
  openConnection: () => Promise<void>;
  run: (query: IQueryModel) => Promise<any[]>;
}

Configuring an Adapter

In order to use queries in your file models, you will need to configure your data adapter. The 'website.json' file has an 'adapter' property that accepts the below interface:

export interface IAdapterConfig {
  module?: string;
  name: string;
  configuration: any;
}
  • module: Name of installed node module (or path to a local js file) that contains your exported implementation of IQueryAdapter. This defaults to "shaman-website-compiler".
  • name: The name of the exported implementation of IQueryAdapter. For example, "JsonRepoAdapter".
  • configuration: Adapter-specific configuration (connection strings, timeouts, etc). Each adapter has its own configuration interface.

Json Repository Adapter

The json repo adapter allows you to use a local JSON file as your data source, and can be used to create dynamic pages, without having to connect to any external databases. When configuring the json repo adatper in your 'website.json' file, use the name value of "JsonRepoAdapter".

The json repo adapter takes a configuration object with the below interface:

interface JsonRepoAdapterConfig {
  dataPath: string;
  models: string[];
}
  • dataPath: Path (relative or absolute) to your persistent JSON file.
  • models: A list containing the names of the objects to store. For example, a website that showcases blogs and products would have 2 models, 'blogs' and 'products'.

To create a json-repo database file, simply add a JSON file anywhere on your computer (preferably in your project folder) and follow the below interface (using the blog / product example from above):

{
  "blogs": [
    {
      "key": "blog1.html",
      "value": {
        "name": "blog1.html",
        "title": "Test Blog",
        "description": "A sample blog entry in json-repo database",
        "content": "This is where you would put the blog content..."
      }
    }
  ],
  "products": [
    {
      "key": "My Awesome Product",
      "value": {
        "name": "My Awesome Product",
        "price": 100.00,
        "description": "Sample description for my awesome product"
      }
    }
  ]
}

Notice each model has an array of objects, each stored in a key-value pair. For more information on the json-repo databse, click here

To configure queries for a json-repo database, use the IQueryModel interface and follow the below implementation guidelines:

  • name: This is the name you will use in your HTML template to refer to this query's result set.
  • path: The name of the model you are querying
  • args: Takes 1-2 arguments. If you provide "*" it will return all objects. If you wish to filter these objects by a property, pass the name of the property in the first argument, then the expected value as the second argument. For example, to filter blogs to only the ones where the property "tag" equals "technology", your "args" property would look like ["tag","technology"].
  • limit: Only returns the top n elements from the result set. This should be used in conjunction with "sort".
  • sort: Sorts the result set, based on a "key" (name of property). Use the "descending" property if you want to reverse the order.

Http Adapter

The HTTP adapter allows you to make HTTP requests to fulfil query requests. If you have an existing RESTful API and want to use this data to populate your website, this is the adapter for your. When configuring the HTTP adatper in your 'website.json' file, use the name value of "HttpAdapter".

The HTTP adapter takes a configuration object with the below interface:

interface HttpAdapterConfig {
  apiBaseUri: string;
}

To configure queries for a RESTful API, use the IQueryModel interface and follow the below implementation guidelines:

  • name: This is the name you will use in your HTML template to refer to this query's result set.
  • path: URL that you wish to query (should be a GET endpoint). For example, if you setup your adapter with the "apiBaseUri" value of "https://mywebsite.com/", and setup a query with the "path" value of "blogs/technology", the query would send a GET request to "https://mywebsite.com/blogs/technology".
  • args: Not used in HTTP adapter queries.
  • limit: Only returns the top n elements from the result set. This should be used in conjunction with "sort".
  • sort: Sorts the result set, based on a "key" (name of property). Use the "descending" property if you want to reverse the order.

Mongo Adapter

The mongo adapter allows you to query a mongo database. If you have an existing Mongo DB database and want to use this data to populate your website, this is the adapter for your. When configuring the mongo adatper in your 'website.json' file, use the name value of "MongoAdapter".

The mongo adapter takes a configuration object with the below interface:

interface MongoAdapterConfig {
  mongoUri: string;
  options: any;
}
  • mongoUri: The URI to connect your Mongo DB instance.
  • options: Connection options for Mongo DB. For a full list of options, click here.

To configure queries for a Mongo DB database, use the IQueryModel interface and follow the below implementation guidelines:

  • name: This is the name you will use in your HTML template to refer to this query's result set.
  • path: The name of the mongo "collection".
  • args: This argument takes a mongo query object, represented in JSON, which will be passed to the "collection.find" mongo request. For example, to find all blogs with the "tag" value of "technology", you could use {"tag": "technology"}.
  • limit: Only returns the top n elements from the result set. This should be used in conjunction with "sort".
  • sort: Sorts the result set, based on a "key" (name of property). Use the "descending" property if you want to reverse the order.

Sitemap Generation

Every time you build your website the compiler will gather all URLs for your website and generate a sitemap. By default, the sitemap generator will use "http://localhost:3000" as the hostname; however, you can configure this in your 'website.json' file. Below is the interface for sitemap configuration:

export interface ISitemapConfig {
  hostname: string;
}

Building Asset Files

Almost every website has files that don't fall into the category of source code; these files are often called assets. For example, websites will tpically have images of some kind, and probably a 'robots.txt' file. The 'website.json' configuration file provides a property, "assets", that allows you to specify what kinds of asset files should be included in the build output. By default, the following files are included:

  • PNG
  • SVG
  • JPG / JPEG
  • ICO
  • TXT

To change this, simply provide an array of glob patterns to the "assets" property in your 'website.json' file. For example, to include all "mp4" and "png" asset files, you would use the following value: ["**/*.mp4","**/*.png"].