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을 사용하는게 정신 건강에 좋을 것 같다.
참고
'Javascript & Typescript' 카테고리의 다른 글
Javascript Runtime error 줄이기 - Typescript스럽게 사용하기 (0) | 2022.10.25 |
---|---|
Prettier로 코드 포맷 통일하기 (1) | 2022.10.24 |