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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@hazae41/glace

v1.0.30

Published

Build a webapp with security in mind

Readme

Glace

Build a webapp with security in mind

npm install -D @hazae41/glace
deno install -gfn glace -RW jsr:@hazae41/glace/bin

📦 NPM📦 JSR

Features

  • Built on web standards and fundamentals
  • Works with any client-side and/or static-side library (e.g. React, jQuery)
  • Focused on security and simplicity without degrading performances
  • Supply-chain hardened with the bare minimum dependencies
  • Built for Deno but backward compatible with Node/Bun
  • Deploy it anywhere HTML/CSS/JS files can be stored
  • Builds are cross-platform cross-runtime reproducible

Starters

Usage

Bash

glace ./www --out=./dst --dev --watch=./www,./src

Code

await new Glace("./www", "./dst", "production").build()

Features

.bundleignore

You can put a .bundleignore file at the root of your input directory to ignore some files

./www/.bundleignore

manifest.json
/assets/*

Those files will be copied as-is without any bundling

JS/TS(X), CSS, JSON files

Those files will be bundled for the client unless explicitly ignored (see previous section)

HTML files

Scripts

Any <script> will be bundled and then executed with the HTML file set for document and location

<script type="module">
  document.body.innerHTML = `<div>Built at ${Date.now()}</div>`
</script>

You can branch on browser or static execution using process.env.PLATFORM

<script type="module">
  if (process.env.PLATFORM === "browser") {
    console.log("Hello from browser")
  } else {
    console.log("Hello from bundler")
  }
</script>

Subresource Integrity

All scripts, whether inline or external, will have their integrity attribute automatically computed.

<script type="module" src="./index.tsx"></script>
<script type="module" src="./index.js" integrity="sha256-xP+cym0GRdm2J0F0v39EBGjOtHbuY8qEHoeQrqrhgcs="></script>

External scripts will also be included in a modulepreload link

<link rel="modulepreload" href="./index.js" integrity="sha256-xP+cym0GRdm2J0F0v39EBGjOtHbuY8qEHoeQrqrhgcs=" />

And an importmap will be generated with the integrity of external scripts

<script type="importmap">{"integrity":{"./index.js":"sha256-xP+cym0GRdm2J0F0v39EBGjOtHbuY8qEHoeQrqrhgcs="}}</script>

Service-worker

If you set background.service_worker to an output file path in manifest.json, it will be injected by replacing FILES by a [string, string][] mapping of all files and their integrity

{
  "name": "Example",
  "short_name": "Example",
  "description": "An example webapp",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#ffffff",
  "icons": [
    {
      "src": "/favicon.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "background": {
    "service_worker": "/service.worker.js"
  }
}

You can then use FILES to cache all your webapp files

import { immutable } from "@hazae41/immutable"

declare const FILES: [string, string][]

const cache = new immutable.cache.Cache(new Map(FILES))

self.addEventListener("install", (event) => {
  /**
   * Precache new version and auto-activate
   */
  event.waitUntil(cache.precache().then(() => self.skipWaiting()))
})

self.addEventListener("activate", (event) => {
  /**
   * Take control of all clients and uncache previous versions
   */
  event.waitUntil(self.clients.claim().then(() => cache.uncache()))
})

self.addEventListener("fetch", (event) => {
  const response = cache.handle(event.request)

  if (response == null)
    return

  /**
   * Respond with cache
   */
  event.respondWith(response)
})

Generated paths

You can generate paths using brackets as parameters

And parameters will be available in location as search params

./www/[lang].html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Example</title>
  <script type="module">
    if (process.env.PLATFORM !== "browser") {
      document.documentElement.lang = new URLSearchParams(location.search).get("lang")
    }
  </script>
</head>

</html>

./www/manifest.json

{
  "short_name": "Example",
  "name": "Example",
  "paths": [
    {
      "lang": "en"
    },
    {
      "lang": "fr"
    },
    {
      "lang": "es"
    },
    {
      "lang": "de"
    }
  ]
}

