From 096b9d6f8db2827c946e19a2746a6038ae2164ae Mon Sep 17 00:00:00 2001 From: Danji-ya Date: Sat, 11 Nov 2023 14:53:54 +0900 Subject: [PATCH] =?UTF-8?q?refactor(transform):=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/transform/index.js | 5 ++ src/transform/transform-esm-to-cjs.js | 80 ++++++++++++++++++++------ src/transform/transform-strict-mode.js | 10 +++- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/transform/index.js b/src/transform/index.js index 2faaa2c..efc2874 100644 --- a/src/transform/index.js +++ b/src/transform/index.js @@ -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, diff --git a/src/transform/transform-esm-to-cjs.js b/src/transform/transform-esm-to-cjs.js index 0a8283c..1cd2b87 100644 --- a/src/transform/transform-esm-to-cjs.js +++ b/src/transform/transform-esm-to-cjs.js @@ -22,6 +22,11 @@ const kimbapVisitor = { ExportNamedDeclaration: exportNamedDeclarationVisitor, }; +/* + * AST 변환을 하고 싶을때, 재귀적으로 트리를 탐색해야 한다. + * AST는 단일 노드 또는 수백개, 수천개의 노드로 이루어 질 수 있으며 각 노드들을 탐색하는 동안 막다른 곳에 다다르는데 + * 이때, 다음 노드로 가기위해서는 뒤로 돌아가는 것이 필요하기 때문에 각 노드에 입장(enter)하고, 돌아올때 각 노드를 퇴장(exit)하면서 두번의 방문을 거치게 된다. + */ const visitor = { Program: { enter(programPath, state) { @@ -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), ); @@ -70,17 +89,22 @@ 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, @@ -88,8 +112,10 @@ function exportDefaultDeclarationVisitor(path) { return; } - // 나머지인 경우 - // module.exports = test; + /* + * module.exports = test; + * export문을 module.exports문으로 업데이트 한다. + */ path.replaceWith( getModuleExportsAssignment(t.identifier(declaration.node.name)), ); @@ -98,10 +124,13 @@ 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, @@ -109,6 +138,10 @@ function exportNamedDeclarationVisitor(path) { }); } + /* + * 함수선언문인 경우 + * export function func(){}; + */ if (declaration.isFunctionDeclaration()) { declarations.push({ name: declaration.node.id, @@ -116,9 +149,12 @@ function exportNamedDeclarationVisitor(path) { }); } + /* + * 변수선언문인 경우 + * 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) => { @@ -130,7 +166,10 @@ function exportNamedDeclarationVisitor(path) { } } - // Export list + /* + * Export list인 경우 + * export { name1, …, nameN }; + */ if (path.has('specifiers')) { const specifiers = path.get('specifiers'); @@ -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), diff --git a/src/transform/transform-strict-mode.js b/src/transform/transform-strict-mode.js index 27cddad..3862fa9 100644 --- a/src/transform/transform-strict-mode.js +++ b/src/transform/transform-strict-mode.js @@ -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')),