Published on

ES2024 RegExp v 플래그로 유니코드 오류 해결

Authors

서버 로그, 사용자 입력 검증, 검색 하이라이팅처럼 문자열을 다루는 기능에서 정규식은 빠질 수 없습니다. 그런데 유니코드가 섞인 텍스트(이모지, 조합형 문자, 한글 자모, 악센트 문자 등)를 정규식으로 처리할 때는, u 플래그만으로는 의도와 다르게 매칭되거나 SyntaxError: Invalid regular expression 같은 오류를 만나기 쉽습니다.

ES2024는 이 문제를 정면으로 다루기 위해 RegExp에 v 플래그를 추가했습니다. 흔히 Unicode sets 라고 부르며, 문자 클래스([]) 내부에서 유니코드 속성, 집합 연산(교집합/차집합), 더 명확한 이스케이프 규칙을 제공해 “유니코드 정규식이 애매해서 생기는 버그”를 크게 줄여줍니다.

이 글에서는 v 플래그가 해결하는 대표적인 유니코드 오류 패턴과, 실무에서 점진적으로 마이그레이션하는 방법을 코드로 설명합니다.

u 플래그만으로는 부족했나

u 플래그는 정규식 엔진이 유니코드 코드 포인트 단위로 해석하도록 바꿔줍니다. 특히 보조 평면(astral plane) 문자를 다루는 데 중요합니다. 하지만 실무에서 부딪히는 문제는 크게 두 가지였습니다.

  1. 문자 클래스에서 “유니코드 속성 + 범위/나열”을 조합하기 어렵다
  2. 유니코드 문자 집합을 정확히 표현하려면 복잡한 패턴이 필요하다

예를 들어 “라틴 문자이면서 숫자는 제외” 같은 조건을 u만으로는 우아하게 표현하기 힘들었습니다. \p{…} 유니코드 속성은 쓸 수 있어도, 문자 클래스 내부에서 집합 연산을 못 하니 결국 부정 클래스([^…])나 복잡한 대체(|)로 돌아가게 됩니다.

또 하나는 이스케이프 규칙의 애매함입니다. 어떤 문자가 문자 클래스에서 특별한 의미를 갖는지, -] 같은 문자를 어떻게 안전하게 포함할지, 그리고 \q{...} 같은 “문자열 리터럴 집합”이 없어 여러 글자 시퀀스를 집합처럼 다루기 힘든 점 등이 쌓여 유지보수성이 크게 떨어졌습니다.

ES2024 v 플래그(Unicode sets) 핵심 개념

v 플래그는 정규식의 유니코드 모드를 강화하고, 특히 문자 클래스 내부 표현력을 확장합니다.

핵심은 다음입니다.

  • 유니코드 속성 이스케이프: \p{…} / \P{…} 를 더 강력하게 활용
  • 집합 연산 지원: 교집합 &&, 차집합 -- 등을 통해 “정확한 문자 집합” 구성
  • 문자열 리터럴 집합: \q{...} 로 여러 코드 포인트로 이루어진 시퀀스를 집합처럼 다룸

주의할 점은 v 플래그는 기존 u와 완전히 동일한 동작이 아니라는 것입니다. “더 엄격하고 명시적”인 방향이라, 기존에 애매하게 동작하던 패턴이 v에서 오류로 바뀌는 경우도 있습니다. 하지만 그게 바로 장점입니다. 런타임에서 조용히 틀리는 것보다, 빌드/테스트에서 바로 깨지는 편이 낫습니다.

v 플래그로 해결되는 대표 오류 1: 유니코드 속성 + 제외 조건

요구사항: “문자(letter)만 허용하되, 공백/숫자/기호는 제외”

기존에는 다음처럼 단순히 \p{L} 만 쓰면 되지만, 실제 입력에서는 결합 문자나 특정 스크립트 처리에서 예외가 생깁니다.

// 단순 버전: letter만
const reU = /^\p{L}+$/u;

console.log(reU.test("cafe")); // true
console.log(reU.test("카페")); // true
console.log(reU.test("café")); // true (조합/정규화 상태에 따라 달라질 수 있음)

여기서 “라틴 문자만 허용하고 싶다” 또는 “마크(결합 악센트)는 허용하지만 특정 기호는 제외” 같은 조건이 붙으면 u만으로는 표현이 급격히 어려워집니다.

v에서는 집합 연산으로 훨씬 명확하게 작성할 수 있습니다.

// 라틴 스크립트이면서(letter) 숫자는 제외
// \p{Script=Latin}은 라틴 스크립트 문자 집합
// \p{Number}는 숫자
const reV = /^[\p{Script=Latin}&&\p{Letter}--\p{Number}]+$/v;

