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

jiplak

v1.0.5

Published

Pixel-perfect skeleton loading screens, auto-captured from your real React UI.

Readme

jiplak

Pixel-perfect skeleton loading screens, auto-captured from your real React UI.

Wrap a component. Run one command. Get a skeleton that matches it exactly.

License: MIT React TypeScript Sponsored by Lokio

Buy Me a Coffee

English · Bahasa Indonesia


Why jiplak?

Hand-drawn skeletons drift out of sync with the real UI the moment a layout changes. jiplak ("to trace" in Indonesian) traces your rendered components in a real browser and stores their exact shape. You never draw a skeleton again.

  • Pixel-exact. Every line of text becomes its own bar, sized to the glyphs. Images, buttons, and cards keep their real size and corner radius.
  • Zero route list. The CLI crawls your app through its own links.
  • Responsive. Captured at several screen widths; the right one is picked at runtime.
  • One YAML file. jiplak.yaml, one line to start.
  • Dark mode. Follows the .dark class (Tailwind convention).
  • Tiny runtime. One React component; the capture tooling never ships to the browser.

Contents

Quick start

1. Install and set up

npm install jiplak
npx jiplak init

init creates jiplak.yaml and an empty src/bones/registry.ts.

2. Import the registry once

In a client component that always loads, such as your providers:

'use client'
import '@/bones/registry' // or a relative path to src/bones/registry

3. Wrap components that wait for data

import { Skeleton } from 'jiplak/react'

function Profile() {
  const { data, isLoading } = useProfile()

  return (
    <Skeleton name="profile-card" loading={isLoading}>
      <ProfileCard user={data} />
    </Skeleton>
  )
}

4. Start your app, then capture

npm run dev

In a second terminal:

npx jiplak build

Done. While loading is true, the skeleton shows automatically.

FAQ

jiplak copies exactly what it sees at build time. If your component is empty or shows placeholder text without data, pass a fixture that looks like real content: the same title length, the same number of summary lines, an image if real rows have one. Then run npx jiplak build again.

No. Wrap only the parts that wait for data, such as cards, lists, or tables. Static parts like headers and menus need nothing. One screen can hold several <Skeleton> components with different names.

Yes. jiplak build crawls your app through its links, finds every <Skeleton>, and records its shape. Your only job is to wrap components.

After a wrapped component changes its layout, or after you add a new <Skeleton>. Commit the src/bones/ folder so your team does not need to rebuild.

Your app works as usual. Components render normally, just without a skeleton.

Pass sample content through fixture. jiplak captures the fixture instead of the real children:

<Skeleton name="profile-card" loading={isLoading} fixture={<ProfileCard user={sampleUser} />}>
  <ProfileCard user={data} />
</Skeleton>

Yes. Write glob paths in routes; the crawler then follows only links that match one of them:

routes:
  - /                  # plain path: a start point
  - /dashboard/**      # ** = every page below /dashboard
  - /products/*        # * = one dynamic segment; one sample page is enough
  • A glob also starts at its fixed prefix (/products/* starts at /products), so you never write a real slug.
  • A * glob is visited once: every /products/:id page has the same shape, so jiplak captures the first one it finds and skips the rest.
  • Without globs, every link is followed, as before.

The crawler finds only pages that another page links to. Add unlinked pages as extra start points:

routes:
  - /
  - /settings/profile

To visit only the listed routes, turn crawling off:

crawl: false

Logout links and files (pdf, images, csv) are always skipped. Skip other paths with ignore:

ignore:
  - /admin

The crawler stops at 50 pages so dynamic routes (/post/1, /post/2, …) cannot run forever. Raise the limit if needed:

maxPages: 200

Log in once, then build as usual:

npx jiplak login   # a browser window opens: sign in like a normal user, then press Enter
npx jiplak build   # reuses that session

The session is saved to .jiplak/session.json; the .jiplak folder ignores itself, so it never lands in git. build writes refreshed cookies back to it, so it keeps working until the app ends the session. When a page redirects to your sign-in page, build says so: run jiplak login again.

No browser window (CI)? Pass cookies through the environment instead, in the Cookie header format:

JIPLAK_COOKIES="session=your-session-cookie; other=value" npx jiplak build

Use the Chrome you already have:

channel: chrome

Or download Chromium once:

npx playwright install chromium

Configuration

Every key is optional. Values below are the defaults. Unknown keys fail with a clear error, so typos never pass silently.

# capture
url: http://localhost:3000       # your running app
routes: [/]                      # start points; globs (* and **) limit the crawl
crawl: true                      # false: visit only routes
ignore: []                       # path prefixes never visited
maxPages: 50                     # crawl limit
breakpoints: [375, 768, 1280]    # screen widths to capture
out: ./src/bones                 # output folder
wait: 800                        # ms to wait after page load
channel: chrome                  # optional: use installed Chrome
cookies: []                      # optional: name=value (prefer the JIPLAK_COOKIES env var)

# look (props on <Skeleton> override these)
animate: pulse                   # pulse | shimmer | solid
color: "#ececec"                 # light mode
darkColor: "#262626"             # dark mode (.dark class)
shimmerColor: "#f7f7f7"
darkShimmerColor: "#323232"
speed: 1.6s

API

<Skeleton>

| Prop | Required | Description | | --- | --- | --- | | name | yes | Unique name: letters, digits, -, _ | | loading | yes | true while data is loading | | fixture | no | Sample content captured at build time | | animate, color, darkColor, shimmerColor, darkShimmerColor, speed | no | Override jiplak.yaml for this skeleton | | options | no | { exclude: ['.badge'] } skips elements; { leafTags: ['section'] } turns a tag into one bone | | bones | no | Bones passed directly instead of the registry | | className | no | Class for the wrapper element |

Functions

| Function | Description | | --- | --- | | registerBones(map) | Register bones by name. The generated registry calls it. | | configureJiplak(theme) | Set global look defaults. The generated registry calls it with your YAML values. |

CLI

jiplak init                                  # create jiplak.yaml and an empty registry
jiplak login [url] [--config jiplak.yaml]    # log in once; build reuses the session
jiplak build [url] [--config jiplak.yaml]    # capture every <Skeleton>

How it works

  1. jiplak build opens your app in a headless browser, starting at routes, and follows links on the same origin.
  2. After the page and its web fonts load, every <Skeleton> is captured at each breakpoint ("bones"):
    • each rendered line of text becomes one bar, measured from the glyphs; clamped or clipped lines are left out;
    • images, icons, buttons, and inputs become blocks with their visible corner radius (including a radius that comes from a clipping parent);
    • cards and panels with a background or border become faint surfaces drawn behind the blocks.
  3. Bones are saved to src/bones/<name>.bones.json, plus a registry.ts that loads them.
  4. At runtime, while loading is true, the bones are drawn over the hidden component, using the breakpoint that fits the container width.

Development

bun install
bun test
bun run typecheck
bun run build

Issues and pull requests are welcome on GitHub.

Sponsor

jiplak is proudly sponsored by Lokio.

Like jiplak? Support its development:

Buy Me a Coffee

Author

Wahyu Agus Arifin · @ItPohgero

License

MIT © 2026 Wahyu Agus Arifin