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 🙏

© 2024 – Pkg Stats / Ryan Hefner

messagepipe

v0.2.3

Published

Formats message strings with number, date, plural, and select placeholders to create localized messages

Downloads

13

Readme

messagepipemessagepipe

Formats message strings with number, date, plural, and select placeholders to create localized messages.

  • Small. Between 700 bytes and 1.3 kilobytes (minified and gzipped). Zero dependencies.
  • Fast. Does absolute minimum amount of computations necessary. View benchmarks.
  • Tree Shakable. Includes separate global transformers config that can be omitted.
  • Pipe syntax. Transformer functions can customized and chained.
  • View framework support. Use React/Preact etc. components as transformers.
  • It has good TypeScript support.
import { MessagePipe } from 'messagepipe'

const msg = MessagePipe().compile('Hello {planet}!')

msg({ planet: 'Mars' }) // => "Hello Mars!"

live demo

import { MessagePipe } from 'messagepipe'

const { compile } = MessagePipe({
  reverse: (val) => val.split('').reverse().join(''),
  capitalize: (val) => val[0].toUpperCase() + val.slice(1).toLowerCase(),
})

const msg = compile('Hello {planet | reverse | capitalize}!')

msg({ planet: 'Mars' }) // => "Hello Sram!"

live demo

Install

npm install messagepipe

Guide

Core concepts

          ┌-transformer
          |     ┌-argument name
          |     |     ┌-argument value
          ├--┐  ├---┐ ├-┐

  {name | json, space:101}

  ├----------------------┘
  |├--┘   ├-------------┘
  ||      |     ├-------┘
  ||      |     └-argument
  ||      └-pipe
  |└-selector
  └-message

In one message there can only be one selector, but there can be unlimited number of pipes with unlimited number of arguments in them. It is possible to build dynamic selector (meaning message can be inside it), but it is not possible to build dynamic pipes except for argument values.

So both of these are valid:

  1. "Hello {agents.{index}.fistName}";
  2. "{a} + {b} = {a | sum, sequence:{b}}". (Note: sum is a custom transformer in this case).

Message

Contains everything between { and } that in large includes 1 selector and n pipes.

Selector

String value that points to value from given props object e.g.:

  • "{name}" + { name: 'john' } => "john";
  • "{agents[0].name}" + { agents: [{ name: 'john' }] } => "john"

Pipe

A combination of 1 transformer and n arguments e.g.:

  • "{name | capitalize}";
  • "{name | reverse | capitalize}";
  • "{a | sum, sequence:1, double}" (Note: argument "double" will pass true value to "sum" transformer).

Transformer

Function that can transform value that is being selected from given props.

Lets define "capitalize" transformer that would uppercase the first letter of any string:

function capitalize(value: string) {
  return value[0].toUpperCase() + value.slice(1).toLowerCase();
}

To use this transformer define it when initiating MessagePipe and then it will be available to pipes with name "capitalize":

const msgPipe = MessagePipe({
  capitalize,
})

This would be valid use case for it: "Greetings {name | capitalize}!".

Argument

To allow more functionality, we can use arguments, that are passed to transformer function.

function increment(value: number, { by = 1 }: Record<string, any> = {}) {
  return value + by;
}

We can now use it like this:

  • "{count | increment}" + { count: 1 } => 2;
  • "{count | increment | by:1}" + { count: 1 } => 2;
  • "{count | increment | by:5}" + { count: 1 } => 6.

We can stack any number of arguments separated by , (comma).

Global transformers

There are number of already provided transformers, but they MUST be added to MessagePipe function when initiating. This is by design to help with tree shaking (although they don't contribute that much to package size, if there are additions in future, that won't hurt anyone).

defaultTransformers

function defaultTransformers(): MessagePipeTransformers

select

Selects what text to show based on incoming value.

const msg = compile('{gender | select, male:"He", female:"She", other:"They"} liked this.')

msg({ gender: 'male' }) // "He liked this"
msg({ gender: 'female' }) // "She liked this"
msg({ }) // "They liked this"

