Babel #5 플러그인 제작

eunji hwang·2020년 7월 15일
2

Babel & Webpack

목록 보기
6/7

AST Explorer

플로그인을 제작하기 위해선 AST 구조를 알아야 한다.

AST Explorer 에서 AST가 뭔지 알아보자.

에디터에 const myNamy= hwang + 'eunji';를 입력했을때 AST Explorer에 출력된 json의 모습이다.

  • type : 각 노드의 타입을 나타낸다
    • Program : 루트의 타입
    • VariableDeclaration : 변수 타입
    • FunctionDeclaration : 함수 타입
    • Identifier : 개발자가 만든 값(변수, 함수 등)
    • BinaryExpression : 사칙연산
    • Literal : 문자열
    • 등...
  • declarations : 선언부, 변수명, 초기값 등이 위치

👻

보기는 했으나 이걸 어떻게 활용하는지..하핳

babel plugin의 기본구조

module.exports = function ({ types: t }) {
  const node = t.BinaryExpression('+', t.Identifier('a'), t.Indentifier('b'));
  console.log('isBinaryEspression:', t.inBinaryExpression(node));
  return {};
};
  • a+b를 하는 코드 작성
  • types를 매겨변수로 하는 함수를 내보냄
  • types 매개변수를 이용해 AST 노드를 생성할 수 있다.
  • inBinaryExpression()을 통해 타입검사를 진행했다.
  • return 값이 없기때문에 아무일도 일어나지 않는다.

반환 값 넣기

module.exports = function ({ types: t }) {
  return {
    visitor: {
      Identifier(path) {
        console.log('Identifier name:', path.node.name);
      },
      BinaryExpression(path) {
        console.log('BinaryExpression operator: ', path.node.operator);
      },
    },
  };
};
  • visitor 객체 내부에서 노트의 타입명으로 함수를 작성한다.(예 :Identifier())
  • 변수가 작성되면 Identifier()-개발자가 만든 값(변수, 함수 등)- 함수가 호출되며, 변수가 쓰인 만큼 호출
  • path.node.name 변수명을 나타내며, const myName= hwang + 'eunji에서 myName, hwang에 해당
  • 연산자를 작성하면 BinaryExpression()함수가 실행
  • path.node.operatorconst myName= hwang + 'eunji에서+연산자에 해당
  • const myName= hwang + 'eunji' 가 입력됬을 때
    • Identifier() 는 2번 호출
    • BinaryExpression() 1번 호출

콘솔로그 제거 plugin

콘솔로그를 제거하는 플러그인을 제작 하기 위해서는 먼저 콘솔로그의 AST를 이해해야 한다.

AST Explorer 사이트에 방문하여 console.log()의 AST를 확인하자

// console.log('hi') 의 JSON 데이터
// 필요없는 옵션 제거함 : start, end)
{
  "type": "Program",
  "body": [
    {
      "type": "ExpressionStatement",
      "expression": {
        "type": "CallExpression",
        "callee": {
          "type": "MemberExpression",
          "object": {
            "type": "Identifier",
            "name": "console"
          },
          "property": {
            "type": "Identifier",
            "name": "log"
          },
          "computed": false
        },
        "arguments": [
          {
            "type": "Literal",
            "value": "hi",
            "raw": "'hi'"
          }]}}
  ],
  "sourceType": "module"
}

콘솔로그의 AST

- [root]:[type]: Program // 최상위
  - [body]
    - [첫 요소 : `console.log('hi)'`]
      - [type] : ExpressionStatement // 1
      - [expression] : CallExpression // 2
        - [callee]
          - [type] : MemberExpression // 3
          - [object] // 객체 타입과 이름
            - [type] : Identifier  // 4
            - [name] : console
          - [property] // 메서드 or 속성이 위치
            - [type] : Identifier
            - [name] : log
         - [args]
           - [1st args : `('hi')`] // 첫번째 인자에 대한 객체
             - [type] : Literal // 5
             - [value] : 'hi'// 입력값이 표시됨
             - [raw] : 'hi'
 

콘솔로그의 Types

  1. ExpressionStatement : 콘솔로그의 최상위 노드
  2. CallExpression : 함수 또는 메서드를 호출하는 코드의 type
  3. MemberExpression : 메서드 호출 시 type
  4. Identifier : 변수명과 같이 선언되기에 Identifier type
  5. Literal : 문자열이 주어졌음으로 literal type

콘솔로그 제거 플로그인 코드작성

module.exports = function ({ types: t }) {
  return {
    visitor: {
      ExpressionStatement(path) {
        // ExpressionStatement type을 갖을때 호출
        if (t.isCallExpression(path.node.expression)) {
          if (t.isMemberExpression(path.node.expression.callee)) {
            // 노드체이닝이 넘 길어서 변수에 넣고 다시 체이닝
            const calleeNode = path.node.expression.callee;
            if (
              calleeNode.object.name === 'console' &&
              calleeNode.property.name === 'log'
            ) {
              path.remove(); // 조건에 맞으면 코드 최상위 제거! === console.log() 제거하게됨
}}}},},};};
  • ExpressionStatement(path) 콘솔로그 최상위 Types를 함수화 하여 작성, 매개변수를 지정해 노드에 접근하도록 한다.
  • isCallExpression(node) : is타입명(노드)을 통해 인자로 지정한 node가 해당 type을 인지 확인!
  • isMemberEspression(node) : 맞찬가지로 인자로 넣은 노드가 해당 type인지 확인
  • 최하위 노드까지 접근했을때 console 객체에 log 메서드가 쓰였다면 해당 요소의 최상위 노드 제거

