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

recipe-scale-conv

v0.1.1

Published

Ingredient measurement conversion, formatting, and recipe scaling utilities.

Readme

recipe-scale-conv

Recipe scaling and measurement formatting for apps that need results cooks would actually use.

It handles fractional quantities, US/metric display, ingredient-specific cup-to-gram conversions, count ingredients like eggs, and configurable unit promotion/demotion.

Install

npm install recipe-scale-conv

This package supports ESM and CommonJS, supports Node.js 18 and newer, and has no runtime dependencies.

Quick Start

import {
  createRecipeConverter,
  formatIngredientMeasurement,
  parseIngredientLine,
  scaleIngredientComponentsForDisplay,
  scaleIngredientForDisplay,
  scaleIngredientsForDisplay,
  scaleRecipeYield
} from 'recipe-scale-conv';

CommonJS works too:

const { formatIngredientMeasurement } = require('recipe-scale-conv');
formatIngredientMeasurement({ name: 'All-purpose flour', quantity: '1', unit: 'cup' }, 'metric');
// { quantity: '125', unit: 'g' }

scaleIngredientForDisplay({ name: 'vanilla', quantity: '1', unit: 'tbsp' }, 0.5, 'us');
// { name: 'vanilla', quantity: '1 ½', unit: 'tsp' }

The library is dependency-free at runtime and treats kitchen conversions as approximate.

What It Does

  • Parses common ingredient lines into structured data.
  • Scales quantities up or down with numbers, decimals, fractions, and mixed fractions.
  • Promotes or demotes units to practical cooking amounts, such as 4 tbsp to ¼ cup.
  • Converts between US and metric display with configurable metric rules.
  • Uses ingredient-aware cup-to-gram conversions for common baking ingredients.
  • Handles count ingredients such as eggs, cloves, pieces, and slices.
  • Lets apps set conversion and formatting defaults once with createRecipeConverter.

Common Workflows

Parse Ingredient Lines

parseIngredientLine('1 1/2 cups all-purpose flour, sifted');
// {
//   original: '1 1/2 cups all-purpose flour, sifted',
//   quantity: '1 1/2',
//   unit: 'cup',
//   name: 'all-purpose flour',
//   note: 'sifted'
// }

The parser also handles adjacent unicode mixed fractions, count units, and common package-size prefixes:

parseIngredientLine('1½ cups all-purpose flour');
// {
//   original: '1½ cups all-purpose flour',
//   quantity: '1½',
//   unit: 'cup',
//   name: 'all-purpose flour',
//   note: undefined
// }

parseIngredientLine('3 cloves garlic');
// {
//   original: '3 cloves garlic',
//   quantity: '3',
//   unit: 'cloves',
//   name: 'garlic',
//   note: undefined
// }

parseIngredientLine('1 (14-ounce) can tomatoes, drained');
// {
//   original: '1 (14-ounce) can tomatoes, drained',
//   quantity: '1',
//   unit: 'can',
//   name: 'tomatoes',
//   note: '14-ounce; drained'
// }

Scale One Ingredient

scaleIngredientForDisplay({ name: 'flour', quantity: '1', unit: 'cup' }, 0.5, 'us');
// { name: 'flour', quantity: '½', unit: 'cup' }

scaleIngredientForDisplay({ name: 'flour', quantity: '2', unit: 'tbsp' }, 2, 'us');
// { name: 'flour', quantity: '¼', unit: 'cup' }

Scaling factors can be numbers or quantity-like strings:

scaleIngredientForDisplay({ name: 'flour', quantity: '2', unit: 'cup' }, '0.75', 'us');
// { name: 'flour', quantity: '1 ½', unit: 'cup' }

A scale factor of 1 still formats the ingredient for the requested target system:

scaleIngredientForDisplay({ name: 'All-purpose flour', quantity: '1', unit: 'cup' }, 1, 'metric');
// { name: 'All-purpose flour', quantity: '125', unit: 'g' }

Scale A Whole Ingredient List

const ingredients = [
  { name: 'All-purpose flour', quantity: '1', unit: 'cup' },
  { name: 'eggs', quantity: '3', unit: '' }
];

scaleIngredientsForDisplay(ingredients, 0.5, 'metric');
// [
//   { name: 'All-purpose flour', quantity: '63', unit: 'g' },
//   { name: 'eggs', quantity: '1 ½', unit: '' }
// ]

Scale Recipe Yield

scaleRecipeYield('8 servings', 0.5);
// '4 servings'

scaleRecipeYield('Makes 2 loaves', 1.5);
// 'Makes 3 loaves'

scaleRecipeYield('Serves 4-6', 2);
// 'Serves 8-12'

Yield labels round to whole numbers by default. Use yieldScaling: 'fractional', floor, or ceil when your app needs a different policy.

scaleRecipeYield('5 servings', 0.5);
// '3 servings'

