{"id":20285643,"url":"https://github.com/marcisbee/messagepipe","last_synced_at":"2025-04-11T08:40:39.407Z","repository":{"id":58015673,"uuid":"526601028","full_name":"Marcisbee/messagepipe","owner":"Marcisbee","description":"Formats message strings with number, date, plural, and select placeholders to create localized messages","archived":false,"fork":false,"pushed_at":"2023-10-11T07:20:54.000Z","size":98,"stargazers_count":44,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-10T06:50:31.168Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Marcisbee.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null}},"created_at":"2022-08-19T12:36:29.000Z","updated_at":"2025-01-30T20:37:25.000Z","dependencies_parsed_at":"2022-08-31T08:00:26.962Z","dependency_job_id":null,"html_url":"https://github.com/Marcisbee/messagepipe","commit_stats":{"total_commits":17,"total_committers":1,"mean_commits":17.0,"dds":0.0,"last_synced_commit":"2fbef84fec5a23ec5e840ae1bf9a5df12f95d0d0"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcisbee%2Fmessagepipe","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcisbee%2Fmessagepipe/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcisbee%2Fmessagepipe/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Marcisbee%2Fmessagepipe/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Marcisbee","download_url":"https://codeload.github.com/Marcisbee/messagepipe/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248362354,"owners_count":21091100,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T14:28:01.746Z","updated_at":"2025-04-11T08:40:39.383Z","avatar_url":"https://github.com/Marcisbee.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# \u003cpicture\u003e\u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/logo-light.svg\"\u003e\u003cimg alt=\"Messagepipe\" src=\"./assets/logo-dark.svg\"\u003e\u003c/picture\u003e\n\n\u003ca href=\"https://snyk.io/test/github/Marcisbee/messagepipe\"\u003e\n  \u003cimg alt=\"snyk\" src=\"https://img.shields.io/snyk/vulnerabilities/github/Marcisbee/messagepipe?style=flat-square\" /\u003e\n\u003c/a\u003e\n\u003ca href=\"https://www.npmjs.com/package/messagepipe\"\u003e\n  \u003cimg alt=\"npm\" src=\"https://img.shields.io/npm/v/messagepipe.svg?style=flat-square\" /\u003e\n\u003c/a\u003e\n\u003ca href=\"https://bundlephobia.com/result?p=messagepipe\"\u003e\n  \u003cimg alt=\"package size\" src=\"https://img.shields.io/bundlephobia/minzip/messagepipe?style=flat-square\" /\u003e\n\u003c/a\u003e\n\u003ca href=\"https://discord.gg/a62gfaDW2e\"\u003e\n  \u003cimg alt=\"discord\" src=\"https://dcbadge.vercel.app/api/server/a62gfaDW2e?style=flat-square\" /\u003e\n\u003c/a\u003e\n\n\u003cbr /\u003e\n\nFormats message strings with number, date, plural, and select placeholders to create localized messages.\n\n* **Small.** Between 700 bytes and 1.3 kilobytes (minified and gzipped).\n  Zero dependencies.\n* **Fast.** Does absolute minimum amount of computations necessary. View [benchmarks](#benchmarks).\n* **Tree Shakable.** Includes separate global transformers config that can be omitted.\n* **Pipe syntax.** Transformer functions can customized and chained.\n* **View framework support.** Use React/Preact etc. components as transformers.\n* It has good **TypeScript** support.\n\n```ts\nimport { MessagePipe } from 'messagepipe'\n\nconst msg = MessagePipe().compile('Hello {planet}!')\n\nmsg({ planet: 'Mars' }) // =\u003e \"Hello Mars!\"\n```\n[live demo](https://runkit.com/marcisbee/messagepipe-demo-1)\n\n```ts\nimport { MessagePipe } from 'messagepipe'\n\nconst { compile } = MessagePipe({\n  reverse: (val) =\u003e val.split('').reverse().join(''),\n  capitalize: (val) =\u003e val[0].toUpperCase() + val.slice(1).toLowerCase(),\n})\n\nconst msg = compile('Hello {planet | reverse | capitalize}!')\n\nmsg({ planet: 'Mars' }) // =\u003e \"Hello Sram!\"\n```\n[live demo](https://runkit.com/marcisbee/messagepipe-demo-2)\n\n## Install\n\n```sh\nnpm install messagepipe\n```\n\n## Guide\n\n### Core concepts\n\n```\n          ┌-transformer\n          |     ┌-argument name\n          |     |     ┌-argument value\n          ├--┐  ├---┐ ├-┐\n\n  {name | json, space:101}\n\n  ├----------------------┘\n  |├--┘   ├-------------┘\n  ||      |     ├-------┘\n  ||      |     └-argument\n  ||      └-pipe\n  |└-selector\n  └-message\n```\n\nIn one [message](#message) there can only be one [selector](#selector), but there can be unlimited number of [pipes](#pipe) with unlimited number of [arguments](#argument) in them. It is possible to build dynamic [selector](#selector) (meaning [message](#message) can be inside it), but it is not possible to build dynamic [pipes](#pipe) except for [argument values](#argument).\n\nSo both of these are valid:\n1. `\"Hello {agents.{index}.fistName}\"`;\n2. `\"{a} + {b} = {a | sum, sequence:{b}}\"`.\n_(Note: sum is a custom transformer in this case)_.\n\n### Message\nContains everything between `{` and `}` that in large includes 1 [selector](#selector) and n [pipes](#pipe).\n\n### Selector\nString value that points to value from given props object e.g.:\n- `\"{name}\"` + `{ name: 'john' }` =\u003e `\"john\"`;\n- `\"{agents[0].name}\"` + `{ agents: [{ name: 'john' }] }` =\u003e `\"john\"`\n\n### Pipe\nA combination of 1 `transformer` and n `arguments` e.g.:\n- `\"{name | capitalize}\"`;\n- `\"{name | reverse | capitalize}\"`;\n- `\"{a | sum, sequence:1, double}\"` _(Note: argument \"double\" will pass `true` value to \"sum\" transformer)_.\n\n### Transformer\nFunction that can transform value that is being selected from given props.\n\nLets define \"capitalize\" transformer that would uppercase the first letter of any string:\n```ts\nfunction capitalize(value: string) {\n  return value[0].toUpperCase() + value.slice(1).toLowerCase();\n}\n```\n\nTo use this transformer define it when initiating MessagePipe and then it will be available to pipes with name \"capitalize\":\n\n```ts\nconst msgPipe = MessagePipe({\n  capitalize,\n})\n```\n\nThis would be valid use case for it: `\"Greetings {name | capitalize}!\"`.\n\n### Argument\nTo allow more functionality, we can use arguments, that are passed to transformer function.\n\n```ts\nfunction increment(value: number, { by = 1 }: Record\u003cstring, any\u003e = {}) {\n  return value + by;\n}\n```\n\nWe can now use it like this:\n- `\"{count | increment}\"` + `{ count: 1 }` =\u003e `2`;\n- `\"{count | increment | by:1}\"` + `{ count: 1 }` =\u003e `2`;\n- `\"{count | increment | by:5}\"` + `{ count: 1 }` =\u003e `6`.\n\nWe can stack any number of arguments separated by `,` (comma).\n\n### Global transformers\nThere 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).\n\n### `defaultTransformers`\n```ts\nfunction defaultTransformers(): MessagePipeTransformers\n```\n\n#### select\nSelects what text to show based on incoming value.\n\n```ts\nconst msg = compile('{gender | select, male:\"He\", female:\"She\", other:\"They\"} liked this.')\n\nmsg({ gender: 'male' }) // \"He liked this\"\nmsg({ gender: 'female' }) // \"She liked this\"\nmsg({ }) // \"They liked this\"\n```\n\n#### json\nRuns value through `JSON.stringify`.\n\n### `intlTransformers`\n```ts\nfunction intlTransformers(locale?: string): MessagePipeTransformers\n```\n\n#### number\nFormats numbers using [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat). All options are available as arguments in pipes.\n\n```ts\nconst msg = compile('{price | number}')\n\nmsg({ price: 123456.789 }) // \"123,456.789\"\n```\n\n```ts\nconst msg = compile('Price: {price | number, style:\"currency\", currency:\"EUR\"}')\n\nmsg({ price: 123 }) // \"Price: 123,00 €\"\n```\n\n#### plural\nSelects correct text to show based on [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules). All options are available as arguments in pipes.\n\n```ts\nconst msg = compile('I have {fruits | plural, one:\"1 fruit\", other:\"# fruits\"}')\n\nmsg({ fruits: 0 }) // \"I have 0 fruits\"\nmsg({ fruits: 1 }) // \"I have 1 fruit\"\nmsg({ fruits: 2 }) // \"I have 2 fruits\"\n```\n\n#### date\nFormats date using [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). All options are available as arguments in pipes.\n\n```ts\nconst msg = compile('Todays date {now | date}')\n\nmsg({ now: new Date('1977-05-25') }) // \"Todays date 25/05/1977\"\n```\n\n#### time\nFormats time using [`Intl.DateTimeFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat). All options are available as arguments in pipes.\n\n```ts\nconst msg = compile('Currently it is {now | time}')\n\nmsg({ now: new Date('1983-05-25 16:42') }) // \"Currently it is 16:42:00\"\n```\n\n## API\n\n### `MessagePipe`\nThis is the main function that takes in all the transformers that will be available to all the messages.\n\n```ts\nfunction MessagePipe(transformers?: MessagePipeTransformers): {\n  compileRaw(message: string): (props?: Record\u003cstring, any\u003e) =\u003e string[]\n  compile(message: string): (props?: Record\u003cstring, any\u003e) =\u003e string\n}\n```\n\nExample usage:\n\n```ts\nconst messagePipe = MessagePipe({\n  hello: (value) =\u003e `Hello ${value}!`,\n})\n```\n\nNow all the messages that get compiled from `messagePipe` can use this transformer like so `\"{name | hello}\"`.\n\n#### `compile`\nThis is where given [message](#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.\n\nGiven this message `\"Hello {name | capitalize}!\"`, compiler will output this function `(a) =\u003e \"Hello \" + capitalize(a.name) + \"!\"` and that is the only thing that runs when executing it. No hidden performance penalties.\n\n#### `compileRaw`\nThis is practically the same as [compile](#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.\n\nSo from the example that was before, the output of that message would be `(a) =\u003e [\"Hello \", capitalize(a.name), \"!\"]`.\n\n## Benchmarks\nIt 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.\n\nMessage | MessageFormat | MessagePipe | Improvement\n|-|-|-|-|\n[\"Wow\"](https://fastbench.dev/Format-%22Wow%22-JtYOF/0) | 926,368 ops/s | __1,847,253 ops/s__ | 2x\n[\"Hello {planet}\"](https://fastbench.dev/Format-%22Hello-planet%22-tKWLnH/0) | 560,131 ops/s | __1,024,051 ops/s__ | 1.8x\n[select transformer](https://fastbench.dev/Format-select-MovWE/0) | 209,513 ops/s | __337,226 ops/s__ | 1.6x\n\n## Framework integration\nWorks with React and Preact out of the box. Just swap out [`compile`](#compile) with [`compileRaw`](#compile) and good to go. This works because it returns raw array of values that was the output of selectors and transformers.\n\n```tsx\nimport { MessagePipe } from 'messagepipe'\n\nfunction Mention(username) {\n  const {href} = useUser(username)\n\n  return \u003ca href={href}\u003e{username}\u003c/a\u003e\n}\n\n// We use React/Preact component as a transformer\nconst { compileRaw } = MessagePipe({ Mention })\nconst msg = compileRaw('Hello {name | Mention}!')\n\nfunction App() {\n  return \u003cdiv\u003e{msg({name: 'john'})}\u003c/div\u003e\n} // =\u003e \"\u003cdiv\u003eHello \u003ca href=\"...\"\u003ejohn\u003c/a\u003e!\u003c/div\u003e\"\n```\nLive demo on [Stackblitz](https://stackblitz.com/edit/vitejs-vite-2cz8zx?file=src%2FApp.tsx\u0026terminal=dev).\n\nSince we used [compileRaw](#compileraw), library would output something like this: `['Hello ', [ReactElement], '!']`.\n\nThis will work with any kind of framework or custom library.\n\n# Motivation\nI 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 \u0026 smaller.\n\nOne immediate flaw that MessagePipe solves is ability to select nested values and build dynamic messages.\n\n# License\n[MIT](LICENCE) \u0026copy; [Marcis Bergmanis](https://twitter.com/marcisbee)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcisbee%2Fmessagepipe","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcisbee%2Fmessagepipe","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcisbee%2Fmessagepipe/lists"}