console.log(reV.test("cafe")); // true
console.log(reV.test("café")); // true
console.log(reV.test("cafe2")); // false
console.log(reV.test("카페")); // false

포인트는 “허용 집합”을 만든 뒤 “제외 집합”을 빼는 방식이 가능해졌다는 점입니다. 이 패턴은 입력 검증에서 특히 강력합니다.

대표 오류 2: 이모지/보조 평면 문자 처리의 함정

이모지는 코드 포인트가 U+1F600 같은 보조 평면에 있는 경우가 많습니다. u 플래그가 없으면 정규식이 UTF-16 코드 유닛 기준으로 동작해, 한 글자를 둘로 쪼개 매칭하는 문제가 생깁니다.

const s = "😀";

console.log(/^.$/.test(s));   // false (u 없음: 2 코드 유닛)
console.log(/^.$/u.test(s));  // true

그런데 실무에서는 “이모지 전체를 잡고 싶다” 또는 “이모지 중 특정 범주만 제외” 같은 요구가 생깁니다. v에서는 유니코드 속성 기반으로 더 안전한 집합을 구성할 수 있습니다.

// 이모지로 분류되는 문자를 매칭 (환경에 따라 속성 지원 범위는 다를 수 있음)
const emoji = /^\p{Emoji}+$/v;

console.log(emoji.test("😀😀")); // true
console.log(emoji.test("A😀"));  // false

유니코드 속성은 엔진이 유니코드 데이터 테이블을 기반으로 제공하므로, 하드코딩된 범위(\u{1F300}-\u{1FAFF} 같은)보다 유지보수성이 좋습니다.

대표 오류 3: 문자 클래스에서 안전한 “문자 그대로” 표현

정규식에서 [ ] - \ 같은 문자는 문자 클래스에서 특별한 의미가 있습니다. 기존에는 위치를 조정하거나 복잡한 이스케이프를 써야 했고, 팀원이 수정하다가 쉽게 깨졌습니다.

v는 문자 클래스 표현을 더 엄격하게 다루는 대신, 집합/리터럴 기능으로 “의도를 드러내는” 방식이 가능합니다.

예를 들어 -를 포함한 특정 구분자 집합을 만들고 싶다면:

// 하이픈, 언더스코어, 점을 허용
const sep = /^[\p{Letter}\p{Number}[\-_.]]+$/v;

console.log(sep.test("user-name_01")); // true
console.log(sep.test("user/name"));    // false

여기서 핵심은 v에서는 문자 클래스 안에 또 다른 클래스처럼 집합을 구성하는 표현이 가능해져, 복잡한 이스케이프 규칙에 덜 의존하게 된다는 점입니다.

\q{...}: 여러 글자 시퀀스를 “집합”으로 다루기

기존 정규식에서 가장 번거로운 것 중 하나가 “여러 코드 포인트로 이루어진 시퀀스”를 문자 클래스처럼 다루는 일이었습니다. 예를 들어 가족 이모지처럼 ZWJ(Zero Width Joiner)로 이어진 시퀀스는 단일 코드 포인트가 아닙니다.

v\q{...}는 이런 시퀀스를 집합 항목으로 취급할 수 있게 해줍니다.

// 예시: 특정 시퀀스를 포함하는지 검사
// (환경에 따라 지원 여부가 다를 수 있으니 런타임 체크 권장)
const re = /[\q{👨‍👩‍👧‍👦}\q{🏳️‍🌈}]/v;

console.log(re.test("가족: 👨‍👩‍👧‍👦")); // true
console.log(re.test("무지개: 🏳️‍🌈"));     // true

이 기능은 “금칙어/치환 규칙” 같은 텍스트 정책을 다룰 때 특히 유용합니다. 단, 유니코드 시퀀스는 정규화/표현이 다양할 수 있어, 데이터 소스에서 어떤 형태로 들어오는지(Variation Selector 포함 여부 등)를 함께 고려해야 합니다.

런타임 호환성: Node.js와 브라우저에서 어떻게 확인하나

v 플래그는 ES2024 기능이라, 실행 환경이 이를 지원해야 합니다. 프런트엔드에서는 사용자 브라우저 버전, 백엔드에서는 Node.js 버전이 관건입니다.

가장 안전한 방법은 “기능 탐지”입니다.

export function supportsRegexpV() {
  try {
    // 문법 자체가 파싱되면 통과
    new RegExp("[a]", "v");
    return true;
  } catch {
    return false;
  }
}

console.log("RegExp v supported:", supportsRegexpV());