scaleRecipeYield('5 servings', 0.5, { yieldScaling: 'fractional' });
// '2 ½ servings'

Convert For Metric Display

formatIngredientMeasurement({ name: 'All-purpose flour', quantity: '1', unit: 'cup' }, 'metric');
// { quantity: '125', unit: 'g' }

formatIngredientMeasurement({ name: 'Water', quantity: '1', unit: 'cup' }, 'metric', { metricMode: 'volume' });
// { quantity: '237', unit: 'ml' }

By default, metric display converts cups and mass units, while preserving small spoon measures such as tsp and tbsp.

formatIngredientMeasurement({ name: 'vanilla', quantity: '2', unit: 'tbsp' }, 'metric');
// { quantity: '2', unit: 'tbsp' }

Configuration

Set App Defaults Once

Use createRecipeConverter when your app has house rules. Per-call options still override these defaults.

const converter = createRecipeConverter({
  defaults: {
    countScaling: 'round',
    unitScaling: 'preserve',
    metricConversion: [{ from: ['tsp', 'tbsp'], convert: false }],
    fractions: 'ascii'
  },
  densities: [{ aliases: ['almond flour'], gramsPerCup: 96 }]
});

converter.scaleIngredientForDisplay({ name: 'eggs', quantity: '3', unit: '' }, 0.5, 'us');
// { name: 'eggs', quantity: '2', unit: '' }

converter.formatIngredientMeasurement({ name: 'Almond flour', quantity: '1', unit: 'cup' }, 'metric');
// { quantity: '96', unit: 'g' }

Unit Promotion And Demotion

unitScaling controls what happens after scaling measured units.

scaleIngredientForDisplay({ name: 'flour', quantity: '2', unit: 'tbsp' }, 2, 'us', { unitScaling: 'auto' });
// { name: 'flour', quantity: '¼', unit: 'cup' }

scaleIngredientForDisplay({ name: 'flour', quantity: '2', unit: 'tbsp' }, 2, 'us', { unitScaling: 'preserve' });
// { name: 'flour', quantity: '4', unit: 'tbsp' }

scaleIngredientForDisplay({ name: 'flour', quantity: '1', unit: 'cup' }, 0.125, 'us', {
  unitScaling: [{ from: 'cup', to: ['tsp'] }]
});
// { name: 'flour', quantity: '6', unit: 'tsp' }

auto is the default. It uses common cooking preferences for tsp, tbsp, cup, oz, and lb.

Measurement conversion recognizes teaspoons, tablespoons, cups, fluid ounces, milliliters, liters, ounces, pounds, grams, and kilograms, including common singular, plural, and abbreviated spellings.

Metric Conversion Rules

metricConversion controls which source units convert when formatting for metric display.

formatIngredientMeasurement({ name: 'vanilla', quantity: '2', unit: 'tbsp' }, 'metric', {
  metricConversion: [{ from: ['tsp', 'tbsp'], convert: false }]
});
// { quantity: '2', unit: 'tbsp' }

formatIngredientMeasurement({ name: 'vanilla', quantity: '2', unit: 'tbsp' }, 'metric', {
  metricConversion: [{ from: 'tbsp', convert: true, metricMode: 'volume' }]
});
// { quantity: '30', unit: 'ml' }

Supported values:

  • auto: convert cups and mass units; preserve small spoon measures.
  • all: convert every known unit.
  • preserve: keep every source unit.
  • rule list: override specific units and let unmatched units follow auto.

Count Ingredients

Count ingredients are unitless or use count-like units such as egg, clove, piece, or slice.

scaleIngredientForDisplay({ name: 'eggs', quantity: '3', unit: '' }, 0.5, 'us', { countScaling: 'round' });
// { name: 'eggs', quantity: '2', unit: '' }

Supported countScaling values:

  • fractional: preserve the exact scaled amount. This is the default.
  • round: round to the nearest whole count.
  • floor: round down.
  • ceil: round up.
  • component: special handling for egg components.

Recognized count units include egg, clove, piece, slice, can, package, packet, jar, bunch, sprig, each, and their common plural forms.

Egg Components

For fractional eggs, the library can keep the amount as beaten egg or split quarter-through-three-quarter egg remainders into yolk/white rows. Remainders below one-quarter round down, and remainders above three-quarters round up.

scaleIngredientForDisplay({ name: 'eggs', quantity: '3', unit: '' }, 0.5, 'us', {
  countScaling: 'component',
  eggComponent: 'beaten'
});
// { name: 'eggs', quantity: '1 ½', unit: '', note: 'beaten' }

scaleIngredientComponentsForDisplay({ name: 'eggs', quantity: '3', unit: '' }, 0.5, 'us', {
  countScaling: 'component',
  eggComponent: 'yolk'
});
// [
//   { name: 'egg', quantity: '1', unit: '' },
//   { name: 'egg yolk', quantity: '1', unit: '' }
// ]