json

Runs value through JSON.stringify.

intlTransformers

function intlTransformers(locale?: string): MessagePipeTransformers

number

Formats numbers using Intl.NumberFormat. All options are available as arguments in pipes.

const msg = compile('{price | number}')

msg({ price: 123456.789 }) // "123,456.789"
const msg = compile('Price: {price | number, style:"currency", currency:"EUR"}')

msg({ price: 123 }) // "Price: 123,00 €"

plural

Selects correct text to show based on Intl.PluralRules. All options are available as arguments in pipes.

const msg = compile('I have {fruits | plural, one:"1 fruit", other:"# fruits"}')

msg({ fruits: 0 }) // "I have 0 fruits"
msg({ fruits: 1 }) // "I have 1 fruit"
msg({ fruits: 2 }) // "I have 2 fruits"

date

Formats date using Intl.DateTimeFormat. All options are available as arguments in pipes.

const msg = compile('Todays date {now | date}')

msg({ now: new Date('1977-05-25') }) // "Todays date 25/05/1977"

time

Formats time using Intl.DateTimeFormat. All options are available as arguments in pipes.

const msg = compile('Currently it is {now | time}')

msg({ now: new Date('1983-05-25 16:42') }) // "Currently it is 16:42:00"

API

MessagePipe

This is the main function that takes in all the transformers that will be available to all the messages.

function MessagePipe(transformers?: MessagePipeTransformers): {
  compileRaw(message: string): (props?: Record<string, any>) => string[]
  compile(message: string): (props?: Record<string, any>) => string
}

Example usage:

const messagePipe = MessagePipe({
  hello: (value) => `Hello ${value}!`,
})

Now all the messages that get compiled from messagePipe can use this transformer like so "{name | hello}".

compile

This is where given message gets parsed and prepared for usage. It is very efficient compiler that does only 1 pass and prepares very tiny and performant function from it.

Given this message "Hello {name | capitalize}!", compiler will output this function (a) => "Hello " + capitalize(a.name) + "!" and that is the only thing that runs when executing it. No hidden performance penalties.

compileRaw

This is practically the same as compile but instead of it returning one string, it returns array of all of the things as a separate chunks so that this compiler can be used as part of React component for example.

So from the example that was before, the output of that message would be (a) => ["Hello ", capitalize(a.name), "!"].

Benchmarks

It is necessary for me that this library is as small and as fast as possible. Since this library compares directly with MessageFormat, I treated both as equal in benchmarks.

Message | MessageFormat | MessagePipe | Improvement |-|-|-|-| "Wow" | 926,368 ops/s | 1,847,253 ops/s | 2x "Hello {planet}" | 560,131 ops/s | 1,024,051 ops/s | 1.8x select transformer | 209,513 ops/s | 337,226 ops/s | 1.6x

Framework integration

Works with React and Preact out of the box. Just swap out compile with compileRaw and good to go. This works because it returns raw array of values that was the output of selectors and transformers.

import { MessagePipe } from 'messagepipe'

function Mention(username) {
  const {href} = useUser(username)

  return <a href={href}>{username}</a>
}

// We use React/Preact component as a transformer
const { compileRaw } = MessagePipe({ Mention })
const msg = compileRaw('Hello {name | Mention}!')

function App() {
  return <div>{msg({name: 'john'})}</div>
} // => "<div>Hello <a href="...">john</a>!</div>"

Live demo on Stackblitz.

Since we used compileRaw, library would output something like this: ['Hello ', [ReactElement], '!'].

This will work with any kind of framework or custom library.

Motivation

I was used to messageformat being the go to for this sort of stuff, but it has big flaws in the spec and library maintainers obviously wouldn't want to deviate from it. So the goal for messagepipe was to create NEW spec that solves all of the issues with it + must be faster & smaller.

One immediate flaw that MessagePipe solves is ability to select nested values and build dynamic messages.

License

MIT © Marcis Bergmanis