Workaround for Lookbehind Regex in Safari

In this post we will discuss:

  • What are Lookbehind Regex
  • Browser Compatibility of Lookbehind Regex
  • Alternative ways to use them so that it works in all browsers

What are Lookbehind regex

At times, we need to match a pattern only if it is followed or preceded by another pattern.

For eg. in case of URL which contains the organization information:

/organizations/:org/dashboard

Here, :org is dynamic name of the organization which can be of following pattern:

/[a-z0-9]+/

We want to match all URLs which match the pattern for

/organizations/:org/*

But there are also URLs such as which we don't want to match.

/users/:slug/*

Where the slug is also of same pattern as /[a-z0-9]/.

So we want to make sure that we match the organizations URLs only if they are preceded by organizations word.

There is a way to write such regular expressions using Lookbehind regex which checks if a pattern is preceded by specific pattern.

The syntax is:

  • Positive lookbehind: (?<=Y)X, matches X, but only if there’s Y before it.
  • Negative lookbehind: (?<!Y)X, matches X, but only if there’s no Y before it.

In our case:

const RE = /(?<=organizations\/[a-z0-9]+\/).+$/;

We use this as follows:

trimmedPath = path.replace(RE, '')

Which will basically replace anything that followed our pattern organizations/:org:

const RE = /(?<=organizations\/[a-z0-9]+\/).+$/;
path = "/organizations/last9/345345435"
path.replace(RE, '') // => '/organizations/last9/'

Browser Compatibility

Lookbehind regex are very powerful but they are not supported in all browsers. Non V8 browsers such as Safari don't support them.

Let's run the same example in Safari:

const RE = /(?<=organizations\/[a-z0-9]+\/).+$/;
SyntaxError: Invalid regular expression: invalid group specifier name
Lookbehind in JS regular expressions | Can I use... Support tables for HTML5, CSS3, etc

Alternative to use Lookbehind Regex in Safari

We can relook at our usage and try to avoid the lookbehind regex and just use the capture groups.

const RE = /(organizations\/[a-z0-9]+\/)(.+)$/;
path = "/organizations/last9/345345435"
path.match(RE) // Two matches => [organizations/last9/, 345345435]

In this case, we capture two groups, the first one where organizations/:org/ gets captured and then in second everything else.

We want to keep organizations/:org/ and drop everything else. This can be achieved with following:

const RE = /(organizations\/[a-z0-9]+\/)(.+)$/;
path = "/organizations/last9/345345435"
path.replace(RE, "$1") // => /organizations/last9/

This basically keeps the first match and drops everything else. Most importantly, this works on Safari as well!


P.S. If you want detailed overview on Lookbehind and its friend Lookahead regex, this is an excellent post - https://javascript.info/regexp-lookahead-lookbehind