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 🙏

© 2026 – Pkg Stats / Ryan Hefner

akini

v1.0.3

Published

A HTML/CSS/Vanilla JS build system

Readme

AKINI

An alternative lightweight Front-End buildsystem

What is AkINI?

AKINI is a lightweight HTML/CSS/Vanilla JS build system for those who want a more reliable way to structure their web projects and skip the whole process of using complicated modules and frameworks. AKINI is built to keep the project in fit and include the tools needed when developing.

Features

AKINI comes with a small API and a opinionated structure that has to be followed. There are two classes that are part of the public API:

  • Compiler
  • Generator

Structure

To work with this library, a specific folder structure must be followed. Below is a general structure with example names marked with **.

.
├── components/
│   ├── *header*
│   └── ...
├── pages/
│   ├── ... 
│   └── *home*/
│       ├── components /
│       │   ├── *banner*/
│       │   │   ├── static.js 
│       │   │   ├── dynamic.js
│       │   │   └── styles.css
│       │   └── ...
│       └── page.js
├── public
└── server/
    └── server.js

Things to be aware of

Pages are statically generated and compiled through the Generator & Compiler classes. Every page share one namespace so name clashes between global variables and classes/ids can happen if not careful.

AKINI does not support import/export f.e like Webpack. It's possible though to pass external scripts in the build through the compiler (see file examples to see how it's done).

Tutorial

The easiest way to see how the system works are through practical examples. Let's start with the pages folder.

├── pages/
│   ├── ... 
│   └── home/
│       ├── components /
│       │   ├── banner/
│       │   │   ├── static.js 
│       │   │   ├── dynamic.js
│       │   │   └── styles.css
│       │   └── ...
│       └── page.js

page.js

/** Importing Compiler class and all the components for the page (static.js file) **/
const { Compiler } = require('akini');
const header = require('../../components/header/static');
const banner = require('./components/banner/static');
const playlists = require('./components/playlists/static');
const getHeard = require('./components/getHeard/static');
const artists = require('./components/artists/static');
const footer = require('../../components/footer/static');

/** components constant array, decides the order of the components rendered **/
const components = [header, banner, playlists, getHeard, artists, footer];

/** 
 * importComponents is an array of objects which are
 * automatically imported through the root components folder
 * **/
const importComponents = [
  { component: 'global' },
  { component: 'header' },
  { component: 'footer' },
]

/** Custom external scripts array, needs to be included for every page**/
const customScripts = [{ src: 'http://kit.fontawesome.com/123456.js', mode: 'defer' }]

/** Compiler class definition with a number of arguments passed to the HTML header **/
const compiler = new Compiler(components, 'home', customScripts, { lang: 'en', importComponents, favicon: { type: '', path: '' }, title: 'Hit Music Grp' });

/** This method does the compilation of the page and all its components, 
 * completely independent of other pages. 
 * **/
compiler.compile();

Component example

static.js

const { Generator } = require('akini');
const generator = new Generator();

/** AKINI uses it's own language that compiles down to HTML 
 * This is done solely through the generator method createTree. 
 * More specific information regarding this under API.
 */
const banner = generator.createTree(`
  div className: 'banner'
    div className: 'banner__items'
      div className: 'title_large' id: 'banner-title'
      a className: 'button_highlight' href: '/submit' id: 'banner_button' innerText: 'We want to hear you!'
end`)

/** Just require it in page.js **/
module.exports = banner;

dynamic.js

In dynamic.js just write your good ole vanilla JS, or use external scripts if you have imported them. Note that you are using the same namespace across the whole page. This compiles down to one minified JS file per page.

const title = document.getElementById('banner-title');
let start = 0;
let slicedAmount = 0;
let counter = 0;
let titleSelection = 0;
let titleString = ["A New Journey", "Next Hit Song?", "Let Us Listen!"];
let spawnedElements = [];

