mrkeyoor.com_
Wed 23 Sept 00:34 UTC
npmWeb Frontendupdated 22 Sept 2026

fontace review

fontace 0.4.1 reads a TTF, WOFF, or WOFF2 file from a Node Buffer and returns the fields needed to compose CSS `@font-face`: embedded family, style, weight or variable weight range, format, Unicode coverage, and a variable-font flag. It is a synchronous metadata extractor, so it does no fetching, rendering, shaping, subsetting, or file rewriting. The only change in 0.4.1 is an update of its `fontkitten` parser dependency to 1.0.2. This is mainly a build-tool helper for web font pipelines, not a UI runtime library.

Verdict

fontace 0.4.1 installed in 1.1 seconds and used 1 MB in our sandbox, but its full browser import measured 94.4 KB gzipped, so it fits Node-based font build steps better than client bundles. Install it for `@font-face` metadata only; choose a full parser when the task reaches glyphs, shaping, collections, or subsetting.

We installed it

Lab card: what happened when we installed fontaceScreenshot of fontace documentation
Install✓ · 1.1s3 packages on disk · 1 MB
ImportESM import works · require() works · ESM package with exports map
Browser94.4 KBgzipped (242.2 KB minified), bundled with esbuild
Typesno TypeScript types found
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does fontace install cleanly?

Yes. In a fresh container with an empty cache, npm install fontace finished in 1 seconds, leaving 3 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does fontace add to a browser bundle?

