[기술세미나] babel

이승준·2025년 1월 30일

JS

목록 보기
4/5
post-thumbnail

바벨이란?

바벨은 주로 ESMA 2015+ 코드를 현재 및 이전 브라우저나 환경에서 이전 버전과 호환되는 JavaScript 버전으로 변환하는 데 사용되는 툴체인입니다.

즉, ES6이후 문법으로 작성되는 코드를 ES6가 지원되지않는 브라우저에서 동작할 수 있게 지원해주는 프로그램

주제 선정 이유

바벨을 주제로 선택한 이유는 크로스 브라우징이 웹 개발에서 중요한 개념이며, 이를 해결하는 대표적인 기술이 바벨이라고 생각했기 때문입니다.
피사기간동안 웹을 학습하면서 최신 JavaScript 문법이 모든 브라우저에서 동일하게 동작하지 않는다는 점을 알게 되었고, 바벨이 이를 해결해 준다는 점이 흥미로워 고르게 되었습니다.

크로스 브라우징(Cross-Browsing)?

크로스 브라우징(Cross-Browsing)이란 다양한 브라우저 환경에서도 웹사이트가 정상적으로 동작하도록 만드는 과정을 의미합니다. 브라우저마다 JavaScript 해석 방식이나 CSS 지원 범위가 다를 수 있기 때문에 등장한 개념입니다.

JavaScript와 브라우저의 호환성


지금은 IE를 제외하곤 대부분 ES6+ 문법이 대부분 지원이 되는 상황이다.

🚀 Babel 기초부터 설정까지

바벨 초기설정

npm init -y

우선, npm init을 해서 package.json을 만들어준다.
-y는 패키지명 등등 입력을 생략하고 한번에 package.json을 만들겠다는 의미이다.

Babel 패키지 설치

npm install --save-dev @babel/core @babel/cli
  • @babel/core: Babel의 핵심 라이브러리
  • @babel/cli: Babel을 CLI(명령줄)에서 사용할 수 있도록 도와주는 도구



babel 플러그인을 사용해서 변환하기

바벨은 플러그인을 사용해서 특정 기능을 변환할 수 있다.
ES6에 추가된 화살표 함수를 ES5에서 동작가능한 일반 함수로 트랜스파일 해보자!

npm install --save-dev @babel/plugin-transform-arrow-functions



변환할 ES6 코드 작성 (script.js)

Babel로 변환할 ES6 코드를 script.js 파일에 작성합니다.


const greet = (name) => {
    console.log(`안녕하세요, ${name}님!`);
};

greet("철수");



babel을 사용하여 변환 실행

npx babel script.js --out-file lib/script.js --plugins=@babel/plugin-transform-arrow-functions
  • npx babel: 바벨을 실행시켜라
  • script.js: 변환 소스 파일
  • --out-file: 변환 출력 파일 경로 지정
  • plugins: 변환에 사용되는 플러그인 지정


명령어 실행 후 lib 폴더의 script.js파일에 변환된 코드가 작성된것을 볼 수 있다. 화살표 함수가 일반함수로 변경됨.



✅ 문제점

--plugins=@babel/plugin-transform-arrow-function 옵션을 매번 명령어에 입력해야 하는 불편함이 있습니다.

지금은 하나의 플러그인만 사용하지만, 더 많은 플러그인이 사용되어야 할 때 감당할수없음. 매우 귀찮음



babel.config.json 설정 파일을 사용하여 변환 자동화

babel.config.json을 사용하여 Babel 설정을 관리하는 방법을 알아보겠습니다. 앞에서 일일이 플러그인을 cli에 옵션으로 넣어줘야하는 귀찮음이 존재했다. 이를 해결하기위해 babel.config.json에 적어주면 앞으로 매번 실행할때 마다 적어주지 않아도 플러그인을 적용할 수 있다.


babel.config.json 파일 생성

{
  "plugins": ["@babel/plugin-transform-arrow-functions"]
}



변환 실행

이제 --plugins 옵션 없이 실행할 수 있습니다.

npx babel script.js --out-file lib/script.js

변환결과

같은 출력물이 나오는걸 볼 수 있다.

하지만 그래도 사용할 플러그인을 config 파일에 다 적어줘야하는데, 이는 브라우저마다 어떤게 필요할지 사용자가 모두 알아야함을 의미한다. 불필요한 코스트가 들기때문에 babel/preset이라는것을 활용해보자



