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

@tiledev/tile-fonts

v0.1.2

Published

Runtime font loading for Tile apps. Registers font files published through the Live Layer so Brand text styles render in the chosen typeface. Ships no font data.

Readme

@tiledev/tile-fonts

Runtime font loading for Tile apps. Registers the font files published through the Live Layer so Brand text styles render in the typeface a merchant chose.

This package ships no font data. The editor holds the font catalogue (~1,998 families) and publishes only the handful of URLs an app actually needs. Bundling that table here would put ~1.09 MB of JS into every merchant app, and a runtime lookup by a dynamic key cannot be tree-shaken away.

Install

npm install @tiledev/tile-fonts

Peers: expo-font >=14, react >=18, react-native >=0.73. No runtime deps.

Use

In the app's ThemeProvider, alongside the existing brand overlay:

import { useBrandFonts } from '@tiledev/tile-fonts';

const brand = useLiveLayer<BrandOverride>(BRAND_CATS, ['brand']);
const { resolve } = useBrandFonts(brand);

// per typography slot
const key = resolve(slot.fontFamily, slot.fontWeight);
const style = key ? { ...slot, fontFamily: key, fontWeight: undefined } : slot;

resolve returns null until the file is registered, so text renders in the platform face and swaps when it lands. That is the intended fallback, not a failure state — a font must never delay first paint.

Dropping fontWeight and fontStyle is not optional. The registered file already is that weight, so leaving the weight on causes synthetic double-bolding on iOS and web.

fontStyle matters more, and only on Android. The catalogue publishes upright faces only, and Android resolves family+style as a unit — an unsatisfiable pair falls back to the system family instead of synthesising a slant. An inherited fontStyle: "italic" silently discards the font: it downloads, caches, registers, resolve() returns its key, and the text still renders in Roboto. This is the hardest failure in the whole feature to diagnose, because every log and cache check says the font is fine.

Payload shape

The editor publishes font files as a sibling of typography, because the app spreads each typography patch straight into a React Native style object where a URL would land as an unknown key on a Text:

{
  "brand": {
    "typography": { "heading": { "fontFamily": "Oswald", "fontWeight": 400 } },
    "fonts":      { "Oswald":  { "400": "https://fonts.gstatic.com/s/oswald/…ttf" } }
  }
}

Weight snapping is driven by what the payload contains, not by a table here: the editor may have snapped 300 → 400 because a family ships no Light, and the app has to land on the same key without knowing why.

The key contract

fontKey(family, weight) → PlayfairDisplay-700.

This must match catalogFontKey in the editor (theme-panel/font-catalog.ts). Fonts are registered under this key and styled by it; a mismatch means the file loads and nothing uses it.

Spaces are stripped because a space is unsafe in a web font-family token, and the weight is part of the key because one registered name can only point at one file while heading and body may share a family at different weights.

Notes

  • Files must be TrueType or OpenType. Android cannot render WOFF2, which is what Google Fonts serves to a browser User-Agent. The editor's catalogue is TTF-only for this reason.
  • Caching is not implemented here, on purpose. expo-font hands the URL to expo-asset, whose native module keys a cache file by MD5 of the URL and reuses it unconditionally on both platforms. Fonts download once per device, not once per launch.
  • The loadAsync key becomes the fontFamily. Styling with the file's own family name works on Android and silently fails on iOS, where the internal PostScript name would have had to match.

Local development

Unpublished, the package resolves from the workspace sibling. Three things must line up for the preview to reach it:

| Where | What | | --- | --- | | tile-run-engine/metro.config.js | watchFolders + resolver.extraNodeModules → the sibling | | tile-run-engine/src/aliases/common.tsx | guarded require of the real implementation, not a stub | | tile-web-v2/…/session/utils/dependencies.ts | PREVIEW_PROVIDED_MODULES entry |

That last one is mandatory, and it stays mandatory now that the package is on the public registry. The reason changed, the requirement did not:

  • Before publishing, the bundler simply could not fetch a private Artifact Registry package. The preview sat on "Loading…" while the engine looped RESEND_CODE — indistinguishable from a slow cold build.
  • Now, the bundler can fetch it, and that is worse. It would build its own copy along with its own expo-font and react, giving the preview two of each — the duplicate-React failure described in the next section, arriving by a different route.

The preview must get this package from the run-engine's alias, never from the bundler.

A real device build resolves it from npm like any other dependency.

Peers must never resolve from this package

react, react-native and expo-font are peers, and Metro resolves a linked sibling's imports from the sibling's own node_modules first. If a copy of react lives here, the package's hooks run against a dispatcher belonging to a different React than the one rendering the app, and every render dies with:

Invalid hook call … Cannot read properties of null (reading 'useMemo')

Confirmed by inspecting the served bundle, which registered both node_modules/react/index.js and ../tile-fonts/node_modules/react/index.js.

A Metro blockList on this package's node_modules is configured in the run-engine as a guard, but do not rely on it — it did not take effect in practice. The reliable fix is that the copies simply are not here:

npm install          # peers land here as transitive deps of expo-font
npm run build:local  # build, then delete react / react-native / expo-font

dist requires only react and expo-font, so with those pruned Metro falls through to the host's copies. This is a local-development concern only: files excludes node_modules, so an installed copy of the package cannot reproduce it.

Restart the run-engine with --clear after changing any of this; Metro caches resolution and a plain restart will not pick it up.

Scripts

npm run build   # tsc → dist (CommonJS; the run-engine `require`s this)
npm run lint    # tsc --noEmit

CommonJS is deliberate: the run-engine's alias table uses require(), and an ESM dist without "type": "module" would throw Unexpected token 'export'.