지원하지 않는 환경에서는 폴백 전략이 필요합니다.

  • 서버(Node.js)라면: 런타임 업그레이드가 최선
  • 클라이언트라면: v를 쓰는 경로를 분기하거나, 빌드 타깃을 조정

정규식은 폴리필이 어렵습니다. 트랜스파일로 완벽히 대체하기 힘들기 때문에, 가능하면 “정규식 엔진이 있는 런타임”을 올리는 게 비용 대비 효과가 큽니다.

실무 마이그레이션 전략

1) “유니코드 입력 검증”부터 v로 바꾸기

가장 먼저 효과를 보는 곳은 입력 검증입니다. 예를 들어 닉네임 정책이 다음과 같다고 합시다.

  • 모든 문자 중 LetterNumber만 허용
  • 공백 불가
  • 이모지 불가

v로는 집합 연산으로 명확히 작성할 수 있습니다.

// Letter 또는 Number에서 Emoji를 제외
const nickname = /^[\p{Letter}\p{Number}--\p{Emoji}]{2,20}$/v;

console.log(nickname.test("홍길동"));     // true
console.log(nickname.test("user01"));    // true
console.log(nickname.test("hi😀"));      // false
console.log(nickname.test("two words")); // false

이런 정책은 “왜 이 문자는 통과했고, 왜 저 문자는 막혔지?” 같은 이슈가 자주 생기는데, v는 규칙을 읽기 쉽게 만들어 QA/보안 리뷰에도 유리합니다.

2) 테스트 케이스를 유니코드 중심으로 보강

v 도입 시에는 정규식 자체보다 “테스트 데이터”가 더 중요합니다.

  • NFC/NFD 정규화 차이(예: é가 단일 코드 포인트인지, e + 결합 악센트인지)
  • ZWJ 시퀀스 이모지
  • 한글 자모 분리 입력

Node.js에서는 String.prototype.normalize()로 정규화 테스트를 쉽게 추가할 수 있습니다.

const nfc = "café";
const nfd = nfc.normalize("NFD");

console.log(nfc === nfd); // false

const re = /^\p{Letter}+$/v;
console.log(re.test(nfc)); // true
console.log(re.test(nfd)); // true (정책에 따라 다르게 처리하고 싶을 수도 있음)

정규화까지 정책 범위에 포함할지(예: 저장 시 NFC로 강제)도 함께 결정해야 “유니코드 오류”가 재발하지 않습니다.

3) CI에서 런타임 버전 고정

v 플래그는 “지원 여부”가 곧 빌드 안정성과 직결됩니다. 로컬에서는 되는데 CI에서 깨지는 전형적인 상황이 생길 수 있습니다. 이럴 때는 CI에서 Node 버전을 명시하고, 캐시/락파일을 함께 관리하는 것이 중요합니다. 관련해서는 GitHub Actions 캐시 미적중 원인 - key·restore-keys·락파일도 함께 참고하면, 환경 차이로 인한 재현 불가 이슈를 줄이는 데 도움이 됩니다.

자주 묻는 함정과 체크리스트

  • v는 “더 많은 것을 허용”하는 플래그가 아니라, 더 정확한 유니코드 집합 표현을 위한 플래그입니다. 기존 패턴이 우연히 동작하던 부분이 v에서 오류로 바뀔 수 있습니다.
  • 유니코드 속성(\p{…})은 엔진의 유니코드 데이터 버전에 영향을 받습니다. 런타임 업그레이드 시 매칭 결과가 달라질 수 있으니, 입력 검증/보안 필터는 회귀 테스트를 꼭 두세요.
  • 프런트엔드에서 v를 쓸 경우, 구형 브라우저에서는 문법 파싱 단계에서 바로 실패할 수 있습니다. 위의 기능 탐지로 분기하거나, 해당 기능을 서버로 옮기는 것도 방법입니다.

결론

ES2024 RegExp v 플래그는 “유니코드 때문에 정규식이 자꾸 틀린다/깨진다”는 오래된 문제를 실질적으로 해결해 줍니다. 특히 문자 클래스 내부에서 속성과 집합 연산을 조합할 수 있게 되면서, 입력 검증과 텍스트 정책 로직을 훨씬 명확하고 안전하게 작성할 수 있습니다.

도입 우선순위는 보통 다음이 좋습니다.

  1. 사용자 입력 검증(닉네임, 검색어, 태그)에서 v 적용
  2. 유니코드 중심 테스트 케이스 보강
  3. CI/런타임 버전 고정 후 점진 확장

정규식은 작게 보이지만, 유니코드에서는 작은 차이가 곧 장애로 이어집니다. v 플래그를 “표현력 개선”이 아니라 “오류 예방 장치”로 보고 접근하면, 마이그레이션의 비용 대비 효과가 큽니다.