Content/1.Guide/1.Index
---
title: Setup
description: Install magic-regexp from npm and (optionally) enable the build-time transform via a plugin.
---
First, install magic-regexp:
:pm-install{name="magic-regexp"}
---
Second, optionally, you can enable the included transform, which enables zero-runtime usage.
::code-group
``js [nuxt.config.ts]
// Nuxt 3
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
// This will also enable auto-imports of magic-regexp helpers
modules: ['magic-regexp/nuxt'],
})
import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [MagicRegExpTransformPlugin.vite()],
})
// or, if using next.config.js
// const { MagicRegExpTransformPlugin } = require('magic-regexp/transform')
import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'
export default {
webpack(config) {
config.plugins = config.plugins || []
config.plugins.push(MagicRegExpTransformPlugin.webpack())
return config
},
}
import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'
// unbuild
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
hooks: {
'rollup:options': (options, config) => {
config.plugins.push(MagicRegExpTransformPlugin.rollup())
},
},
})
::---
Content/1.Guide/2.Usage
---
title: Usage
---
import { createRegExp, exactly } from 'magic-regexp'
const regExp = createRegExp(exactly('foo/test.js').after('bar/'))
console.log(regExp)
// /(?<=bar\/)foo\/test\.js/
createRegExpcreateRegExp
Every pattern you create with the library should be wrapped in
, which enables the build-time transform.createRegExpaccepts an arbitrary number of arguments of typestringorInput(built up using helpers frommagic-regexp), and an optional final argument of an array of flags or a flags string. It creates aMagicRegExp, which concatenates all the patterns from the arguments that were passed in.
import { createRegExp, exactly, global, maybe, multiline } from 'magic-regexp'
createRegExp(exactly('foo').or('bar'))
createRegExp('string-to-match', [global, multiline])
// you can also pass flags directly as strings or Sets
createRegExp('string-to-match', ['g', 'm'])
// or pass in multiple string There are a range of helpers that can be used to activate pattern matching, and they can be chained. Each one of these returns an object of type | | | ::alert All of the helpers above return an object of type | | | ::alert When using This is true not just for the final RegExp, but also for the pieces you create along the way. So, for example: and input patterns,
// all inputs will be concatenated to one RegExp pattern
createRegExp(
'foo',
maybe('bar').groupedAs('g1'),
'baz',
[global, multiline]
)
// equivalent to /foo(?<g1>(?:bar)?)baz/gm::alertmagic-regexp
By default, all helpers from assume that input that is passed should be escaped - so no special RegExp characters apply. So createRegExp('foo.\d') will not match food3 but only foo.\d exactly.Input
::Creating inputs
that can be passed directly to new RegExp, createRegExp, to another helper or chained to produce more complex patterns.charIn
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| , charNotIn | this matches or doesn't match any character in the string provided. |anyOf
| | this takes a variable number of inputs and matches any of them. |char
| , word, wordChar, wordBoundary, digit, whitespace, letter, letter.lowercase, letter.uppercase, tab, linefeed and carriageReturn | these are helpers for specific RegExp characters. |not
| | this can prefix word, wordChar, wordBoundary, digit, whitespace, letter, letter.lowercase, letter.uppercase, tab, linefeed or carriageReturn. For example createRegExp(not.letter). |maybe
| | equivalent to ? - this takes a variable number of inputs and marks them as optional. |oneOrMore
| | Equivalent to + - this takes a variable number of inputs and marks them as repeatable, any number of times but at least once. |exactly
| | This takes a variable number of inputs and concatenate their patterns, and escapes string inputs to match it exactly. |string
All helpers that takes and Input are variadic functions, so you can pass in one or multiple arguments of string or Input to them and they will be concatenated to one pattern. for example, exactly('foo', maybe('bar')) is equivalent to exactly('foo').and(maybe('bar')).Input
::Chaining inputs
that can be chained with the following helpers:and
| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| | this takes a variable number of inputs and adds them as new pattern to the current input, or you can use and.referenceTo(groupName) to adds a new pattern referencing to a named group. |or
| | this takes a variable number of inputs and provides as an alternative to the current input. |after
| , before, notAfter and notBefore | these takes a variable number of inputs and activate positive/negative lookahead/lookbehinds. Make sure to check browser support as not all browsers support lookbehinds (notably Safari). |times
| | this is a function you can call directly to repeat the previous pattern an exact number of times, or you can use times.between(min, max) to specify a range, times.atLeast(x) to indicate it must repeat at least x times, times.atMost(x) to indicate it must repeat at most x times or times.any() to indicate it can repeat any number of times, _including none_. |optionally
| | this is a function you can call to mark the current input as optional. |as
| | alias for groupedAs |groupedAs
| | this defines the entire input so far as a named capture group. You will get type safety when using the resulting RegExp with String.match(). |grouped
| | this defines the entire input so far as an anonymous group. |at
| | this allows you to match beginning/ends of lines with at.lineStart() and at.lineEnd(). |anyOf
By default, for better regex performance, creation input helpers such as , maybe, oneOrMore, and chaining input helpers such as or, times(.between/atLeast/any), or optionally will wrap the input in a non-capturing group with (?:). You can use chaining input helper grouped after any Input type to capture it as an anonymous group.magic-regexp
::Debugging
, a TypeScript generic is generated for you that should show the RegExp that you are constructing, as you go.
import { exactly } from 'magic-regexp'
exactly('test.mjs')
// (alias) exactly<"test.mjs">(input: "test.mjs"): Input<"test\\.mjs", never>
exactly('test.mjs').or('something.else')
// (property) Input<"test\\.mjs", never>.or: <"something.else">(input: "something.else") => Input<"(?:test\\.mjs|something\\.else)", never>
Each function, if you hover over it, shows what's going in, and what's coming out by way of regular expression.toString()You can also call
on any input to see the same information at runtime.magic-regexp/further-magicType-Level match result (experimental)
We also provide an experimental feature that allows you to obtain the type-level results of a RegExp match or replace in string literals. To try this feature, please import all helpers from a subpath exportinstead ofmagic-regexp.
import { createRegExp, digit, exactly } from 'magic-regexp/further-magic'
This feature is especially useful when you want to obtain the type of the matched groups or test if your RegExp matches and captures from a given string as expected.This feature works best for matching literal strings such as
'foo'.match(createRegExp(exactly('foo').groupedAs('g1')))
which will return a matched result of type['foo', 'foo'].result.groupsof type{ g1: 'foo' },result.indexof type0andresult.lengthof type2.If matching with dynamic string, such as
myString.match(createRegExp(exactly('foo').or('bar').groupedAs('g1')))
the type of the matched result will benull, or array of union of possible matches["bar", "bar"] | ["foo", "foo"]andresult.groupswill be type{ g1: "bar" } | { g1: "foo" }.::alert
For more usage details please see the usage examples or test. For type-related issues, please report them to type-level-regexp.
::---
Content/1.Guide/3.Examples
---
title: Examples
---Quick-and-dirty semver
import { char, createRegExp, digit, maybe, oneOrMore } from 'magic-regexp'
createRegExp(
oneOrMore(digit).groupedAs('major'),
'.',
oneOrMore(digit).groupedAs('minor'),
maybe('.', oneOrMore(char).groupedAs('patch'))
)
// /(?<major>\d+)\.(?<minor>\d+)(?:\.(?<patch>.+))?/
References to previously captured groups using the group name
import assert from 'node:assert'
import { char, createRegExp, oneOrMore, wordChar } from 'magic-regexp'
const TENET_RE = createRegExp(
wordChar
.groupedAs('firstChar')
.and(wordChar.groupedAs('secondChar'))
.and(oneOrMore(char))
.and.referenceTo('secondChar')
.and.referenceTo('firstChar')
)
// /(?<firstChar>\w)(?<secondChar>\w).+\k<secondChar>\k<firstChar>/
assert.equal(TENET_RE.test('TEN<==O==>NET'), true)
createRegExpType-level RegExp match and replace result (experimental)
::alert
This feature is still experimental, to try it please importand allInputhelpers frommagic-regexp/further-magicinstead ofmagic-regexp.magic-regexp v3.2.5.beta.1 just release!
::When matching or replacing with literal string such as
import {
anyOf,
createRegExp,
digit,
exactly,
oneOrMore,
wordChar
} from 'magic-regexp/further-magic'
const literalString = 'magic-regexp 3.2.5.beta.1 just release!'
const semverRegExp = createRegExp(
oneOrMore(digit)
.as('major')
.and('.')
.and(oneOrMore(digit).as('minor'))
.and(
exactly('.')
.and(oneOrMore(anyOf(wordChar, '.')).groupedAs('patch'))
.optionally()
)
)
// String.match() example
const matchResult = literalString.match(semverRegExp)
matchResult[0] // "3.2.5.beta.1"
matchResult[3] // "5.beta.1"
matchResult.length // 4
matchResult.index // 14
matchResult.groups
// groups: {
// major: "3";
// minor: "2";
// patch: "5.beta.1";
// }
// String.replace() exampleminor version "$2" brings many great DX improvements, while patch "$<patch>" fix some bugs and it's
const replaceResult = literalString.replace(
semverRegExp,
)
replaceResult // "magic-regexp minor version \"2\" brings many great DX improvements, while patch \"5.beta.1\" fix some bugs and it's just release!"
When matching dynamic string, the result will be union of possible matchesconst myString = 'dynamic'
const RegExp = createRegExp(exactly('foo').or('bar').groupedAs('g1'))
const matchAllResult = myString.match(RegExp)
matchAllResult
// null | RegExpMatchResult<{
// matched: ["bar", "bar"] | ["foo", "foo"];
// namedCaptures: ["g1", "bar"] | ["g1", "foo"];
// input: string;
// restInput: undefined;
// }>
matchAllResult?.[0] // ['foo', 'foo'] | ['bar', 'bar']
matchAllResult?.length // 2 | undefined
matchAllResult?.groups
// groups: {
// g1: "foo" | "bar";
// } | undefined
---magic-regexpContent/1.Guide/4.Transform
---
title: Transform
---The best way to use
is by making use of the included build-time transform.
const beforeTransform = createRegExp(exactly('foo/test.js').after('bar/'))
// => gets _compiled_ to
const afterTransform = /(?<=bar\/)foo\/test\.js/
Of course, this only works with non-dynamic regexps. Within thecreateRegExpblock you have to include all the helpers you are using frommagic-regexp- and not rely on any external variables. This, for example, will not statically compile into a RegExp, although it will still continue to work with a minimal runtime:
const someString = 'test'
const regExp = createRegExp(exactly(someString))
---magic-regexpContent/1.Guide/5.Converter
---
title: Converter (experimental)
---It is also possible to convert existing regular expressions to
syntax.
import { convert } from 'magic-regexp/converter'
convert(/[abc]/)
// createRegExp(exactly('a').or('b').or('c'))
convert(/(foo)bar\d+/)
// createRegExp(exactly('foo').grouped(), 'bar', oneOrMore(digit))
argsOnlyOptions
-
(boolean)false
_Default:_createRegExp
Only show arguments without
convert(/\w+@\w\.com/, { argsOnly: true })
// oneOrMore(wordChar), '@', wordChar, '.com'
`
---
Content/2.Contribution
---
title: Contribution
---
Roadmap
Future ideas
::list{type=info}
- More TypeScript guard-rails
- More complex RegExp features/syntax
- Instrumentation for accurately getting coverage on RegExps
- Hybrid/partially-compiled RegExps for better dynamic support
::
Development
- Clone this repository
- Enable Corepack using
corepack enable
- Install dependencies using pnpm install
- Run interactive tests using pnpm dev---
Content/Index
---
title: Home
navigation: false
---
::hero
---
actions:
- name: Documentation
leftIcon: 'lucide:rocket'
to: /guide
- name: Try it out
leftIcon: 'lucide:play'
variant: outline
to: https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAbzgYwBYEMoBoVQKbox4BKeA5gKIAeYOAJsGcDDnlesjADYCeOI6HgCM8OCADs8AeSgBZaKLgB3aHQDCGKHAC+cAGZQIIOAHIBTZAFp8ZNmBMAoBwHoAVK4dxXKDONtwYCE9vUEhYRAA6KN0DI1NzYCsbO2c9AFdYVDwoSwTkRy8AiACoHgCeMDw4LjwANzwuOFJKGjgBGDQ4fABnNK4YbrgACjts0DxxGHQuAEpg5ydnZzgAFQq8OhR0MBgMqrJDNLBuh2QJbvgASQARAH1iCjgAXlwCImbqMBH2Tl4hk2AdEsJhmEXQ4joQwYTBgERg426QwArKCDhAjhsAIKIgF0EEzOZncQXOBoo6DF4mbpGKqAywAJgAzAAWACcAAY4Fl8Mp9hATBF2mghjd7hQZgB+CJk44uZZwBVwAB6EsWywAimlEgBrSzgoEMWBlbp4ED1KCnc7wADKFFkADUKMQxc9XoQSORPkNPHAJNI5AooYxmKjDpU6Nj-gIAFbQEFYH0CkwJhV+mTyfBBmGh9HhyNmYDiOMzFNtQQif4RZO+yTpwNoTA5jERnFgQhofEJuYOW0Op1iuXK1WW4nwHp9eAvZD4d0fGjfDjcHj-PQQCDOIgXCLR7ogsF6IhQf5CTDOfFwvAXY+n1frzewncgtVNPB6bITZCXopwMD4WrAdFul4LYdj2TYZUGNJukLMgAiyUkwzgcR0BAPARxJFYKAAOQoFYXSnGd3k9ecfTYRc-hUKB1E0aUwyxHE9GAKALg0TB42UVRWKgWjc3o-4TSJai2JLGt-QzPAhgbKACR9BUwQhCJ8DffBxE-FYIH4vBBK4p9FTgeS6EU1931UvB1JXJiWM0eMHG7IkSXHfp6VdTCcLwh4LyvExXIAHieJ4pH8gA+NyQSAA
---
#title
magic-regexp
#description
A compiled-away, type-safe, readable RegExp alternative.
::
::card-group
::card
---
icon: 'heroicons-cube-transparent'
icon-size: 26
---
#title
Lightweight runtime
#description
Zero-dependency, minimal runtime if no transform is used.
::
::card
---
icon: 'heroicons-wrench'
icon-size: 26
---
#title
... or pure RegExp
#description
Ships with transform to compile to pure regular expression.
::
::card
---
icon: 'heroicons-shield-check'
icon-size: 26
---
#title
Type-safe
#description
Automatically typed capture groups, with generated RegExp displaying on hover.
::
::card
---
icon: 'heroicons-book-open'
icon-size: 26
---
#title
Intuitive syntax
#description
Natural language syntax regular expression builder.
::
::
---
README
🦄 magic-regexp
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
[![Github Actions][github-actions-src]][github-actions-href]
[![Codecov][codecov-src]][codecov-href]
[![Bundlephobia][bundlephobia-src]][bundlephobia-href]
[](https://nuxt.care/?search=magic-regexp)
A compiled-away, type-safe, readable RegExp alternative
- ✨ Changelog
- 📖 Documentation
- ▶️ Online playground
Features
- Runtime is zero-dependency and ultra-minimal
- Ships with transform to compile to pure RegExp
- Automatically typed capture groups
- Natural language syntax
- Generated RegExp displays on hover
💻 Development
- Clone this repository
- Enable Corepack using
corepack enable (use npm i corepack for Node.js < 16.10)
- Install dependencies using pnpm install
- Run interactive tests using pnpm dev`Similar packages
- verbal-expressions
- typed-regex
License
Made with ❤️
Published under MIT License.
[npm-version-src]: https://npmx.dev/api/registry/badge/version/magic-regexp
[npm-version-href]: https://npmx.dev/package/magic-regexp
[npm-downloads-src]: https://npmx.dev/api/registry/badge/downloads/magic-regexp
[npm-downloads-href]: https://npmx.dev/package/magic-regexp
[github-actions-src]: https://img.shields.io/github/actions/workflow/status/danielroe/magic-regexp/ci.yml?branch=main&style=flat-square
[github-actions-href]: https://github.com/danielroe/magic-regexp/actions?query=workflow%3Aci
[codecov-src]: https://img.shields.io/codecov/c/gh/danielroe/magic-regexp/main?style=flat-square
[codecov-href]: https://codecov.io/gh/danielroe/magic-regexp
[bundlephobia-src]: https://img.shields.io/bundlephobia/minzip/magic-regexp?style=flat-square
[bundlephobia-href]: https://bundlephobia.com/package/magic-regexp
---