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

publican

v0.3.0

Published

Simple and fast HTML-first static site generator

Downloads

18

Readme

Publican

Publican is a tiny, simple, and very fast HTML-first static site generator.

This is a beta product! Please use it at your own risk!

Full documentation, examples, and starter templates coming soon!

Features:

  • lightweight EcmaScript module
  • templating handled with standard JavaScript template literals ${ expression }
  • !{ expression } values are converted to ${ expression } at build time. Templates can be partially-built where possible and used in Express.js or other frameworks with jsTACS
  • automatic markdown conversion with block and inline code syntax highlighting
  • automatic creation of page navigation, in-page heading contents, paginated posts, and paginated tag lists
  • renders HTML or any other text-based file types
  • pass-through file copying
  • custom string replacement
  • automatic minification options
  • hooks for custom processing functions
  • watch mode
  • works on Windows, Mac OS, and Linux

Quick start

If necessary, create a new Node.js project directory:

mkdir mysite
cd mysite
npm init

Add "type": "module", to package.json to use EcmaScript modules by default.

Install Publican:

npm i publican

Create markdown or other content files in the src/content/ sub-directory. For example, src/content/#index.md:

---
title: My Publican site
---

This is my new static site!

*Under construction!*

Create HTML template files in the src/template/ sub-directory. For example, src/template/default.html:

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>${ data.title }</title>
  </head>
  <body>
    ${ include('_partials/header.html') }
    <main>
      <h1>${ data.title }</h1>
      ${ data.content }
    </main>
  </body>
</html>

The template above includes a partial at src/template/_partials/header.html:

<header>
  <nav><a href="${ tacs.root }">HOME</a></nav>
</header>

Create a configuration file in the project root, e.g. publican.config.js (use a .mjs extension if "type": "module", is not set in package.json):

import { Publican } from 'publican';
const publican = new Publican();

// clear build directory (optional)
await publican.clean();

// build site
await publican.build();

Build the site to the ./build/ directory:

node publican.config.js

Content files

Publican ignores all content files with names starting with an underscore, e.g. _draft.md. This restriction does not apply to template files.

Front matter

You can add any front matter to content files but the following values control publication:

|name|description| |-|-| |title|page title (optional)| |menu|title used on menus or set false to omit (optional)| |slug|page slug (optional)| |template|HTML template filename (in template directory)| |tags|comma-delimited list of tags| |date|date of post| |publish|date of publication or draft determine whether post is published| |priority|post priority from 0 (least important) to 1 (most important)| |index|indexing frequency (daily, weekly, monthly, yearly) or false to not index| |debug|set true to output content properties|

Note that front matter can contain ${ expressions }.

Content

Add a <nav-heading></nav-heading> element in any content or template to add a contents section with links to the main content headings (H2 to H6).

Post properties

Post information can be analysed and used in templates with a data object that has the front matter properties above, any custom properties you set, and the following properties:

|name|description| |-|-| |filename|original name of data file| |slug|rendered file name| |link|link to post| |directory|top-level directory name| |date|date of post| |publish|false if the post is not to published| |priority|post priority from 0 - 1| |isMD|content provided in markdown format| |isHTML|content is rendered to HTML| |isXML|content is rendered to XML| |index|indexing frequency (daily, weekly, monthly, yearly) or false if not indexed| |tags|array of tag objects: { tag, ref, link, slug }| |content|page content| |contentRendered|final rendered page content (post templating)| |wordCount|content word count| |postnext|data object of the next post in the directory| |postback|data object the previous post in the directory| |pagination|pagination data|

Pagination properties

The data.pagination object contains the following properties when available:

|name|description| |-|-| |page|array of pages| |pageTotal|total number of pages| |pageCurrent|current page number (zero based)| |pageCurrent1|current page number (one based)| |subpageFrom1|post number from| |subpageTo1|post number to| |hrefBack|link to previous paginated page| |hrefNext|link to next paginated page| |href|array of links to all paginated pages|

Global values

The following values are available in all pages:

|name|description| |-|-| |tacs.root|root build directory (defaults to /)| |tacs.all|Map object of all posts indexed by slug| |tacs.dir|Map object of all posts in a root directory. Returns an array of posts.| |tacs.tag|Map object of all tags. Returns an array of posts.| |tacs.tagList|array of tag objects: { tag, ref (normalized tag), link, slug, count }| |tacs.nav|nested array of navigation item objects: { data: {}, children [ { data,children },... ] }|

Publican configuration

Publican configuration is set in a publican.config object with the following properties (defaults shown in brackets):

|property|description| |-|-| |.dir.content|content directory (./src/content/)| |.dir.template|template directory (./src/template/)| |.dir.build|build directory (./build/)| |.defaultHTMLTemplate|default template for HTML files (default.html)| |.root|root path (/)| |.ignoreContentFile|ignore file regex (anything starting _)| |.slugReplace|slug replacer Map| |.frontmatterDelimit|front matter delimiter (---)| |.indexFrequency|default indexing frequency (monthly). Set false to prevent sitemap indexing| |.markdownOptions.core|markdown-it core options object| |.markdownOptions.prism|markdown-it-prism syntax highlighting options object| |.headingAnchor|heading anchor and contents block options object| |.dirPages|directory pages options object| |.tagPages|tag page index options object| |.minify|HTML minification options object| |.passThrough|file copy Set| |.replace|string replacer Map| |.processContent|function hook Set for content files (slug, object)| |.processTemplate|function hook Set for template files (slug, string) - returns string| |.processPreRender|function hook Set prior to rendering (slug, object)| |.processPostRender|function hook Set post rendering (slug, string) - returns string| |.watch|enable watch mode (false)| |.watchDebounce|watch debounce in milliseconds (300)| |.logLevel|log verbosity, 0 to 2 (2)|

