sprintf-typescript
v1.0.2
Published
Modern TypeScript sprintf/vsprintf. Drop-in replacement for sprintf-js with template-literal-typed format strings.
Maintainers
Readme
sprintf-typescript
A modern, fully-typed TypeScript reimplementation of
sprintf-js. It is a drop-in
replacement for sprintf-js — same functions, same format specifiers, same
semantics — but rewritten from scratch with:
- Template-literal-typed format strings. The compiler parses your format string and infers argument types, and where possible the return type too.
- ESM-only. No CommonJS, no UMD, no AMD, no browser globals.
- Zero runtime dependencies.
- Modern tooling. Vite, Vitest, oxlint, TypeScript ≥ 5.6, Node.js ≥ 20.
Install
npm install sprintf-typescriptUsage
import { sprintf, vsprintf } from 'sprintf-typescript';
sprintf('Hello, %s!', 'world');
// ⇒ "Hello, world!" (inferred return type: "Hello, world!")
sprintf('%d items at %.2f each', 3, 9.99);
// ⇒ "3 items at 9.99 each"
sprintf('%(user.name)s has %(user.posts.length)d posts', {
user: { name: 'Dolly', posts: [/* … */] },
});
vsprintf('%2$s %3$s a %1$s', ['cracker', 'Polly', 'wants']);
// ⇒ "Polly wants a cracker"If a %d is given a non-number, or a format string is malformed, you get a
TypeError / SyntaxError at runtime — and likely a type error at compile
time too.
Format grammar
%[index$|(name)][flags][width][.precision]specifierSpecifiers
| Specifier | Output |
| --------: | ------------------------------------------ |
| % | A literal % |
| b | Binary integer |
| c | Character (from char code) |
| d i | Signed decimal integer |
| e | Scientific notation |
| f | Fixed-point float |
| g | Float (general, uses toPrecision) |
| j | JSON-serialised value (width → indent) |
| o | Unsigned octal |
| s | String (anything coerced via String(...)) |
| t | Boolean ("true" / "false") |
| T | Type name (e.g. "number", "array") |
| u | Unsigned decimal |
| v | Primitive via .valueOf() |
| x | Lowercase hexadecimal |
| X | Uppercase hexadecimal |
Flags
| Flag | Meaning |
| ---------- | -------------------------------------------------- |
| + | Always emit a sign for numeric specifiers |
| - | Left-align within the width |
| 0 | Pad numeric output with zeros |
| '<char>' | Pad with <char> (any single character after ') |
Named arguments
sprintf('%(path.to[0].key)s', { path: { to: [{ key: 'hi' }] } });Paths support .key and [index] access, arbitrarily nested.
Function-valued arguments
If an argument is a function, it is called with no arguments and its return
value is used — except for %T and %v, which expect the function value
itself.
TypeScript story
The generic overloads of sprintf / vsprintf parse the format string at the
type level, so:
sprintf('%d', 'oops');
// ~~~~~ Argument of type 'string' is not assignable to parameter of type 'number'.
sprintf('%s %s', 'only one');
// ^^^^^^ Expected 3 arguments, but got 2.
const s = sprintf('Hi, %s!', 'world');
// ^? const s: "Hi, world!"For format strings with width/precision/padding, the compile-time result
gracefully falls back to string for that slot while keeping the literal
parts literal.
Credits
sprintf itself is a POSIX/C standard library function, and most of the
format grammar here (specifiers, flags, width, precision, %n$ positional
arguments) comes from C. The %(name)s named-argument syntax follows
Python's convention.
This package is a from-scratch reimplementation that aims to be a drop-in
replacement for sprintf-js by
Alexandru Mărășteanu — matching its JS-specific extensions (%j, %T,
%v, %t) and coercion semantics.
License
MIT — see LICENSE.
