babel - AST

jingjinge·2025년 3월 10일
0

OpenSource

목록 보기
5/9
post-thumbnail

현대 Javascript 생태계에 있어서 빠지지 않고 등장하는 것이 무엇일까

react 개발이든, nodejs를 이용한 next.js, express.js든 어떠한 코드를 작성을 한다면 결국 js엔진이 읽을 수 있게끔 컴파일 하는 과정이 필요하다.

우리가 컴파일러를 알아야 하는 이유는 무엇일까?

실제 개발하면서 사용한 Bable의 AST 구문 해석 및 순환에 대해 정리해보자 한다.

실제코드


배경

위와 같은 코드가 있다고 하자.

위 코드는 현재 typescript이지만, 트랜스파일링과 관련된 의존성들을 번들링 하기 전에는 위와 같은 모양이지만 번들링을 한다면 어떤 모양이 될까?

나는 위 코드가 18만줄 js 배포하기글에 나온 이유로 번들링을 한 이후에 사용자에게 보내주어야한다.

즉, 파라미터를 통한 어떠한 조작을 할 수 없다. 그리고 어떤 식으로 변환될지 예상할 수 없다.

처음에 나는 아래와 같은 선택을 했다.


가정

JS 문자열 파싱 및 바꿔주기

터미널에서 선택한 값을 이용해, 사용자의 터미널에서 번들링된 JS를 파싱해 사용자에따라다른지역입니다를 찾아서 바꿔주면 되는 것 아닐까?

실제로 구현했을때, 잘 동작했다. 하지만 위와 같은 방식은 문제가 생길 수 있다.

나는 저 함수를 사용자의 터미널 입력을 받아서 region을 넣어주어야한다.

위 코드는 실제로 잘 동작한다.

하지만 아래와 같은 문제가 있다.

단점

  • 취약한 매칭 방식
    • 주석이나 문자열 내에 동일한 텍스트가 있으면 오작동할 수 있음
  • 번들러에 의한 코드 변형
    • 번들링 과정에서 코드가 압축되거나 변형될 수 있어 원하는 패턴을 찾지 못할 수 있음
  • 유지보수의 어려움
    • 코드 구조가 변경되면 매칭 로직도 함께 변경해야함
  • 에러 발생 가능성
    • 정규식 패턴이 예상치 못한 곳에서 매칭되어 코드를 손상시킬 위험이 있음
  • 복잡한 변환 불가능
    • 단순 치환 이상의 복잡한 코드 변환을 수행하기 어려움
region: 사용자에따라다른지역입니다,
  //이렇게 바뀐다면?
region:사용자에따라다른지역입니다,
  //혹은
region:
사용자에따라다른지역입니다
,

위와 같은 상황에서는 정규식에서 요구하는 조건을 만족할 수 없다.

그래서 아래와 같은 방식을 사용했다.


AST를 이용한 구문 파싱

단순 문자열이 아니라 구문자체를 파싱한다면 어떨까? region:사용자에따라다른지역입니다 <- 이 코드 자체를 찾아서 치환해준다면 미래에 고려하지 못할 다른 변수들을 차단할 수 있다.

구문 자체를 해석하는 방법은 AST를 이용하면 된다.

AST란 무엇일까?

AST

function sum(a,b){
  return a+b
}

위와 같은 코드가 있다고 해보자, javascript는 이를 어떤 식으로 해석할까?

위의 JS코드는 내 코드에서는 babel에 의해 아래와 같은 AST(Abstract Syntax Tree)로 변환된다.

이 AST 구문으로 변환하는 과정은 언제언제 쓰일까? 내 코드의 흐름을 읊어보자

  1. TypeScript 코드는 TypeScript 컴파일러(tsc)를 통해 JavaScript로 변환
  2. 이 과정에서 TypeScript 컴파일러는 소스 코드를 AST로 파싱
  3. 타입 체크를 수행한 후, JavaScript 코드를 생성
  4. 변환된 JavaScript 파일은 Webpack, Rollup, Parcel 같은 번들러를 통해 번들링
    • tree shaking, minify, polyfill 추가 등의 작업을 거침
  5. 브라우저의 JavaScript 엔진(V8, SpiderMonkey 등)이 이 JavaScript 코드를 실행할 때도 또 한 번 파싱 과정을 거치며 AST를 생성

AST는 뗄레야 뗄 수 없는 개발자의 필수적인 기본 소양인 셈이다. AST는 코드 주변 어디에나 있다.

그렇다면 나는 이걸 어디에 이용할 수 있을까? Identifier를 순회하면서 updateInfoFunction라는 Identifier안에 parameter가 key가 region이면서 value가 사용자에따라다른지역입니다 인 Identifier가 있다면, 이를 바꿔주면 되지 않을까?


적용

위 코드에서 transformEnv의 파라미터는 아래 두 가지다.

  • AST구문으로 변환할 코드
  • 바꿔줄 코드

parse를 통해 javascript 코드를 AST구문으로 바꿔주고, traverse를 통해 바꿔준 AST 구문을 순회하며 원하는 값을 찾아낸다.

원하는 값을 찾아냈다면 Identifier 자체를 바꿔준 후, 원래 js코드로 바꿔준다.

장점

  1. 정확한 구문 이해
    • 단순 문자열이 아닌 코드의 구조와 의미를 이해하고 변환
  2. 안전한 코드 변환
    • 구문 오류를 방지하고 의도한 대로만 코드를 변경
  3. 형식 무관
    • 공백, 줄바꿈, 주석 등의 형식에 영향을 받지 않음
  4. 유연한 변환 가능
    • 단순 치환부터 복잡한 코드 구조 변경까지 가능
  5. 타입 시스템 활용
    • TypeScript의 경우 타입 정보까지 활용 가능
  6. 확장성
    • 다양한 패턴에 대응할 수 있는 확장 가능한 변환 로직 구현 가능
  7. 유지보수성
    • 코드 구조 변경에도 변환 로직이 강건하게 동작
  8. 디버깅 용이성
    • 오류 발생 시 정확한 위치와 원인 파악 가능
  9. 도구 생태계
    • Babel, TypeScript, ESLint 등 다양한 도구와 호환
      • ESLint나 prettier등은 AST로 구문 변환을 한 후, 위의 로직과 비슷하게 코드를 파악한다.

결론

처음엔 단순한 문자열 파싱이었는데 어떻게 하다 보니 babel과 컴파일러가 어떻게 동작하는지

안정성을 위해서는 어떠한 기술들을 사용할 수 있는지에 대해 많은 것을 알게 되었다.

코드 -> AST구문 -> 코드라는 과정이 어떻게 보면 비효율적일 수 있지만, 중요한 것은 유지보수성이지 않을까 생각한다.

만약 내가 문자열 파싱으로 코드를 작성했다면 저 코드는 지금 나만 알고 있지만, 누군가 나중에 저 함수의 파라미터를 임의로 변경했을때(정말 단순한 스페이스바 한 스푼이어도), 어디서 무엇이 일어나는지, 왜 이게 이렇게 바뀌는지 모를 가능성이 높다.

누구나 내 코드를 만지게 되었을 때, 불필요한 사이드 이펙트를 발생시키지 않을 만한 코드를 작성하는 것이 중요하지 않을까?

0개의 댓글