@babel/preset-env 설치

npm install --save-dev @babel/preset-env

@babel/preset-env은 코드가 돌아가는 브라우저 환경에 필요한 플러그인을 사용자가 관리할 필요없이 최신 JS를 사용할 수 있도록 사전 설정해주는 기능이다.

동작원리가 궁금하다면? 클릭!

babel.config.json 설정 파일 수정

  • 이제 @babel/plugin-transform-arrow-functions을 제거하고, @babel/preset-env를 추가합니다.
{
  "presets": ["@babel/preset-env"]
}

그럼 테스트를 해봅시다!

여러 ES6 문법을 사용한 코드 변환하기 (script.js)

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        return `안녕하세요, 저는 ${this.name}입니다.`;
    }
}

const person = new Person("홍길동", 30);
console.log(person.greet());

const fetchData = async () => {
    return new Promise((resolve) => {
        setTimeout(() => resolve("데이터 로드 완료!"), 1000);
    });
};

fetchData().then(console.log);
  • 위의 예시 코드를 작성한 후, npx babel script.js --out-file lib/script.js 바벨을 실행시킨다.
  • 이제 @babel/preset-env가 모든 최신 문법을 ES5로 변환해 줍니다.



변환 결과물 코드
  "use strict";

function _regeneratorRuntime() { "use strict"; /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */ _regeneratorRuntime = function _regeneratorRuntime() { return e; }; var t, e = {}, r = Object.prototype, n = r.hasOwnProperty, o = Object.defineProperty || function (t, e, r) { t[e] = r.value; }, i = "function" == typeof Symbol ? Symbol : {}, a = i.iterator || "@@iterator", c = i.asyncIterator || "@@asyncIterator", u = i.toStringTag || "@@toStringTag"; function define(t, e, r) { return Object.defineProperty(t, e, { value: r, enumerable: !0, configurable: !0, writable: !0 }), t[e]; } try { define({}, ""); } catch (t) { define = function define(t, e, r) { return t[e] = r; }; } function wrap(t, e, r, n) { var i = e && e.prototype instanceof Generator ? e : Generator, a = Object.create(i.prototype), c = new Context(n || []); return o(a, "_invoke", { value: makeInvokeMethod(t, r, c) }), a; } function tryCatch(t, e, r) { try { return { type: "normal", arg: t.call(e, r) }; } catch (t) { return { type: "throw", arg: t }; } } e.wrap = wrap; var h = "suspendedStart", l = "suspendedYield", f = "executing", s = "completed", y = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} var p = {}; define(p, a, function () { return this; }); var d = Object.getPrototypeOf, v = d && d(d(values([]))); v && v !== r && n.call(v, a) && (p = v); var g = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(p); function defineIteratorMethods(t) { ["next", "throw", "return"].forEach(function (e) { define(t, e, function (t) { return this._invoke(e, t); }); }); } function AsyncIterator(t, e) { function invoke(r, o, i, a) { var c = tryCatch(t[r], t, o); if ("throw" !== c.type) { var u = c.arg, h = u.value; return h && "object" == _typeof(h) && n.call(h, "__await") ? e.resolve(h.__await).then(function (t) { invoke("next", t, i, a); }, function (t) { invoke("throw", t, i, a); }) : e.resolve(h).then(function (t) { u.value = t, i(u); }, function (t) { return invoke("throw", t, i, a); }); } a(c.arg); } var r; o(this, "_invoke", { value: function value(t, n) { function callInvokeWithMethodAndArg() { return new e(function (e, r) { invoke(t, n, e, r); }); } return r = r ? r.then(callInvokeWithMethodAndArg, callInvokeWithMethodAndArg) : callInvokeWithMethodAndArg(); } }); } function makeInvokeMethod(e, r, n) { var o = h; return function (i, a) { if (o === f) throw Error("Generator is already running"); if (o === s) { if ("throw" === i) throw a; return { value: t, done: !0 }; } for (n.method = i, n.arg = a;;) { var c = n.delegate; if (c) { var u = maybeInvokeDelegate(c, n); if (u) { if (u === y) continue; return u; } } if ("next" === n.method) n.sent = n._sent = n.arg;else if ("throw" === n.method) { if (o === h) throw o = s, n.arg; n.dispatchException(n.arg); } else "return" === n.method && n.abrupt("return", n.arg); o = f; var p = tryCatch(e, r, n); if ("normal" === p.type) { if (o = n.done ? s : l, p.arg === y) continue; return { value: p.arg, done: n.done }; } "throw" === p.type && (o = s, n.method = "throw", n.arg = p.arg); } }; } function maybeInvokeDelegate(e, r) { var n = r.method, o = e.iterator[n]; if (o === t) return r.delegate = null, "throw" === n && e.iterator["return"] && (r.method = "return", r.arg = t, maybeInvokeDelegate(e, r), "throw" === r.method) || "return" !== n && (r.method = "throw", r.arg = new TypeError("The iterator does not provide a '" + n + "' method")), y; var i = tryCatch(o, e.iterator, r.arg); if ("throw" === i.type) return r.method = "throw", r.arg = i.arg, r.delegate = null, y; var a = i.arg; return a ? a.done ? (r[e.resultName] = a.value, r.next = e.nextLoc, "return" !== r.method && (r.method = "next", r.arg = t), r.delegate = null, y) : a : (r.method = "throw", r.arg = new TypeError("iterator result is not an object"), r.delegate = null, y); } function pushTryEntry(t) { var e = { tryLoc: t[0] }; 1 in t && (e.catchLoc = t[1]), 2 in t && (e.finallyLoc = t[2], e.afterLoc = t[3]), this.tryEntries.push(e); } function resetTryEntry(t) { var e = t.completion || {}; e.type = "normal", delete e.arg, t.completion = e; } function Context(t) { this.tryEntries = [{ tryLoc: "root" }], t.forEach(pushTryEntry, this), this.reset(!0); } function values(e) { if (e || "" === e) { var r = e[a]; if (r) return r.call(e); if ("function" == typeof e.next) return e; if (!isNaN(e.length)) { var o = -1, i = function next() { for (; ++o < e.length;) if (n.call(e, o)) return next.value = e[o], next.done = !1, next; return next.value = t, next.done = !0, next; }; return i.next = i; } } throw new TypeError(_typeof(e) + " is not iterable"); } return GeneratorFunction.prototype = GeneratorFunctionPrototype, o(g, "constructor", { value: GeneratorFunctionPrototype, configurable: !0 }), o(GeneratorFunctionPrototype, "constructor", { value: GeneratorFunction, configurable: !0 }), GeneratorFunction.displayName = define(GeneratorFunctionPrototype, u, "GeneratorFunction"), e.isGeneratorFunction = function (t) { var e = "function" == typeof t && t.constructor; return !!e && (e === GeneratorFunction || "GeneratorFunction" === (e.displayName || e.name)); }, e.mark = function (t) { return Object.setPrototypeOf ? Object.setPrototypeOf(t, GeneratorFunctionPrototype) : (t.__proto__ = GeneratorFunctionPrototype, define(t, u, "GeneratorFunction")), t.prototype = Object.create(g), t; }, e.awrap = function (t) { return { __await: t }; }, defineIteratorMethods(AsyncIterator.prototype), define(AsyncIterator.prototype, c, function () { return this; }), e.AsyncIterator = AsyncIterator, e.async = function (t, r, n, o, i) { void 0 === i && (i = Promise); var a = new AsyncIterator(wrap(t, r, n, o), i); return e.isGeneratorFunction(r) ? a : a.next().then(function (t) { return t.done ? t.value : a.next(); }); }, defineIteratorMethods(g), define(g, u, "Generator"), define(g, a, function () { return this; }), define(g, "toString", function () { return "[object Generator]"; }), e.keys = function (t) { var e = Object(t), r = []; for (var n in e) r.push(n); return r.reverse(), function next() { for (; r.length;) { var t = r.pop(); if (t in e) return next.value = t, next.done = !1, next; } return next.done = !0, next; }; }, e.values = values, Context.prototype = { constructor: Context, reset: function reset(e) { if (this.prev = 0, this.next = 0, this.sent = this._sent = t, this.done = !1, this.delegate = null, this.method = "next", this.arg = t, this.tryEntries.forEach(resetTryEntry), !e) for (var r in this) "t" === r.charAt(0) && n.call(this, r) && !isNaN(+r.slice(1)) && (this[r] = t); }, stop: function stop() { this.done = !0; var t = this.tryEntries[0].completion; if ("throw" === t.type) throw t.arg; return this.rval; }, dispatchException: function dispatchException(e) { if (this.done) throw e; var r = this; function handle(n, o) { return a.type = "throw", a.arg = e, r.next = n, o && (r.method = "next", r.arg = t), !!o; } for (var o = this.tryEntries.length - 1; o >= 0; --o) { var i = this.tryEntries[o], a = i.completion; if ("root" === i.tryLoc) return handle("end"); if (i.tryLoc <= this.prev) { var c = n.call(i, "catchLoc"), u = n.call(i, "finallyLoc"); if (c && u) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } else if (c) { if (this.prev < i.catchLoc) return handle(i.catchLoc, !0); } else { if (!u) throw Error("try statement without catch or finally"); if (this.prev < i.finallyLoc) return handle(i.finallyLoc); } } } }, abrupt: function abrupt(t, e) { for (var r = this.tryEntries.length - 1; r >= 0; --r) { var o = this.tryEntries[r]; if (o.tryLoc <= this.prev && n.call(o, "finallyLoc") && this.prev < o.finallyLoc) { var i = o; break; } } i && ("break" === t || "continue" === t) && i.tryLoc <= e && e <= i.finallyLoc && (i = null); var a = i ? i.completion : {}; return a.type = t, a.arg = e, i ? (this.method = "next", this.next = i.finallyLoc, y) : this.complete(a); }, complete: function complete(t, e) { if ("throw" === t.type) throw t.arg; return "break" === t.type || "continue" === t.type ? this.next = t.arg : "return" === t.type ? (this.rval = this.arg = t.arg, this.method = "return", this.next = "end") : "normal" === t.type && e && (this.next = e), y; }, finish: function finish(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.finallyLoc === t) return this.complete(r.completion, r.afterLoc), resetTryEntry(r), y; } }, "catch": function _catch(t) { for (var e = this.tryEntries.length - 1; e >= 0; --e) { var r = this.tryEntries[e]; if (r.tryLoc === t) { var n = r.completion; if ("throw" === n.type) { var o = n.arg; resetTryEntry(r); } return o; } } throw Error("illegal catch attempt"); }, delegateYield: function delegateYield(e, r, n) { return this.delegate = { iterator: values(e), resultName: r, nextLoc: n }, "next" === this.method && (this.arg = t), y; } }, e; }
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var Person = /*#__PURE__*/function () {
  function Person(name, age) {
    _classCallCheck(this, Person);
    this.name = name;
    this.age = age;
  }
  return _createClass(Person, [{
    key: "greet",
    value: function greet() {
      return "\uC548\uB155\uD558\uC138\uC694, \uC800\uB294 ".concat(this.name, "\uC785\uB2C8\uB2E4.");
    }
  }]);
}();
var person = new Person("홍길동", 30);
console.log(person.greet());
var fetchData = /*#__PURE__*/function () {
  var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
    return _regeneratorRuntime().wrap(function _callee$(_context) {
      while (1) switch (_context.prev = _context.next) {
        case 0:
          return _context.abrupt("return", new Promise(function (resolve) {
            setTimeout(function () {
              return resolve("데이터 로드 완료!");
            }, 1000);
          }));
        case 1:
        case "end":
          return _context.stop();
      }
    }, _callee);
  }));
  return function fetchData() {
    return _ref.apply(this, arguments);
  };
}();
fetchData().then(console.log);




🚀 이제 최신 문법을 자동으로 ES5로 변환하여 구버전 브라우저에서도 실행할 수 있습니다! 🎉

어떤 변환이 일어났을까

  • class Person {} (클래스) -> function Person() {} (생성자 함수로 변환)
  • 템플릿 리터럴 (백틱 ``) -> 문자열 + 연산 ("안녕하세요, 저는 " + this.name + "입니다.")
  • async / await (비동기 함수) -> Promise + regeneratorRuntime 기반 변환
  • 화살표 함수 (=>) -> function 키워드로 변환

babel preset이 없었다면 우린 아래 명령어를 입력해서 변환했어야 했다..

  npx babel script.js --out-file lib/script.js \
--plugins=@babel/plugin-transform-classes,@babel/plugin-transform-template-literals,@babel/plugin-transform-async-to-generator,@babel/plugin-transform-arrow-functions



브라우저에서 동작하는지?

변환된 코드로도 잘 작동하는걸 확인 할 수 있다.

profile
들은것은 잊어버린다 본것은 기억된다 해본것은 내것이 된다

0개의 댓글