function animateTextController() {
  spawnedElements.forEach(element => element.parentNode.removeChild(element));
  spawnedElements = [];
  slicedAmount = 0;
  counter = 0;
  start = 0;
  titleSelection++;
  if (titleSelection === titleString.length)
    titleSelection = 0;
  window.requestAnimationFrame(animateText);
}

function animateText(timestamp) {
  if (!start)
    start = timestamp;
  if (timestamp - start > 1000 || slicedAmount === titleString[titleSelection].length) {
    return 0;
  }

  if (counter === 4) {
    counter = 0;
    const letter = document.createElement('span');
    letter.classList.add('banner-title__letter');
    letter.innerText = titleString[titleSelection][slicedAmount];
    title.appendChild(letter);

    spawnedElements.push(letter);
    slicedAmount++;
  }

  counter++;
  window.requestAnimationFrame(animateText)
}

function fullyLoaded() {
  window.requestAnimationFrame(animateText);
  setInterval(animateTextController, [5500])
  window.removeEventListener('load', fullyLoaded);
}

const loadedEvent = window.addEventListener('load', fullyLoaded);

styles.css

.banner {
	display: flex;
	align-items: center;
	justify-content: center;
  height: 100vh;
  width: 100vw;
  background-image: url('../images/banner.webp');
	background-size: cover;
}

.banner__items {
	display: flex;
	align-items: center;
	flex-direction: column;
}

.title_large {
	color: white;
	font-size: 70px;
  font-family: 'Copperplate', sans-serif;
  font-weight: 700;
	margin-top: 10px;
	width: 555px;
	height: 100px;
}

.banner-title__letter {
	animation: blur 5s forwards;
}

@keyframes blur {
	0% { filter: blur(12px); opacity: 0; }
	10% { filter: blur(0); opacity: 1; }
	80% { filter: blur(0); opacity: 1; }
	100% { filter: blur(12px); opacity: 0; }
}

.button_highlight {
	display: flex;
	align-items: center;
	justify-content: center;
	height: 50px;
	width: 200px;
	border-radius: 0;
	border: none;
	font-family: Copperplate;
	font-size: 16px;
	opacity: 0.8;
	color: white;
	transition-duration: 0.5s;
	animation: breathing 4s infinite;
	margin: 10px;
	text-decoration: none;
}

.button_highlight:hover {
	opacity: 1;
	transform: scale(1.02);
	cursor: pointer;
}

@keyframes breathing {
	0% {
		background-color: var(--second-color-darker);

	}
	50% {
		background-color: var(--second-color);
	}

	100% {
		background-color: var(--second-color-darker);
	}
}

This simple structure is necessary to follow for all components with the correct names, which makes the project easy to keep clean. To continue with practical examples, I think it would be wise to show an example of the general components in the root folder:

.
└── components/
    └── *header*/
        ├── static.js
        ├── dynamic.js
        └── styles.css

As can be seen in the page.js example, you can import them solely by passing in an object with a component key and a value of the name. They follow the exact same structure as page specific components.

Server folder

At the current version, the server folder requires a server.js file and it's wise to use the library's own inbuilt system for re-compiling when saving, but this is of course possible to set up with external libraries as well.

server.js

const express = require('express');
const { Compiler } = require('akini');

const PORT = 4444;

const app = express();
app.use(express.static('build'));
app.use(express.static('public'));
app.use(express.json());

(async () => {
  const compiler = new Compiler([], '', { isBuilding: true }); //Pass in, "isBuilding" when using for watch
  await compiler.watch();
})()


app.get('/', (req, res) => {
  res.redirect('/home');
})

app.get('*', (req, res) => {
  res.redirect('/404');
})

app.listen(PORT, () => console.log(`👹 -> ${PORT}`))

API

StaticGenerator

Kind: global class

new StaticGenerator()

The generator is a powerful way to generate HTML without writing actual HTML! No more angle brackets!

Example

const { Generator } = require('akini')
const generator = new Generator();
const header = generator.createTree(`
  header className: 'header'
    h1 className: 'header__title' innerText: 'Hello World!'
    h2 className: 'header__subtitle' innerText: 'This is my site.'
end`)