플로그인을 제작하기 위해선 AST 구조를 알아야 한다.
AST Explorer 에서 AST가 뭔지 알아보자.
에디터에 const myNamy= hwang + 'eunji';
를 입력했을때 AST Explorer에 출력된 json의 모습이다.
👻
보기는 했으나 이걸 어떻게 활용하는지..하핳
module.exports = function ({ types: t }) {
const node = t.BinaryExpression('+', t.Identifier('a'), t.Indentifier('b'));
console.log('isBinaryEspression:', t.inBinaryExpression(node));
return {};
};
a+b
를 하는 코드 작성inBinaryExpression()
을 통해 타입검사를 진행했다.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);
},
},
};
};
Identifier()
)Identifier()
-개발자가 만든 값(변수, 함수 등)- 함수가 호출되며, 변수가 쓰인 만큼 호출path.node.name
변수명을 나타내며, const myName= hwang + 'eunji
에서 myName, hwang에 해당BinaryExpression()
함수가 실행path.node.operator
는 const myName= hwang + 'eunji
에서+
연산자에 해당const myName= hwang + 'eunji'
가 입력됬을 때Identifier()
는 2번 호출BinaryExpression()
1번 호출콘솔로그를 제거하는 플러그인을 제작 하기 위해서는 먼저 콘솔로그의 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"
}
- [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'
ExpressionStatement
: 콘솔로그의 최상위 노드CallExpression
: 함수 또는 메서드를 호출하는 코드의 typeMemberExpression
: 메서드 호출 시 typeIdentifier
: 변수명과 같이 선언되기에 Identifier typeLiteral
: 문자열이 주어졌음으로 literal typemodule.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
을 빼먹어서 한참을 다시 봤다! 노드를 잘 보기!
콘솔로그 제거를 해봤으니 콘솔로그 추가하는 플러그인을 작성해보자.
규칙 : '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(번역)
바벨-핸드북 깃헙 : 한국어버전 있음
바벨-핸드북 깃헙 : 플러그인 만들기 : 한국어버전 있음