개발 관련 도서

모던 자바스크립트 - Babel과 Webpack을 이용한 ES6+/ES.NEXT 개발 환경 구축

mrban 2024. 8. 11. 23:21

크롬, 사파리, 파이어폭스, 엣지 같은 에버그린 브라우저의 ES6 지원율은 약 98%로 거의 대부분 ES6 사양을 지원합니다.

하지만 IE 11의 ES6 지원율은 약 11%이고. ES6 이상의 버전(ES6+)과 제안 단계에 있는 ES 제안 사양(ES.NEXT)은 브라우저에 따라 지원율이 다른것을 알 수 있습니다.

 

따라서 ES6+와 ES.NEXT의 최신 ESCMAScript 사양을 사용하여 프로젝트를 진행하려면 최신 사양으로 작성된 코드를 경우에 따라 IE를 포함한 구형 브라우저에서 문제 없이 동작시키기 위한 개발 환경을 구축하는 것이 필요합니다.

 

또한 대부분의 프로젝트가 모듈을 사용하므로 ES6 모듈(ESM)외의 별도의 모듈 로더도 필요합니다.

  • IE를 포함한 구형 브라우저는 ESM을 지원X
  • ESM을 사용하더라도 트랜스파일링이나 번들링이 필요함
  • ESM이 아직 지원하지 않는 기능(bare import 등)이 있고 점차 해결되고 있지만 아직 몇 가지 이슈가 존재

트랜스파일러인 Bable과 모듈 번들러인 Webpack을 이용하면 이러한 문제를 해결할 수 있습니다.


Babel

Babel은 ES6+/ES.NEXT로 구현된 최신 사양의 소스코드를 IE 같은 구형 브라우저에서도 동작하는 ES5 사양의 소스코드로 변환(트랜스파일링)할 수 있습니다.

 

예를 들어 다음과 같은 ES6의 화살표 함수와 ES7의 지수 연산자를 Babel을 통해 ES5 사양으로 변환할 수 있습니다.

 

// ES6+
[1, 2, 3].map(n => n ** n);

// ES5
"use strict";

[1, 2, 3].map(function (n) {
  return Math.pow(n, n);
});

 

이처럼 트랜스파일링을 위한 Babel을 사용하기 위해 개발 환경을 구축해보겠습니다.

 

Babel 설치

npm을 사용하여 터미널에서 다음과 같이 명령어를 입력해 Babel을 설치합니다.

 

# 프로젝트 폴더 생성
$ mkdir esnext-project
$ cd esnext-project

# package.json 생성
$ npm init -y

# babel-core, babel-cli 설치
$ npm install --save-dev @babel/core @babel/cli

 

설치가 완료된 이후 package.json 파일은 다음과 같습니다.

 

 

Babel 프리셋 설치와 babel.config.json 설정 파일 작성

Babel을 사용하려면 @babel/preset-env를 설치해야 합니다.

 

@bebel/preset-env는 함께 사용되어야 하는 Babel 플러그인을 모아 둔 것으로 Babel 프리셋이라고 부르며 Bebel이 제공하는 공식 Babel 프리셋(official preset)은 다음과 같습니다.

  • babel/preset-env
  • babel/preset-flow
  • babel/preset-react
  • babel/preset-typescript

이러한 @bebel/preset-env는 필요한 플러그인들을 프로젝트 지원 환경에 맞춰 동적으로 결정해 줍니다. 프로젝트 지원 환경은 Browserslist 형식으로 .browserslistrc 파일에 상세히 설정할 수 있으며 이를 생략하면 기본값으로 설정됩니다.

 

기본 설정은 모든 ES6+/ES.NEXT 사양의 소스코드를 변환합니다.

 

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

 

설치가 완료된 이후 pakage.json 파일은 다음과 같습니다.

 

 

설치가 완료되면 프로젝트 루트 폴더에 babel.config.json 설정 파일을 생성하고 다음과 같이 작성하면 @babel/preset-env를 사용할 수 있습니다.

 

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

 

트랜스파일링

Babel을 사용하여 ES6+/ES.NEXT 사양의 소스코드를 ES5 사양의 소스코드로 트랜스파일링하기 위해서 Babel CLI 명령어를 사용할 수 있습니다. 하지만 매번 Babel CLI 명령어를 입력하는 것은 번거로우므로 npm scripts에 Babel CLI 명령어를 등록해서 사용합니다.

 

