Babel 플러그인을 작성하여AST의 마력을 보여줍니다.

추상적인 문법 트리를 들었을 때 머릿속에서 가장 먼저 떠오르는 것은 무엇입니까?
컴파일러와 관련이 있습니까?복잡한 트리 조작?비트 조종?🤔
내 생애 초에 이AST는 복잡한 용어인 것 같았는데, 그 안에는 저급 컴파일러와 Transpiler가 장식되어 있었다.

💡 모티프


이 블로그를 쓰는 동기는 모든 사람들이 추상적인 문법 트리가 무엇인지, 그리고 우리가 일상적으로 사용하는 대부분의 도구에서 어떻게 중요한 역할을 발휘하는지 쉽게 이해할 수 있도록 하는 것이다.
바벨, 웹 패키지,parcel,eslint,codemods, css 해상도, js의 css를 막론하고 모든 도구는AST의 마력을 사용하여 우리의 코드를 조작하고 다른 내용으로 변환합니다.
이 글에서 우리는 이 마법을 풀고 그 과정에서 아주 간단한 바베타 플러그인을 만드는 것을 배울 것이다.그래.🎉

🤔 AST란?


모든 새로운 개념과 마찬가지로 우리는 구체적인 정의로부터 시작할 것이다.
위키백과에 의하면

An abstract syntax tree is a tree representation of source code written in a programming language. Each node of the tree denotes a construct occurring in the source code.


이 점을 이해하기 위해서, 우리가 편집기에서 간단한 코드를 한 줄 썼다고 가정하자
 const a = 5 + 3;
이것은 매우 간단한 변수 부수와 두 수의 덧셈이다.
이 간단한 조작은 TokenizationParsing의 과정을 거쳐야 한다.

🕹️ 표기화(어휘 분석)


태그 또는 구문 분석은 함수가 코드를 문자열로 읽고 태그 목록으로 분할하는 단계입니다.
간단하게 보기 위해서, 모든 영패에 다음과 같은 인터페이스가 있다고 가정하자
interface Token {
 type: string,
 value: string
}
우리의 코드는 어법 분석의 과정을 거쳤고 표기로 분해되었다.

🧵 구문 분석(구문 분석)


어법 후 분석은 우리에게 일련의 표기를 제공했다. 우리는 이를 아스트 해석기(babylon, acorn 또는 espree)에 전달하고 후자는 그들 간의 의존 관계를 구축하여 아스트 노드 트리로 전환시킨다.

우리가 작성한 아주 간단한 코드는 하나의 노드 트리로 변환되어 추상적인 문법 트리라고 부른다.
전체 트리는 다음과 같은 방식으로 json으로 표시됩니다
{
    "type": "VariableDeclaration",
    "declarations": [{
        "type": "VariableDeclarator",
        "id": {
            "type": "Identifier",
            "name": "a"
        },
        "init": {
            "type": "BinaryExpression",
            "left": {
                "type": "Literal",
                "value": 5
            },
            "operator": "+",
            "right": {
                "type": "Literal",
                "value": 3
            }
        }
    }],
    "kind": "const"
}
이 json 대상에서 type 라는 인자를 발견했습니다.우리는AST 노드 유형이라고 부른다.
다양한 종류의AST 노드가 존재하는데babel에 대해 다음과 같은 내용을 참고할 수 있습니다
Babel AST Node Types
espree 해상도(eslint에서 사용하는 해상도)에 대해 참고할 수 있습니다.
Eslint AST Node Types
Babel, 웹 패키지, Parcel 및 모든 도구는 일반적인 방법을 사용합니다.
그들은 먼저 우리의 코드를 AST 트리로 변환한 다음에 변환(추가, 편집, 업데이트, 삭제)을 적용하여 이 변환으로 새로운 트리를 만들고 인간이 읽을 수 있는 코드로 변환한다.

특정 코드 줄의AST 트리가 형식이 어떤 모양인지 이해하기 위해 시종일관 검사하는 것을 권장합니다AST Explorer
현재, 우리는 어떠한 시간도 낭비하지 않는 상황에서, 우리의 첫 번째 바벨 플러그인을 작성할 것이다.
이 플러그인은 코드에 포함될 수 있는 모든 디버거 문장을 삭제합니다.

📕 Babel 플러그인 - 디버거 제거


