magic-regexp

repository·main·Indexed 26 days ago

https://github.com/unjs/magic-regexp

A type-safe, readable alternative to standard Regular Expressions that uses a natural language API. It features a build-time transform to compile patterns into pure RegExp objects for zero runtime overhead, automatic typing for capture groups, and a zero-dependency runtime. Supports integration with Nuxt, Vite, Webpack, and Rollup.

Tokens
6.8K
Snippets
15
Records
48
Agent score
85%

What's inside magic-regexp

  1. Overview of magic-regexp

    main
    magic-regexp is a type-safe, readable alternative to standard Regular Expressions. It uses a natural language syntax to build regex patterns and provides automatic typing for capture groups. To ensure zero runtime overhead, it ships with a transform that compiles the magic-regexp syntax into pure, standard RegExp objects.
  2. Key features of magic-regexp

    main

    Features

    • Zero-dependency runtime: The runtime is ultra-minimal.
    • Compilation transform: Ships with a transform to compile patterns into pure RegExp at build time.
    • Type-safe capture groups: Automatically provides types for capture groups.
    • Natural language syntax: Uses readable methods instead of complex regex strings.
    • Developer experience: The generated RegExp is displayed on hover in supported editors.
  3. Enable build-time transform for zero-runtime usage

    main

    You can optionally enable the included transform to allow for zero-runtime usage. This is done by adding the appropriate plugin to your build tool configuration.

    // 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())
        },
      },
    })
  4. Convert existing regular expressions to magic-regexp syntax

    main

    You can use the convert function from magic-regexp/converter to transform standard JavaScript regular expressions into magic-regexp syntax. This is useful for migrating existing regex patterns to the more readable magic-regexp API.

    Note: This feature is currently marked as experimental.

    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))
  5. Avoid external variables in createRegExp for static compilation

    main

    If you use external variables inside a createRegExp call, the expression will not be statically compiled into a RegExp. While the code will still function using a minimal runtime, you will lose the performance benefits of the build-time transform.

    Example of non-compilable code:

    const someString = 'test'
    const regExp = createRegExp(exactly(someString))
  6. Use experimental type-level match results

    main

    You can obtain type-level results of a RegExp match or replace in string literals using an experimental feature. This allows you to know the exact types of matched groups, the index, and the length at compile time.

    To use this feature, you must import helpers from the magic-regexp/further-magic subpath instead of the standard magic-regexp entry point.

    Key Behaviors:

    • Literal Strings: When matching against a literal string (e.g., 'foo'), the result type will reflect the exact content and structure (e.g., ['foo', 'foo']).
    • Dynamic Strings: When matching against a variable string, the result type will be a union of possible matches or null (e.g., ["bar", "bar"] | ["foo", "foo"] | null).
  7. Set up the magic-regexp development environment

    main

    To contribute to magic-regexp, follow these steps to set up your local development environment:

    1. Clone the repository.
    2. Enable Corepack using corepack enable.
    3. Install dependencies using pnpm install.
    4. Run the interactive development environment/tests using pnpm dev.
    corepack enable
    pnpm install
    pnpm dev
  8. Debug RegExp patterns

    main

    You can inspect the RegExp being constructed in two ways:

    1. TypeScript Intellisense: The library generates TypeScript generics that show the resulting RegExp string when you hover over the code in your IDE.
    2. Runtime: Call .toString() on any Input object to see the generated pattern string at runtime.
  9. Use build-time transforms with createRegExp

    main

    The most efficient way to use magic-regexp is through its build-time transform. By using createRegExp with helpers, the library can statically compile your expression into a standard RegExp at build time, avoiding runtime overhead.

    Important Constraint: To enable static compilation, you must include all magic-regexp helpers directly within the createRegExp block. You cannot rely on external variables inside the block if you want the expression to be statically compiled.

    const beforeTransform = createRegExp(exactly('foo/test.js').after('bar/'))
    // => gets _compiled_ to
    const afterTransform = /(?<=bar\/)foo\/test\.js/
  10. Use experimental type-level RegExp match and replace

    main

    For experimental type-level support where match() and replace() results are typed based on the input string, import from magic-regexp/further-magic instead of magic-regexp.

    Matching with literal strings

    When matching against a literal string, the result provides precise types for groups and matches.

    Matching with dynamic strings

    When matching against a dynamic string, the result is a union of all possible matches defined by the RegExp structure.

    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"
    
    // `String.replace()` example
    const replaceResult = literalString.replace(
      semverRegExp,
      `minor version "$2" brings many great DX improvements, while patch "$<patch>" fix some bugs and it's`
    )
    // replaceResult // "magic-regexp minor version \"2\" brings many great DX improvements, while patch \"5.beta.1\" fix some bugs and it's just release!"
  11. Create a semver RegExp using groupedAs

    main

    You can build complex regular expressions like semantic versioning (semver) patterns by chaining components and using .groupedAs(name) to create named capture groups.

    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>.+))?/