Will output

  • ./out/en.html

  • ./out/fr.html

  • ./out/es.html

  • ./out/de.html

And you can use multiple parameters

./www/posts/[post]/[name].html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Example</title>
  <script type="module">
    if (process.env.PLATFORM !== "browser") {
      const params = new URLSearchParams(location.search)

      document.title = params.get("name")

      document.body.innerHTML = await fetch(`/api/posts/${params.get("post")}`).then(r => r.text())
    }
  </script>
</head>

</html>

./www/manifest.json

{
  "short_name": "Example",
  "name": "Example",
  "paths": [
    {
      "post": 1,
      "name": "how-to-start-coding"
    },
    {
      "post": 2,
      "name": "how-to-choose-a-license"
    },
    {
      "post": 3,
      "name": "how-to-deploy-a-website"
    }
  ]
}

Will output

  • ./out/posts/1/how-to-start-coding.html

  • ./out/posts/2/how-to-choose-a-license.html

  • ./out/posts/3/how-to-deploy-a-website.html

You can write your own script that will fetch your database and fill manifest.json

./scripts/generate.ts

const manifest = await readFile("./www/manifest.json", "utf-8").then(JSON.parse)

const posts = await fetch("https://example.com/api/posts").then(r => r.json())

for (const { id, title } of posts)
  manifest.paths.push[{ post: id, name: title.replaceAll(" ", "-").toLowerCase() }]

await writeFile("./www/manifest.json", JSON.stringify(manifest, null, 2))
deno -RWN ./scripts/generate.ts

Examples

A simple HTML file with prerendering

./www/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Example</title>
  <script type="module">
    if (process.env.PLATFORM !== "browser") 
      document.body.innerHTML = `<div>Copyright ${new Date().getUTCFullYear()}</div>`
    }
  </script>
</head>

</html>

Will output

<!DOCTYPE html>
<html lang="en">
  
<head>
  <title>Example</title>
</head>

<body>
  <div>Copyright 2025</div>
</body>

</html>

A simple HTML file with prerendering and hydration

./www/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Example</title>
  <script type="module">
    document.body.innerHTML = `<div>Copyright ${new Date().getUTCFullYear()}</div>`
  </script>
</head>

</html>

Will output

<!DOCTYPE html>
<html lang="en">
  
<head>
  <title>Example</title>
  <script type="module">
    document.body.innerHTML = `<div>Copyright ${new Date().getUTCFullYear()}</div>`
  </script>
</head>

<body>
  <div>Copyright 2025</div>
</body>

</html>

A simple React and Tailwind app with prerendering and hydration using Rewind

npm i react react-dom @types/react @types/react-dom @hazae41/rewind

./www/index.html

<!DOCTYPE html>
<html lang="en">

<head>
  <title>Example</title>
  <script type="module" src="./index.tsx"></script>
  <link rel="stylesheet" data-rewind href="./index.css" />
</head>

</html>

./www/index.css

@import "tailwindcss/index";

.big {
  @apply text-3xl;
}

./www/index.tsx

import { Rewind } from "@hazae41/rewind";
import React, { type ReactNode, useEffect } from "react";
import { hydrateRoot } from "react-dom/client";

React;

export function App() {
  useEffect(() => {
    console.log("Hello world");
  }, [])

  return <div className="text-red-500 big">
    Hello world
  </div>
}

if (process.env.PLATFORM === "browser") {
  await new Rewind(document).hydrateOrThrow().then(() => hydrateRoot(document.body, <App />))
} else {
  const prerender = async (node: ReactNode) => {
    const ReactDOM = await import("react-dom/static")

    using stack = new DisposableStack()

    const stream = await ReactDOM.default.prerender(node)
    const reader = stream.prelude.getReader()

    stack.defer(() => reader.releaseLock())

    let html = ""

    for (let result = await reader.read(); !result.done; result = await reader.read())
      html += new TextDecoder().decode(result.value)

    return html
  }

  document.body.innerHTML = await prerender(<App />)

  await new Rewind(document).prerenderOrThrow()
}

For more complete examples, see #starters