A compiled-away, type-safe, readable RegExp alternative
magic-regexp
is currently a work in progress.
- Runtime is zero-dependency and ultra-minimal
- Ships with transform for compiling runtime to pure RegExp
- Supports automatically typed capture groups
- Packed with useful utilities:
charIn
,charNotIn
,anyOf
,char
,word
,digit
,whitespace
,letter
,tab
,linefeed
,carriageReturn
,not
,maybe
,exactly
,oneOrMore
- All chainable with
and
,or
,after
,before
,notAfter
,notBefore
,times
,as
,at
,optionally
Future ideas
- More TypeScript guard-rails
- More complex RegExp features/syntax
- Instrumentation for accurately getting coverage on RegExps
- Hybrid/partially-compiled RegExps for better dynamic support
Install package:
# npm
npm install magic-regexp
# yarn
yarn add magic-regexp
# pnpm
pnpm install magic-regexp
import { createRegExp, exactly } from 'magic-regexp'
const regExp = createRegExp(exactly('foo/test.js').after('bar/'))
console.log(regExp)
// /(?<=bar\/)foo\/test\.js/
Every pattern you create with the library should be wrapped in createRegExp
. It also takes a second argument, which is an array of flags.
import { createRegExp, global, multiline } from 'magic-regexp'
createRegExp('string-to-match', [global, multiline])
// you can also pass flags directly as strings
createRegExp('string-to-match', ['g', 'm'])
Note By default, all helpers from
magic-regexp
assume that input that is passed should be escaped - so no special RegExp characters apply. SocreateRegExp('foo?\d')
will not matchfood3
but onlyfoo?\d
exactly.
There are a range of helpers that can be used to activate pattern matching, and they can be chained.
They are:
charIn
,charNotIn
- this matches or doesn't match any character in the string provided.anyOf
- this takes an array of inputs and matches any of them.char
,word
,digit
,whitespace
,letter
,tab
,linefeed
andcarriageReturn
- these are helpers for specific RegExp characters.not
- this can prefixword
,digit
,whitespace
,letter
,tab
,linefeed
orcarriageReturn
. For examplecreateRegExp(not.letter)
.maybe
- equivalent to?
- this marks the input as optional.oneOrMore
- equivalent to+
- this marks the input as repeatable, any number of times but at least once.exactly
- this escapes a string input to match it exactly.
All of these helpers return an object of type Input
that can be chained with the following helpers:
and
- this adds a new pattern to the current input.or
- this provides an alternative to the current input.after
,before
,notAfter
andnotBefore
- these 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 usetimes.between(min, max)
to specify a range,times.atLeast(num)
to indicate it must repeat x times ortimes.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
- this defines the entire input so far as a named capture group. You will get type safety when using the resulting RegExp withString.match()
.at
- this allows you to match beginning/ends of lines withat.lineStart()
andat.lineEnd()
.
The best way to use magic-regexp
is by making use of the included transform.
const regExp = createRegExp(exactly('foo/test.js').after('bar/'))
// => gets _compiled_ to
const regExp = /(?<=bar\/)foo\/test\.js/
Of course, this only works with non-dynamic regexps. Within the createRegExp
block you have to include all the helpers you are using from magic-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))
import { defineNuxtConfig } from 'nuxt'
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
// This will also enable auto-imports of magic-regexp helpers
modules: ['magic-regexp/nuxt'],
})
import { defineConfig } from 'vite'
import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'
export default defineConfig({
plugins: [MagicRegExpTransformPlugin.vite()],
})
For Next, you will need to ensure you are using next.config.mjs
or have set "type": "module"
in your `package.json.
import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'
export default {
webpack(config) {
config.plugins = config.plugins || []
config.plugins.push(MagicRegExpTransformPlugin.webpack())
return config
},
}
import { defineBuildConfig } from 'unbuild'
import { MagicRegExpTransformPlugin } from 'magic-regexp/transform'
export default defineBuildConfig({
hooks: {
'rollup:options': (options, config) => {
config.plugins.push(MagicRegExpTransformPlugin.rollup())
},
},
})
import { createRegExp, exactly, oneOrMore, digit } from 'magic-regexp'
// Quick-and-dirty semver
createRegExp(
oneOrMore(digit)
.as('major')
.and('.')
.and(oneOrMore(digit).as('minor'))
.and(exactly('.').and(oneOrMore(char).as('patch')).optionally())
)
// /(?<major>(\d)+)\.(?<minor>(\d)+)(\.(?<patch>(.)+))?/
- Clone this repository
- Enable Corepack using
corepack enable
(usenpm i -g corepack
for Node.js < 16.10) - Install dependencies using
pnpm install
- Run interactive tests using
pnpm dev
Made with ❤️
Published under MIT License.