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

@loilo/rx

v3.0.0

Published

A template tag for creating regular expressions

Downloads

26

Readme

rx

A regular expression template tag written in TypeScript

Tests Version on npm

IMPORTANT: This package is ESM-only. Read more.

Motivation

Regular expressions are a tool needed fairly often in the web world — mostly due to JavaScript's lackluster ability to do string matching/searching/replacing.

However, writing regular expressions gets messy very quickly as soon as any third party input is involved. User-provided strings have to be escaped and the convenient regex literals give way to that unwieldy RegExp constructor.

After struggling with this for years, it crossed my mind (and the mind of many others) that this might be solved pretty comfortably with a tagged template literal:

import rx from '@loilo/rx'

const disallowedWindowsFilenameChars = rx`[${'<>:"/\\|?*'}]`

if (disallowedWindowsFilenameChars.test(someFilename)) {
  console.error('Invalid characters in filename')
} else {
  console.log("You're probably fine")
}

This package exposes a nice rx template tag. It's

  • tiny — less than 450 bytes minified & gzipped, no dependencies
  • typed — written in TypeScript, so our IDE can provide type hints 🎉
  • fun — to the extent that regular expressions can be fun

Installation

Install from npm:

npm install --save @loilo/rx

Or use in the browser via unpkg (using the global rx variable):

<script src="https://unpkg.com/@loilo/rx"></script>

Usage

Disclaimer: Please keep in mind that all code examples in this readme are exclusively for demonstrational purposes. Most of them can be solved more efficiently and elegantly without any use of regular expressions.

Flags

We can add flags to our regular expressions like this:

function matchCaseInsensitive(string) {
  return rx.i`${string}`
}

const pattern = matchCaseInsensitive('foo') // pattern = /foo/i
pattern.test('foo') // true
pattern.test('fOO') // true

Note: This way of adding flags will only work in modern environments (Node.js version 6 and up, evergreen browsers). If we need to support Internet Explorer etc., we may use rx('i') instead of rx.i.

This is because an unpolyfillable technique called Proxies is used for the default way of adding flags.

Raw Strings

From time to time, we may want to include control characters in some kind of conditional when rx inadvertently escapes them:

function naiveNumberMatcher(allowFloat) {
  return rx`^-?[0-9]+${allowFloat ? '(\\.[0-9]+)?' : ''}$`
}

const pattern = naiveNumberMatcher(true)
// pattern = /^-?[0-9]+\(\\\.\[0\-9\]\+\)\?$/
// Snap! This won't match floating point numbers.

Luckily, there's an easy solution: just return the control characters as a regular expression:

function naiveNumberMatcher(allowFloat) {
  return rx`^-?[0-9]+${allowFloat ? /(\.[0-9]+)?/ : ''}$`
}

const intPattern = naiveNumberMatcher(false) // intPattern = /^-?[0-9]+$/
intPattern.test('abc') // false
intPattern.test('0') // true
intPattern.test('-1') // true
intPattern.test('1.5') // false

const floatPattern = naiveNumberMatcher(true) // floatPattern = /^-?[0-9]+(\.[0-9]+)?$/
floatPattern.test('abc') // false
floatPattern.test('0') // true
floatPattern.test('-1') // true
floatPattern.test('1.5') // true

Alternatively, we could have wrapped the control characters in an rx.raw() call which will exclude them from being escaped:

function naiveNumberMatcher(allowFloat) {
  return rx`^-?[0-9]+${allowFloat ? rx.raw('(\\.[0-9]+)?') : ''}$`

  // rx.raw also works as a template tag — note that we don't even have to double-escape the "." wildcard:
  return rx`^-?[0-9]+${allowFloat ? rx.raw`(\.[0-9]+)?')` : ''}$`
}

This can be necessary when the wrapped control characters are quantifiers which cannot form a regular expression of their own, e.g. /?/.

Arrays

If an array is passed as a placeholder, its entries will be escaped and joined by a vertical bar — this way, we can easily express enumerations:

function oneOf(...strings) {
  return rx.i`^${strings}$`
}

const pattern = oneOf('a', 'b') // pattern = /^a|b$/i
pattern.test('a') // true
pattern.test('B') // true
pattern.test('d') // false

Note that arrays may also contain regular expressions or rx.raw strings which stay unescaped as described above:

function oneOfTheseOrInteger(...strings) {
  return rx.i`^(${[...strings, /[0-9]+/]})$`
}

const pattern = oneOfTheseOrInteger('a', 'b') // pattern = /^(a|b|[0-9]+)$/i
pattern.test('A') // true
pattern.test('d') // false
pattern.test('42') // true

Arrays can even be nested and are flattened automatically:

const naivePluralize = value => value + 's'

function oneOrMultipleOf(...strings) {
  return rx`^${strings.map(string => [string, naivePluralize(string)])}$`
}

oneOrMultipleOf('cat', 'dog') // /^cat|cats|dog|dogs$/i

Credit

In the world of programming, you're basically never the first person to come up with a clever trick. I googled my idea and it turned out that Lea Verou published the very same thing in 2018, and Dr. Axel Rauschmeyer created a similar utility in 2017.

Key differences to those implementations are that I added type hints (therefore this package is written in TypeScript) and the aforementioned capability to merge in arrays and raw strings.