scaleIngredientsForDisplay also uses component splitting, so whole recipe lists can expand one egg row into multiple display rows.

Formatting

formatIngredientMeasurement({ name: 'Milk', quantity: '1/2', unit: 'cup' }, 'us', {
  fractions: 'ascii',
  unitStyle: 'long'
});
// { quantity: '1/2', unit: 'cup' }

formatIngredientText({ name: 'flour', quantity: '2', unit: 'cup' }, 'us');
// '2 cups flour'

Fraction display can be unicode, ascii, or decimal.

Conversion Notes

Kitchen conversions are approximate. US volume conversions use US customary measures, and ingredient-specific cup-to-gram conversions use built-in density assumptions for common ingredients such as butter, flour, sugar, oats, rice, honey, water, and milk.

When converting cups to metric weight, matching ingredient-specific densities take priority. If no matching density exists and metric weight is requested, the library falls back to a water-like approximation where 1 ml is treated as roughly 1 g. Use createRecipeConverter({ densities }) to provide app-specific values for ingredients you care about.

Browser Demo

From a repository checkout, run the interactive example page to try the parser, scaling slider, metric conversion options, fraction formatting, count handling, unit promotion, and custom density overrides in a browser.

npm run demo:web

Then open http://127.0.0.1:5173/.

API Reference

Main Functions

| Function | Use it for | | --------------------------------------------------------------------------------- | ------------------------------------------------------------------- | | parseIngredientLine(line) | Parse a common recipe line into { quantity, unit, name, note }. | | scaleIngredientQuantity(quantity, factor, options?) | Scale a standalone quantity string. | | scaleIngredientForDisplay(ingredient, factor, targetSystem, options?) | Scale one ingredient object and format it for display. | | scaleIngredientComponentsForDisplay(ingredient, factor, targetSystem, options?) | Scale one ingredient and allow egg component splits. | | scaleIngredientsForDisplay(ingredients, factor, targetSystem, options?) | Scale a full ingredient list. | | scaleRecipeYield(yieldText, factor, options?) | Scale labels such as 8 servings, Makes 2 loaves, or ranges. | | formatIngredientMeasurement(ingredient, targetSystem, options?) | Format one measurement for us or metric. | | convertMeasurement(ingredient, targetSystem, options?) | Format one measurement and include conversion metadata. | | formatIngredientText(ingredient, targetSystem, options?) | Produce display text such as 2 cups flour. | | createRecipeConverter(options) | Create a converter with app-wide defaults and custom density rules. |

Quantity Helpers

| Function | Use it for | | ---------------------------------------- | ------------------------------------------------------------------------------------- | | parseRecipeQuantity(quantity) | Parse decimals, fractions, mixed fractions, glyph fractions, and approximate numbers. | | formatRecipeQuantity(value, options?) | Format numbers as decimals or fractions. | | normalizeMeasurementUnit(unit, style?) | Normalize aliases such as Tablespoons to tbsp or tablespoon. |

Types

type MeasurementSystem = 'us' | 'metric';
type ScaleFactor = number | string;
type FractionFormat = 'unicode' | 'ascii' | 'decimal';
type UnitStyle = 'short' | 'long';
type MetricMode = 'auto' | 'weight' | 'volume';
type CountScaling = 'fractional' | 'round' | 'floor' | 'ceil' | 'component';
type YieldScaling = 'round' | 'fractional' | 'floor' | 'ceil';
type EggComponent = 'beaten' | 'yolk' | 'white';
type UnitScaling = 'auto' | 'preserve' | UnitScalingRule[];
type MetricConversion = 'auto' | 'all' | 'preserve' | MetricConversionRule[];
type IngredientMeasurement = {
  name: string;
  quantity?: string;
  unit?: string;
  note?: string;
};

Development

npm install
npm run format
npm run lint
npm run format:check
npm run test
npm run check
npm run build
npm run package:test
npm run pack:dry
npm run release:check

npm run release:check runs the full local release gate. npm run pack:dry verifies the publish contents without creating a tarball, and npm pack runs lint, format checking, type checking, tests, and build through the prepack script. CI runs the same verification on Node.js 18, 20, and 22.

| Command | Use it for | | ----------------------- | ----------------------------------------------- | | npm run demo:web | Build the library and serve the browser demo. | | npm run format | Rewrite files with Prettier. | | npm run lint | Run ESLint. | | npm run format:check | Check Prettier formatting without rewriting. | | npm run test | Run the Vitest suite. | | npm run check | Type-check TypeScript without emitting output. | | npm run build | Build ESM, CJS, and declaration output. | | npm run package:test | Pack and test install/import/type consumption. | | npm run pack:dry | Verify publish contents without creating a tgz. | | npm run release:check | Run the full local release gate. |