Skip to content

Commit

Permalink
refactor(transform): 주석 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
Danji-ya committed Nov 11, 2023
1 parent 9546126 commit 096b9d6
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 20 deletions.
5 changes: 5 additions & 0 deletions src/transform/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ const getActualOptions = (options) =>
const transform = (ast, content, options) => {
const { requireAst, requireCode, ...restOptions } = getActualOptions(options);

/*
* babel.transformFromAstSync
* 변환 단계에서는 추상 구문 트리(AST)를 받아 그 속을 탐색해 나가며 노드들을 추가, 업데이트, 제거
* {@link https://babeljs.io/docs/babel-core#transformfromastsync}
*/
const { ast: transformedAst, code: transformedContent } =
transformFromAstSync(ast, content, {
ast: requireAst,
Expand Down
80 changes: 61 additions & 19 deletions src/transform/transform-esm-to-cjs.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ const kimbapVisitor = {
ExportNamedDeclaration: exportNamedDeclarationVisitor,
};

/*
* AST 변환을 하고 싶을때, 재귀적으로 트리를 탐색해야 한다.
* AST는 단일 노드 또는 수백개, 수천개의 노드로 이루어 질 수 있으며 각 노드들을 탐색하는 동안 막다른 곳에 다다르는데
* 이때, 다음 노드로 가기위해서는 뒤로 돌아가는 것이 필요하기 때문에 각 노드에 입장(enter)하고, 돌아올때 각 노드를 퇴장(exit)하면서 두번의 방문을 거치게 된다.
*/
const visitor = {
Program: {
enter(programPath, state) {
Expand All @@ -34,29 +39,43 @@ const visitor = {
};

function importDeclarationVisitor(path) {
/*
* 로컬로 정의된 변수와 충돌하지 않는 식별자 생성
* Node { type: "Identifier", name: "_imported" }
*/
const newIdentifier = path.scope.generateUidIdentifier('imported');

const specifiers = path.get('specifiers');

if (!isArray(specifiers)) return;

specifiers.forEach((specifier) => {
// import square from "./square";의 경우
// square에 해당하는 이름의 바인딩 목록들을 가져온다.
/*
* import specifier from "library" 의 경우
* specifier에 대한 바인딩 목록들을 가져온다.
*/
const { referencePaths } = specifier.scope.getBinding(
specifier.node.local.name,
);

// a라는 importedKey에 접근하기 위해서는 두 가지의 경우가 존재
// default export 의 형태인 경우 import a from "source"
// 아닌 경우 import { a } from "source"
/*
* a라는 importedKey에 접근하기 위해서는 두 가지의 경우가 존재
* default export 의 형태인 경우 import a from "source"
* 아닌 경우 import { a } from "source"
*/
const importedKey = specifier.isImportDefaultSpecifier()
? DEFAULT_IMPORT_KEYWORD
: specifier.get('imported.name').node;

// convert console.log(square(1, 2)) to console.log(newIdentifier[`importedKey`](1, 2))
/*
* 바인딩 목록들을 순회하며 노드를 업데이트 한다.
* specifier(1, 2)를 newIdentifier[`importedKey`](1, 2) 형태로 업데이트 한다.
*/
referencePaths.forEach((refPath) => {
// If computed === true, `object[property]`.
// Else, `object.property` -- meaning property should be an Identifier.
/*
* If computed === true, `object[property]`.
* Else, `object.property` -- meaning property should be an Identifier.
*/
refPath.replaceWith(
t.memberExpression(newIdentifier, t.stringLiteral(importedKey), true),
);
Expand All @@ -70,26 +89,33 @@ function importDeclarationVisitor(path) {
SOURCE: t.stringLiteral(path.get('source.value').node),
});

// const newIdentifier = require(`path.get("source.value").node`);
/*
* import문을 require문으로 업데이트 한다.
* const newIdentifier = require(`path.get("source.value").node`);
*/
path.replaceWith(newNode);
}

function exportDefaultDeclarationVisitor(path) {
const declaration = path.get('declaration');

// 함수선언문인 경우
/*
* 함수선언문인 경우
* module.exports = test;
* function test(){...}
*/
if (declaration.isFunctionDeclaration()) {
// module.exports = test;
// function test(){...}
path.replaceWithMultiple([
getModuleExportsAssignment(t.identifier(declaration.node.id.name)),
declaration.node,
]);
return;
}

// 나머지인 경우
// module.exports = test;
/*
* module.exports = test;
* export문을 module.exports문으로 업데이트 한다.
*/
path.replaceWith(
getModuleExportsAssignment(t.identifier(declaration.node.name)),
);
Expand All @@ -98,27 +124,37 @@ function exportDefaultDeclarationVisitor(path) {
function exportNamedDeclarationVisitor(path) {
const declarations = [];

// Exporting declarations
if (path.has('declaration')) {
const declaration = path.get('declaration');

/*
* 클래스선언문인 경우
* export class A {};
*/
if (declaration.isClassDeclaration()) {
declarations.push({
name: declaration.node.id,
value: t.toExpression(declaration.node),
});
}

/*
* 함수선언문인 경우
* export function func(){};
*/
if (declaration.isFunctionDeclaration()) {
declarations.push({
name: declaration.node.id,
value: t.toExpression(declaration.node),
});
}

/*
* 변수선언문인 경우
* export const foo = 'a';
* export const foo = 'a', bar = 'b';
*/
if (declaration.isVariableDeclaration()) {
// export const foo = 'a';
// export const foo = 'a', bar = 'b';
const decls = declaration.get('declarations');

decls.forEach((decl) => {
Expand All @@ -130,7 +166,10 @@ function exportNamedDeclarationVisitor(path) {
}
}

// Export list
/*
* Export list인 경우
* export { name1, …, nameN };
*/
if (path.has('specifiers')) {
const specifiers = path.get('specifiers');

Expand All @@ -142,7 +181,10 @@ function exportNamedDeclarationVisitor(path) {
});
}

// module.exports.[property] = value;
/*
* module.exports.[property] = value;
* export문을 module.exports문으로 업데이트 한다.
*/
path.replaceWithMultiple(
declarations.map((decl) =>
getModuleExportsAssignment(decl.value, decl.name),
Expand Down
10 changes: 9 additions & 1 deletion src/transform/transform-strict-mode.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@ const visitor = {
Program: programVisitor,
};

function programVisitor(path, state) {
function programVisitor(path, _state) {
const {
node: { directives },
} = path;

/*
* 'use strict' 가 존재하는 경우
* 따로 노드를 업데이트하지 않는다.
*/
for (const directive of directives) {
if (directive.value.value === 'use strict') return;
}

/*
* 'use strict' 가 존재하지 않는 경우
* use strict' 노드를 추가한다.
*/
path.unshiftContainer(
'directives',
t.directive(t.directiveLiteral('use strict')),
Expand Down

0 comments on commit 096b9d6

Please sign in to comment.