👻

여기까지 실습하면서 AST 구조 들여다 보다보니 조금, 아주 쪼오오오오오오금 이해가 된다.

직접 만든 플러그인 사용하기

직접 만들 플러그인을 사용하기 위해서는 바벨 설정이 필요하다. 전역 설정파일(babel.confil.js)을 만들고 다음 코드를 작성하자.

// babel.config.js

// plugins 변수에 [플러그인1, 플러그인2, ... 그리고 내가만든 플러그인]
// 여러게 플러그인을 배열로 담기
const plugins = ['./plugins/consolelog-remover.js']
module.exports = { plugins }

콘솔로그가 있는 코드를 작성하고 바벨을 사용해 컴파일하여 보자~~아!

// sample-consolelog-remove.js

console.log('콘솔로그를 지워볼깨');
const text = 'run console.log() remove-plugin';
console.log(text);

npx babel 경로/파일명.js 명령 실행 후 콘솔출력

const text = 'run console.log() remove-plugin';

책보고 따라한 실습이지만, 감격....멋지당!!!
분명 잘 작성했는데 에러가나거나 한다면. 노드 체이닝에 문제가 없는지 다시한번 확인하자. 나는 if (t.isMemberExpression(path.node.expression.callee)){} 에서 인자로 넣은 노드에서 expression을 빼먹어서 한참을 다시 봤다! 노드를 잘 보기!

콘솔로그 추가 plugin

콘솔로그 제거를 해봤으니 콘솔로그 추가하는 플러그인을 작성해보자.

규칙 : 'on'으로 시작하는 함수에 콘솔로그를 추가

먼저 AST Explorer 사이트에 방문하여 onFunctionName() 로직을 작성해 노드를 확인해보자.

// AST 구조 확인하기 : JSON
// 너무 길어서 필요없는 옵션은 제거
{
  "type": "Program",
  "body": [
    {
      "type": "FunctionDeclaration",
      "id": {
        "type": "Identifier",
        "name": "onFunc"
      },
      "body": {
        "type": "BlockStatement",
        "body": [
          {
            "type": "VariableDeclaration", // const 변수 노드
            "kind": "const"
          },
          {
            "type": "ExpressionStatement",  // console.log 노드
            "expression": {
              "type": "CallExpression",
              "callee": {
                "type": "MemberExpression",
                "object": {
                  "type": "Identifier",
                  "name": "console"
                },
                "property": {
                  "type": "Identifier",
                  "name": "log"
           // .. 이하 닫기
          {
            "type": "ReturnStatement", // return 노드
          } 
            // .. 이하 닫기

콘솔로그 플러그인 추가 작성

module.exports = function ({ types: t }) {
  return {
    visitor: {
      FunctionDeclaration(path) {
        if (path.node.id.name.substr(0, 2) === 'on') {
          path
            .get('body')
            .unshiftContainer(
              'body',
              t.expressionStatement(
                t.callExpression(
                  t.memberExpression(
                    t.identifier('console'),
                    t.identifier('log')
                  ),
                  [t.stringLiteral(`call ${path.node.id.name}`)]
                )));}},},};};

실행결과

바벨적용 전

function onFunc1() {
  return '일반함수 타입: FunctionDeclaration';
}

const onFunc3 = function () {
  return '익명함수 변수지정: VariableDeclaration';
};

const onFunc4 = () => {
  return '화살표함수 타입: VariableDeclaration';
};

바벨 적용 후

function onFunc1() {
  console.log("call onFunc1");
  return '일반함수 타입: FunctionDeclaration';
}

const onFunc3 = function () {
  return '익명함수 변수지정: VariableDeclaration';
};

const onFunc4 = function () {
  return '화살표함수 타입: VariableDeclaration';
};

AST Explorer에서 확인해보면 알겠지만, 함수 선언 방식마다 타입종류가 다르다 변수형으로 function 함수명 방식 이외의 함수 선언방식은 변수선언 타입 VariableDeclaration 로 적용된다.

  • 일반 함수 선언방식 : function 함수명(){} FunctionDevlaration type
  • 화살표함수, 익명함수 : ()=>{}, function(){} VariableDeclaration type

참고자료

실전 리액트 프로그래밍/이재승 저
자바스크립트 개발자를 위한 AST(번역)
바벨-핸드북 깃헙 : 한국어버전 있음
바벨-핸드북 깃헙 : 플러그인 만들기 : 한국어버전 있음

profile
TIL 기록 블로그 :: 문제가 있는 글엔 댓글 부탁드려요!

0개의 댓글