Javascript & Typescript

Javascript로 백엔드 개발할 때 Babel을 꼭 써야 될까?

MoDoLEE 2022. 10. 27. 00:28
반응형

Babel 이란?

Babel is a JavaScript compiler.
Use next generation JavaScript, today.

Babel 공식 홈페이지 메인에 써 있는 문구이다.

간단히 설명하면 최신 Javascript 문법으로 작성을 하면, 하위 호환성을 위해 이전 버전의 문법으로 변환해 주는 역할을 한다.

 

왜 Transpiler가 아니라 Compiler라고 말하나 궁금해서 검색을 해봤다.

Is Babel a compiler or transpiler?

위 게시물의 답변에 의하면 compiler와 transpiler의 경계가 모호해 졌으며, 둘 다 하나의 언어에서 다른 언어로 변환해 주는 것이기 때문에 큰 문제가 없다고 말한다. 그리고 공식 홈페이지에서 compiler라고 말하니 그냥 그렇게 불러주자.

 

이 Babel이 실제로 어떤 역할을 하며, 예제 코드를 기반으로 Babel을 통해 실행할 때와 그렇지 않을 때의 차이점을 알아보겠다.

예제 코드

 

예제 코드는 아래와 같이 구성하였고, ES Module을 사용하여 작성했다. 

index.js

import * as path from 'path';
import { BACKEND_URL } from './config';
import { User } from './model/user';

const tempDir = path.join(__dirname, 'temp');

const user = new User({ name: 'modolee', age: 37, phone: '012-345-6789' });

console.log({ tempDir, BACKEND_URL, user });

config/constants.js

export const TIMEOUT = 100;
export const HASH_SALT_ROUND = 10;

config/environments.js

export const BACKEND_HOST = process.env.BACKEND_HOST || 'http://localhost';
export const BACKEND_PORT = process.env.BACKEND_PORT || 3000;
export const BACKEND_URL = `${BACKEND_HOST}:${BACKEND_PORT}`;

config/index.js

export * from './constants';
export * from './environments';

model/user.js

export class User {
  name;
  phone;

  constructor({ name, phone }) {
    this.name = name;
    this.phone = phone;
  }
}

 

Babel 설치

정확히 어떤 역할을 하는지 알아보기 위해 우선 설치하고 사용해 보자.

 

npm install --save-dev @babel/core @babel/node @babel/cli @babel/preset-env

프로젝트 루트에 .babelrc 파일을 만들고 아래 내용을 입력한다.

{
  "presets": ["@babel/preset-env"]
}

그리고 빌드와 실행을 위한 스크립트를 package.json에 추가한다.

"scripts": {
  "start": "node src/index.js",
  "start:babel": "babel-node src/index.js",
  "build": "babel src -d dist"
 }

 

Babel 이 변환한 코드

 

아래 명령을 실행한 후, dist 폴더에 있는 babel을 통해 변환된 코드를 확인해 보자.

npm run build

index.js

"use strict";

function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
var path = _interopRequireWildcard(require("path"));
var _config = require("./config");
var _user = require("./model/user");
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
var tempDir = path.join(__dirname, 'temp');
var user = new _user.User({
  name: 'modolee',
  age: 37,
  phone: '012-345-6789'
});
console.log({
  tempDir: tempDir,
  BACKEND_URL: _config.BACKEND_URL,
  user: user
});

config/constants.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.TIMEOUT = exports.HASH_SALT_ROUND = void 0;
var TIMEOUT = 100;
exports.TIMEOUT = TIMEOUT;
var HASH_SALT_ROUND = 10;
exports.HASH_SALT_ROUND = HASH_SALT_ROUND;

간단해 보였던 코드들이 정신없게 변해버렸다. 그런데 이렇게 표현해줘야 CommonJS Module을 사용하는 하위 버전의 Node.js에서도 실행이 가능하다고 한다.

babel-node로 실행하기

npm run start:babel

아무런 이상 없이 정상 실행된다.

> basic@1.0.0 start:babel
> babel-node src/index.js

{
  tempDir: '/Users/modolee/Dev/blog/basic/src/temp',
  BACKEND_URL: 'http://localhost:3000',
  user: User { name: 'modolee', phone: '012-345-6789' }
}

node로 실행하기

만약 Babel 없이 실행한다면 어떻게 될까?

아래 명령어로 babel 없이 실행해 보자

npm run start

SyntaxError: Cannot use import statement outside a module

(node:34388) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/modolee/Dev/blog/basic/src/index.js:1
import * as path from 'path';
^^^^^^

SyntaxError: Cannot use import statement outside a module

첫번째 에러가 발생한다.

package.json 파일에 "type": "module"을 추가하면 에러를 제거할 수 있다.

"type": "module"

ERR_UNSUPPORTED_DIR_IMPORT

node:internal/errors:464
    ErrorCaptureStackTrace(err);
    ^

Error [ERR_UNSUPPORTED_DIR_IMPORT]: Directory import '/Users/***/src/config' is not supported resolving ES modules imported from /Users/***/src/index.js

두번째 에러는 폴더 안에 index.js 파일이 있을 때, 경로를 폴더까지만 작성해서 발생하는 에러이다.

이 부분은 2가지 방법으로 해결할 수 있다.

 

1. 경로에 /index.js를 추가하는 방법

import { BACKEND_URL } from './config'; // 수정 전
import { BACKEND_URL } from './config/index.js'; // 수정 후

2. 실행 시 옵션을 주는 방법

package.json의 start 스크립트 수정

  "scripts": {
    "start": "node --experimental-specifier-resolution=node src/index.js"
  }

ERR_MODULE_NOT_FOUND

위의 에러를 1번 방법으로 해결했다면, 추가적으로 발생하는 에러이다.

import 시 파일명에 확장자를 표시해줘야 된다.

import { User } from './model/user'; // 수정 전
import { User } from './model/user.js'; // 수정 후

ReferenceError: __dirname is not defined in ES module scope

CommonJS Module에서 기본 제공하던 __dirname은 ES Module에서는 제공하고 있지 않다.

대신 path.resolve()를 사용하면 된다.

const tempDir = path.join(__dirname, 'temp'); // 수정 전
const tempDir = path.join(path.resolve(), 'temp'); // 수정 후

 

여기까지 해결하면 위의 예제는 이제 babel 없이도 정상적으로 동작한다.

 

결론

Babel이 없이도 코드를 작성할 수는 있지만, 코드 작성의 편의성과 과거에 작성 된 레퍼런스들을 원활하게 이용하고자 한다면 Babel을 사용하는게 정신 건강에 좋을 것 같다.

 

참고

반응형