환매 협의의 여러 위치에서 아래 코드를 사용하는 것을 고려하다
function test() {
   const a = 5;
   debugger;
   const b = 6;
}
분명히 우리는 이 디버거 문구가 우리의 생산 응용 프로그램에 나타나기를 원하지 않는다.
(주의: 현실 세계의 응용 프로그램에서 우리는 이러한 오류를 피하는 데 도움을 줄 수 있는 연결 프로그램이나 배치 파이프 절차가 있을 것이다. 그러나 이 예시를 위해 우리는 어떠한 배치 파이프도 없다고 가정하자.)
그래서 우리는 같은 일을 하기 위해 바베타 플러그인을 썼다.

babel 플러그인 작성


첫 번째 단계: 우리가 겨냥할 AST 노드의 유형을 확정한다.AST 자원 관리자로 돌아가서 두 번째 줄을 누르면 노드 유형이 노란색으로 강조되어 있음을 알 수 있습니다. 이것은 DebuggerStatement를 목표로 하는 AST 노드를 표시합니다.

2단계: 편집기를 시작하고 새 파일을 만듭니다.그것을 removeDebugger.js 라고 명명합시다.이것은 우리의 플러그인 파일이 될 것입니다.
지금부터 우리가 작성한 모든 바벨 플러그인은 하나의 공통된 모델을 따를 것이다
module.exports = function(babel) {
  return {
    name: "remove-debugger-plugin", // this is optional
    visitor: {
    }
  };
};
키가 포함된 다른 중첩 대상을 되돌려줍니다. visitor방문자 모드 때문에visitor라고 명명됩니다.
3단계: 우리가 원하는 노드 유형은 DebuggerStatement인 것으로 알고 있습니다.
그래서 저희 코드가 지금 이렇습니다.
module.exports = function(babel) {
  return {
    name: "remove-debugger-plugin", // this is optional
    visitor: {
      DebuggerStatement: function(path) {
      }
    }
  };
};

우리가 겨냥하고자 하는 모든 노드는 방문자의 대상 중의 키가 되어야 한다.
4단계: 현재 이 바벨 플러그인의 유일한 단계는 debugger 문장 노드를 삭제하는 것입니다. 이렇게 합니다.
module.exports = function(babel) {
  return {
    name: "remove-debugger-plugin", // this is optional
    visitor: {
      DebuggerStatement: function(path) {
         path.remove();
      }
    }
  };
};

내 친구는 우리의 첫 번째 바벨 플러그인이다.
이 바벨 플러그인은 노드를 제거해서AST를 조작하는 방법을 설명합니다.
다음 플러그인은 기존 노드를 편집하고 다른 내용으로 전환하는 방법을 배울 것입니다

📕 Babel 플러그인 - 알림 콘솔


여기서 우리는 각각alert문장을 하나console.warn문장으로 전환할 것이다.
그래서 저희가 바꾼 코드가 이렇게 보였으면 좋겠어요.
function test() {
  const a = 5;
  alert(a);
}
우리는 그것을
function test() {
  const a = 5;
  console.warn(a);
}
첫 번째 단계: 우리가 겨냥할 AST 노드의 유형을 확정한다.AST 자원 관리자로 가서 붙여넣은 from 코드를 복사하고 alert 를 누르십시오.오른쪽 노드 유형이 강조표시됩니다.우리는 현재 목표 노드의 유형이 CallExpression라고 불리는 것을 보았다.