package.json 파일에 scripts를 추가한 후 완성된 package.json 파일은 다음과 같습니다.

 

 

위 npm scripts의 build는 src/js 폴더(타깃 폴더)에 있는 모든 자바스크립트 파일들을 트랜스파일링한 후, 그 결과물을 dist/js 폴더에 저장합니다. 사용한 옵션의 의미는 다음과 같습니다.

  • -w : 타깃 폴더에 있는 모든 자바스크립트 파일들의 변경을 감지하여 자동으로 트랜스파일. (--watch 옵션의 축약형) 
  • -d : 트랜스파일링된 결과물이 저장될 폴더를 지정. 만약 지정된 폴더가 존재하지 않으면 자동 생성 (--out-dir 옵션의
    축약형)

 

 

다음은 트랜스파일링을 테스트하기 위해 프로젝트 루트 폴더에 src/js 폴더를 생성한 후 lib.js와 main.js를 추가하겠습니다.

 

// src/js/lib.js
export const pi = Math.PI; // ES6 모듈

export function power(x, y) {
  return x ** y; // ES7: 지수 연산자
}

// ES6 클래스
export class Foo {
  #private = 10; // stage 3: 클래스 필드 정의 제안

  foo() {
    // stage 4: 객체 Rest/Spread 프로퍼티 제안
    const { a, b, ...x } = { ...{ a: 1, b: 2 }, c: 3, d: 4 };
    return { a, b, x };
  }

  bar() {
    return this.#private;
  }
}
// src/js/main.js
import { pi, power, Foo } from './lib';

console.log(pi);
console.log(power(pi, pi));

const f = new Foo();
console.log(f.foo());
console.log(f.bar());

 

터미널에서 다음과 같이 명령어를 입력하여 트랜스파일링을 실행할 수 있습니다.

$npm run build

 

트랜스파일링에 성공하면 프로젝트 루트 폴더에 dist/js 폴더가 자동으로 생성되고 트랜스파일링된 main.js와 lib.js가 저장됩니다. 트랜스파일링된 main.js를 실행한 결과는 다음과 같습니다.

 

 

만약 추가 설치가 필요한 플러그인은 Babel 홈페이지 상단 메뉴의 Search란에 제안 사양의 이름을 입력하면 해당 플러그인을 검색하여 설치할 수 있습니다.

 

브라우저에서 모듈 로딩 테스트

위에서 살펴본 예제의 모듈 기능은 Node.js 환경에서 동작한 것이고 Babel이 모듈을 트랜스파일링한 것도 Node.js가 기본 지원하는 CommonJS 방식의 모듈 로딩 시스템에 따른 것입니다. 다음은 src/js/main.js가 Babel에 의해 트랜스파일링된 결과입니다.

 

// dist/js/main.js
"use strict";

var _lib = require("./lib");

// src/js/main.js
console.log(_lib.pi);
console.log((0, _lib.power)(_lib.pi, _lib.pi));
var f = new _lib.Foo();
console.log(f.foo());
console.log(f.bar());

 

브라우저는 CommonJS 방식의 requrie 함수를 지원하지 않으므로 위에서 트랜스파일링된 결과를 그대로 프로젝트 루트 폴더에 다음과 같이 index.html을 작성하여 HTML 파일을 브라우저에서 실행하면 에러가 발생합니다. 

 

<!DOCTYPE html>
<html>
<body>
  <script src="dist/js/lib.js"></script>
  <script src="dist/js/main.js"></script>
</body>
</html>

 

이러한 문제는 Webpack을 통해 해결할 수 있습니다.


Webpack

Webpack은 의존 관계에 있는 자바스크립트, CSS, 이미지 등의 리소스들을 하나(또는 여러 개)의 파일로 번들링하는 모듈 번들러입니다. Webpack을 사용하면 다음과 같은 장점이 있습니다.

  • 의존 모듈이 하나의 파일로 번들링되므로 별도의 모듈 로더가 필요 없음
  • 여러 개의 자바스크립트 파일을 하나로 번들링하므로 HTML 파일에서 script 태그로 여러 개의 자바스크립트 파일을 로드해야 하는 번거로움이 사라짐

 