94.4 KB gzipped (242.2 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does fontace work with both ESM and CommonJS?

Yes. Both import 'fontace' and require('fontace') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does fontace include TypeScript types?

No type declarations were found in our install, so TypeScript users need their own declarations.

fontace or fontkit: which should you use?

fontkit: Use it when glyph access, shaping, collections, or subsetting is part of the job. fontace 0.4.1 installed in 1.1 seconds and used 1 MB in our sandbox, but its full browser import measured 94.4 KB gzipped, so it fits Node-based font build steps better than client bundles.

When should you not use fontace?

You need glyph paths, shaping, kerning, subsetting, or font generation; the public result contains metadata only

API stability4/5Version 0.4.1 exports one named function that accepts a Buffer and returns seven documented metadata values, leaving little surface for ambiguous behavior. The current patch changes only the `fontkitten` dependency to 1.0.2. Both module loaders worked in our test, and the package has an exports map. It is still below 1.0, ships no declarations, and offers no compatibility contract for richer data, so consumers should pin the minor line.
Docs4/5The README defines every returned value and shows local-file input, remote fetching, variable weights, Unicode ranges, and assembly of a full `@font-face` rule. It clearly says the caller passes a Buffer and acknowledges `fontkitten` as the parser. Missing details include the thrown collection case, malformed-file handling, synchronous CPU cost, supported edge cases in OpenType tables, and the absence of bundled declarations. The main path is clear, while operational boundaries require source reading.
Maintenance4/5npm published 0.4.1 on February 1, 2026, and GitHub records a repository push on August 21, 2026. The repository is not archived and currently shows 1 open issue or pull request. Release 0.4.1 is a dependency-only patch, which is appropriate for a narrow wrapper. The deduction reflects its pre-1.0 status, 8-star contributor footprint, and reliance on one parser rather than evidence that the project has been abandoned.
Ecosystem3/5The npm endpoint counted 4,212,704 downloads for the week ending August 24, 2026, while GitHub reports only 8 stars. It uses familiar Node Buffers and outputs standard CSS descriptor values without peers or native compilation. That makes it easy for tools to consume, but there is no plugin layer, type package, or broad user community around the wrapper itself. Most advanced capability and support live in larger font libraries such as fontkit and opentype.js.

Use it if

  • A build step must derive CSS descriptors from the font's tables instead of trusting its filename
  • You need both a ready-to-insert `unicode-range` string and an array of individual ranges
  • Fixed weights and a variable font's `wght` range must be represented correctly in generated CSS
  • Your Node process already holds each TTF, WOFF, or WOFF2 file in a Buffer
Skip it if

Setup reality

Our fontace 0.4.1 install completed in 1.1 seconds in a clean Node 22 container. It left 3 packages occupying 1 MB, and npm audit reported 0 known vulnerabilities. The package measured 28 KB unpacked with 1 direct dependency and 0 peers. Both require() and ESM import loaded it, despite the package declaring ESM and exposing an exports map. No TypeScript declaration was present.

There are no credentials or config files. fontace() expects bytes, not a path or URL. fs.readFile gives you a Buffer directly; fetch gives you an ArrayBuffer, which the README converts through Buffer.from. The embedded family can differ from the filename. Your code still owns URL selection, quoting, font-display, CSS output, and any cache key.

Parsing is synchronous and has no streaming API or cache. A directory build should bound parallel file reads and avoid parsing identical content repeatedly. TTF, WOFF, and WOFF2 map to CSS format values; font collections and corrupt input can throw, so an upload pipeline needs a per-file try/catch rather than letting one bad file stop the batch.

Our esbuild browser test succeeded but produced 242.2 KB minified and 94.4 KB gzipped for a full-package import. That is far larger than the 28 KB package itself because the parser is pulled into the bundle. The README still speaks in Node Buffer terms. Keep font inspection in a build step or server unless a browser-only workflow clearly earns that transfer cost. Version 0.4.1 is pre-1.0, so pin the minor range in a production build chain.

Patterns

Inspect bytes read from disk local-font

import { readFile } from 'node:fs/promises';
import { fontace } from 'fontace';

const bytes = await readFile('./public/fonts/Inter-Regular.woff2');
const metadata = fontace(bytes);

`readFile` returns the Buffer expected by 0.4.1. Passing the pathname itself does not trigger file I/O.

Convert a fetched font before parsing remote-font

import { fontace } from 'fontace';

const response = await fetch('https://cdn.example.com/Inter.woff2');
if (!response.ok) throw new Error(`font download failed: ${response.status}`);
const bytes = Buffer.from(await response.arrayBuffer());
const metadata = fontace(bytes);

Check the HTTP response and convert its ArrayBuffer to a Node Buffer. fontace performs neither step.

Build CSS from the returned descriptors font-face-css

function fontFace(url, bytes) {
  const m = fontace(bytes);
  const tech = m.isVariable ? ' tech(variations)' : '';
  return `@font-face {
  font-family: ${JSON.stringify(m.family)};
  font-style: ${m.style};
  font-weight: ${m.weight};
  font-display: swap;
  unicode-range: ${m.unicodeRange};
  src: url(${JSON.stringify(url)}) format('${m.format}')${tech};
}`;
}

The caller supplies the URL and `font-display` policy. `JSON.stringify` quotes ordinary family names and URLs for this generated rule.

Read a fixed weight or variable range variable-weight

const { isVariable, weight } = fontace(fontBytes);

console.log(isVariable
  ? `variable axes present, weight ${weight}`
  : `fixed weight ${weight}`);

`isVariable` covers any variation axis. `weight` becomes a range only when the font includes a `wght` axis.

Process each CSS Unicode range unicode-ranges

const { unicodeRangeArray } = fontace(fontBytes);

for (const range of unicodeRangeArray) {
  console.log(range);
}

The array carries the same information as `unicodeRange`, without splitting the comma-separated CSS value yourself.

Isolate a bad font file parse-errors

function inspect(bytes) {
  try {
    return { ok: true, metadata: fontace(bytes) };
  } catch (error) {
    return { ok: false, error: String(error) };
  }
}

Collections are explicitly refused and malformed files can fail inside the parser. Catch per file in a batch.

Scan supported files in a folder directory-scan

import { readdir, readFile } from 'node:fs/promises';
import { extname, join } from 'node:path';

const allowed = new Set(['.ttf', '.woff', '.woff2']);
const rows = [];
for (const name of await readdir('./public/fonts')) {
  if (!allowed.has(extname(name).toLowerCase())) continue;
  rows.push({ name, ...fontace(await readFile(join('./public/fonts', name))) });
}

Filter to the 3 documented formats and decide whether one parser error should stop or merely annotate the scan.

Group variants by the embedded family family-groups

const families = new Map();
for (const file of fontFiles) {
  const metadata = fontace(file.bytes);
  const variants = families.get(metadata.family) ?? [];
  variants.push({ file: file.name, ...metadata });
  families.set(metadata.family, variants);
}

The internal family is safer than a filename guess because filenames commonly append weight, style, or subset labels.

Enforce an upload format allowlist format-policy

const metadata = fontace(uploadedBytes);
const allowed = new Set(['truetype', 'woff', 'woff2']);
if (!allowed.has(metadata.format)) {
  throw new Error(`unsupported font format: ${metadata.format}`);
}

Version 0.4.1 already returns one of these 3 values. The explicit check documents the surrounding upload policy.

Store a font metadata manifest json-manifest

const manifest = Object.fromEntries(
  fontFiles.map(({ url, bytes }) => [url, fontace(bytes)]),
);

await writeFile(
  './dist/fonts.json',
  JSON.stringify(manifest, null, 2),
);

The result contains strings, a boolean, and a string array, so no Buffer data enters the JSON manifest.

Avoid parsing duplicate font bytes content-cache

import { createHash } from 'node:crypto';

const cache = new Map();
function inspectOnce(bytes) {
  const key = createHash('sha256').update(bytes).digest('hex');
  if (!cache.has(key)) cache.set(key, fontace(bytes));
  return cache.get(key);
}

The package has no cache and parses synchronously. A content hash handles the same file copied under several names.

Order variants by their minimum weight variant-sort

const variants = fontFiles
  .map(({ name, bytes }) => ({ name, ...fontace(bytes) }))
  .sort((a, b) =>
    Number.parseInt(a.weight, 10) - Number.parseInt(b.weight, 10) ||
    a.style.localeCompare(b.style)
  );

For `100 900`, `parseInt` sorts on 100. Keep the full range string when emitting the CSS declaration.

Alternatives

PackageRegistryPick it when
fontkitnpmUse it when glyph access, shaping, collections, or subsetting is part of the job.
opentype.jsnpmUse it for browser-oriented parsing, glyph paths, metrics, kerning, and font construction.
fontkittennpmUse the underlying parser when raw OpenType tables matter more than ready-made CSS descriptors.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.