그래서 모든 함수 호출은 하나의 함수CallExpression이고 대상에 대한 모든 함수 호출은 하나의 함수MemberExpression이다.그래서alertCallExpression,console.warnMemberExpression이었다.
우리의 예에서, MemberExpression에는 항상 하나의 대상 (컨트롤러) 과 하나의 속성 (경고) 이 있다.
2단계: 편집기를 다시 시작하고 새 파일을 만듭니다.그것을 convertAlertToConsole.js 라고 명명합시다.
우리가 프레임워크 코드의 플러그인을 사용하기 전처럼
module.exports = function(babel) {
  const t = babel.types;
  return {
    name: "convert-alert-to-console", // this is optional
    visitor: {
    }
  };
};
3단계: 우리가 겨냥해야 할 노드가 aCallExpression라는 것을 알았으니 코드를 작성하자.
module.exports = function(babel) {
  const t = babel.types;
  return {
    name: "convert-alert-to-console", // this is optional
    visitor: {
      CallExpression: function(path)
      }
    }
  };
};
4단계: 우리는 모든 다른 함수 호출을 목표로 하지 않기 때문에if조건을 설정하고 이름alert만 목표로 하는 호출 표현식을 목표로 지정합니다
module.exports = function(babel) {
  const t = babel.types;
  return {
    name: "convert-alert-to-console", // this is optional
    visitor: {
      CallExpression: function(path) {
        if (path.node.callee.name === "alert") {
        }
      }
    }
  };
};
이제 유일하게 남은 부분은 그것을 무엇으로 대체할지 알아내는 것이다.
5단계:AST 브라우저로 돌아가서 이번to 코드를 복사하고 컨트롤러를 클릭합니다.warn은 모든 함수 호출이 호출 표현식이기 때문에 다른 호출 표현식으로 바꿔야 한다고 알려 줍니다. 그러나 이것은 object property function call이기 때문에 호출 표현식이 필요합니다. 그 중에는 호출된 구성원 표현식이 포함되어 있습니다.

 module.exports = function(babel) {
  const t = babel.types;
  return {
    name: "convert-alert-to-console", // this is optional
    visitor: {
      CallExpression: function(path) {
        if (path.node.callee.name === "alert") {
          const args = path.node.arguments;
          path.replaceWith(
            t.callExpression(
              t.memberExpression(t.identifier("console"), t.identifier("warn")),
              args
            )
          );
        }
      }
    }
  };
};
이렇게저희가 두 번째 플러그인도 만들었어요.🥳. 이거 쉽지 않아요?🤗

📕 보상 플러그인 - react 프로그램에서 데이터 테스트 id 삭제


본고에서, 우리는react 프로그램에서 각각의 data-test-id 속성을 삭제할 것이다.data-test-id는 일반적으로 dev env에서만 필요하기 때문에, 우리의 플러그인은 안전하게 제품 패키지에서 삭제할 수 있습니다.
그래서 저희가 바꾼 코드가 이렇게 보였으면 좋겠어요.
import React from "react";

const App = () => {
  return (
    <div data-test-id="test">Hello World</div>
  );
}
우리는 그것을
import React from "react";

const App = () => {
  return <div>Hello World</div>;
};
첫 번째 단계: 우리가 겨냥할 AST 노드의 유형을 확정한다.AST 자원 관리자로 가서 붙여넣은 from 코드를 복사하고 data-test-id 를 누르십시오.오른쪽 노드 유형이 강조표시됩니다.우리는 현재 목표 노드의 유형이 JSXAttribute라고 불리는 것을 보았다.

2단계: 편집기를 시작하고 새 파일을 만듭니다.그것을 removeDataAttribute.js 라고 명명합시다.
우리가 프레임워크 코드의 플러그인을 사용하기 전처럼
module.exports = function(babel) {
  return {
    name: "remove-date-test-id", // this is optional
    visitor: {
    }
  };
};
3단계: 우리가 겨냥해야 할 노드가 aJSXAttribute라는 것을 알았으니 코드를 작성하자.
그래서 저희 코드가 지금 이렇습니다.
module.exports = function(babel) {
  return {
    name: "remove-date-test-id", // this is optional
    visitor: {
      JSXAttribute: function(path) {
      }
    }
  };
};

4단계: 현재 이 babel 플러그인의 유일한 단계는 이 jsx 속성 노드를 삭제하는 것입니다. 우리는 이렇게 합니다.
module.exports = function(babel) {
  return {
    name: "remove-date-test-id", // this is optional
    visitor: {
      JSXAttribute: function(path) {
         if(path.node.name.name === "data-test-id") {
           path.remove();
         }
      }
    }
  };
};

이렇게저희가 또 다른 플러그인을 썼어요.🥳.
Github 재구매 계약: https://github.com/vivek12345/webcamp-zagreb-demo

🍬 결론


나는AST가 복잡하지 않다는 것을 이해하는데 도움이 되기를 바란다. 우리 모두는 링크 플러그인을 만들거나 바벨 플러그인을 작성하거나 코드mods를 사용하여 대규모 재구성을 해서 우리의 개발 도구 생태계를 개선할 수 있기를 바란다.만약 당신이 기분이 있다면, js 라이브러리에서 css를 작성할 수도 있습니다.

🔗 도구책


  • Leveling up parsing game 작성자: Vaidehi Joshi

  • AST explorer저자: 펠릭스 클린
  • Babel handbook
  • Step by step guide to write a babel transformation

  • 좋은 웹페이지 즐겨찾기