Webpack과 Babel을 이용하여 ES6+/ES.NEXT 개발 환경을 구축해 보겠습니다.

 

Webpack 설치

터미널에서 다음과 같이 명령어를 입력하여 Webpack을 설치합니다.

 

$npm install --save-dev webpack webpack-cli

 

설치가 완료된 이후 package.js 파일은 다음과 같습니다.

 

 

babel-loader 설치

Webpack이 모듈을 번들링할 때 Babel을 사용하여 ES6+/ES.NEXT 사양의 소스코드를 ES5 사양의 소스코드로 트랜스파일링하도록 babel-loader을 설치합니다.

 

$npm install --save-dev babel-loader

 

npm scripts를 변경하여 Babel 대신 Webpack을 실행하도록 수정하면 package.json 파일은 다음과 같습니다.

 

 

 

webpack.config.js 설정 파일 작성

webpack.config.js는 Webpack이 실행될 때 참조하는 설정 파일입니다. 프로젝트 루트 폴더에 webpack.config.js 파일을 생성하고 다음과 같이 작성합니다.

 

const path = require('path');

module.exports = {
  // entry file
  // https://webpack.js.org/configuration/entry-context/#entry
  entry: './src/js/main.js',
  // 번들링된 js 파일의 이름(filename)과 저장될 경로(path)를 지정
  // https://webpack.js.org/configuration/output/#outputpath
  // https://webpack.js.org/configuration/output/#outputfilename
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'js/bundle.js'
  },
  // https://webpack.js.org/configuration/module
  module: {
    rules: [
      {
        test: /\.js$/,
        include: [
          path.resolve(__dirname, 'src/js')
        ],
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  },
  devtool: 'source-map',
  // https://webpack.js.org/configuration/mode
  mode: 'development'
};

 

다음과 같이 명령어를 실행하면 Babel이 트랜스파일링을 수행하고 Webpack이 번들링을 수행합니다. 

 

 

Webpack을 실행한 결과, dist/js 폴더에 bundle.js가 생성되고 이 파일은 main.js, lib.js 모듈이 하나로 번들링된 결과물입니다. index.html을 다음과 같이 수정하고 브라우저에서 실행하면 문제없이 실행되는 것을 확인할 수 있습니다.

 

<!DOCTYPE html>
<html>
<body>
  <script src="./dist/js/bundle.js"></script>
</body>
</html>

 

babel-polyfill 설치

Babel을 사용하여 ES6+/ES.NEXT 사양의 소스코드를 ES5 사양의 소스코드로 트랜스파일링해도 브라우저가 지원하지 않는 코드가 남아 있을 수 있습니다.

  • ES6에서 추가된 Promise, Object.assign, Array.from 등은 ES5 사양에 대체할 기능이 없기 때문에 트랜스파일링되지 못하고 그래도 남음

따라서 IE 같은 구형 브라우저에서도 Promise, Object.assign, Array.from 등과 같은 객체나 메서드를 사용하기 위해서는 @babel/polyfill을 설치해야 합니다.

 

$npm install @babel/polyfill

 

설치가 완료된 이후 package.json 파일은 다음과 같습니다.

 

 

@babel/polyfill은 개발 환경에서만 사용하는 것이 아니라 실제 운영 환경에서도 사용해야 합니다. 따라서 개발용 의존성(devDependencies)으로 설치하는 --save-dev 옵션을 지정하지 않습니다.

 

ES6의 inport을 사용하는 경우에는 진입점의 선두에서 먼저 폴리필을 로드하도록 합니다.

 

// src/js/main.js
import "@babel/polyfill";
import { pi, power, Foo } from './lib';
...

 

Webpack을 사용하는 경우에는 위 방법 대신 webpack.config.js 파일의 entry 배열에 폴리필을 추가합니다.

 

const path = require('path');

module.exports = {
  // entry file
  // https://webpack.js.org/configuration/entry-context/#entry
  entry: ['@babel/polyfill', './src/js/main.js'],
...

 

위와 같이 webpack.config.js 파일을 수정하여 폴리필을 반영하여 Webpack을 실행하면 dist/js/bundle.js에 다음과 같이 폴리필이 추가된 것을 확인할 수 있습니다.