Pass-through files

Copy static files into the build directory using:

publican.config.passThrough.add({ from: <src>, to: <dest> });

where:

  • <src> is a source directory relative to the project root, and
  • <dest> is a destination directory relative to the build directory

Examples:

// copy static files
publican.config.passThrough.add({ from: './src/media/images/', to: 'images/' });
publican.config.passThrough.add({ from: './src/css/', to: 'css/' });

Custom string replacement

Built files can have strings replaced:

publican.config.passThrough.set(<search>, <replace>);

where:

  • <search> is a search string or regular expression
  • <replace> is the replacement string

Examples:

// replace __YEAR__ with the current year
publican.config.passThrough.set( '__YEAR__', (new Date()).getUTCFullYear() );

// replace text in <p class="bold"> with <p><strong>
publican.config.passThrough.set( /<p class="bold">(.*?)<\/p>/ig, '<p><strong>$1</strong></p>);

Custom jsTACS data and functions

The tacs object can have any global data or functions appended although you should avoid changing the default global values. For example:

tacs.generator = {
  name: 'Publican',
  url: 'https://www.npmjs.com/package/publican/'
}

Any template can now use a render-time expression such as ${ tacs.generator.name }.

You can also create reusable functions to simplify rendering, e.g.

tacs.exec = tacs.exec || {};

// output links to most recent pages
tacs.exec.listRecent = (list, maxSize = Infinity) => {

  // copy list
  list = [ ...list ];

  // limit size
  list.length = Math.min( list.length, maxSize );

  // sort and generate list of links
  let ret = list.
    .sort((a, b) => b.date - a.date)
    .map(i => `<li><a href="${ i.link }">${ i.title }</a></li>`)
    .join('\n');

  // make into list
  if (ret) ret = `<ol>\n${ ret }</ol>\n`;

  // return to template
  return ret;

};

Templates can now use this function, e.g. list the ten most recent posts in the article directory:

<p>Ten most recent articles:</p>
${ tacs.exec.listRecent( tacs.dir.get('article'), 10 ) }

Virtual content and templates

Content can be programmatically added by calling:

publican.addContent( <filename>, <content> );

where:

  • <filename> is a virtual filename such as article/mypost.md, and
  • <content> is the content of that file

Example:

publican.addContent(
  'article/vpost.md',
`
---
title: Virtual post
---
This is a virtual post!
`
);

This would render a file to article/vpost/index.html in the build directory.

Similarly, a virtual template can be added by calling:

publican.addTemplate(
  'mytemplate.html',
  '<!doctype html>\n<html>\n<head>\n<meta charset="UTF-8">\n<title>${ data.title }</title>\n</head>\n<body>\n${ include("_partials/header.html") }\n<main>\n<h1>${ data.title }</h1>\n${ data.content }\n</main>\n${ include("_partials/footer.html") }\n</body>\n</html>'
);

Content can refer to this template in front matter using template: mytemplate.html.

Note the template string cannot be delimited with ` backticks if they contain ${ expressions }.

Processing function hooks

Plugins or configuration code can define custom functions to alter data at build time.

To process content data when it's initially loaded, add a .processContent function. The function is passed the file name and data object which it can manipulate (return values are ignored). The following example prepends "POST:" to every title:

publican.config.processContent.add(
  (filename, data) => data.title = 'POST: ' + data.title
);

To process a template string when it's initially loaded, add a .processTemplate function. The function is passed the file name and the template string which it can manipulate and return. The following example replaces all instances of __COPYRIGHT__ with a © symbol:

publican.config.processTemplate.add(
  (filename, template) => template.replaceAll('__COPYRIGHT__', '&copy;')
);

To process content data before it's rendered, add a .processPreRender function. The function is passed the data object which it can manipulate (return values are ignored). The following example sets a renderTime value to the current datetime on all output HTML files:

publican.config.processPreRender.add(
  (filename, data) => {
    if (data.isHTML) data.renderTime = new Date();
  }
);

To process the fully rendered content prior to minification, add a .processPostRender function. The function is passed the data object and the final output string which it can manipulate and return. The following example inserts a meta tag into HTML content:

publican.config.processPostRender.add(
  (data, output) => output.replace(
    '</head>',
    '<meta name="generator" content="Publican" />\n</head>'
  )
);

Notes

Avoid JavaScript expressions in markdown content. Simple expressions are generally fine, e.g. ${ data.title }, but the markdown process will:

  • alter complex expressions such as ${ data.all.map(i => i.title) }, and
  • escape expressions in code blocks so no code will run.

To work around these restrictions, you can:

  1. Use the ${{ expression }} notation. This denotes a real expression irrespective of where it resides in the markdown.
  2. Move expressions into the HTML template file.
  3. Create jsTACS functions to simplify the expression.
  4. Change the .md file to .html and author HTML instead.

You can used escape characters to avoid any template expression processing:

  • for ` backticks, use &#96;
  • for ${, use &#36;{
  • for !{, use &#33;{