Logo

Node.js로 ES6 코드 실행하기 (Babel6)

ES6(ES2105) 이상의 최신 자바스크립트 문법으로 작성된 코드가 노드JS(NodeJS)에서 실행이 안 되는 경우가 종종있습니다. 이럴 경우 어쩔 수 없이 예전 자바스크립트 문법으로 코드를 재작성하기도 하는데요. 이번 포스팅에서는 자바스크립트 Transpiler인 Babel을 이용하여 이 문제를 해결해보겠습니다.

NodeJS에서 ES6 코드 실행 오류

먼저 간단한 예제 프로젝트를 하나를 만들겠습니다. babel-test라는 디렉터리에 NPM 패키지를 생성합니다.

$ mkdir babel-test
$ cd babel-test
$ npm init -y

이제 다음과 같이 매우 간단한 자바스크립트 코드를 작성해보겠습니다. NodeJS 내장 모듈인 path를 이용해서 프로젝트의 dist 디렉터리의 절대 경로를 출력 후에 외부에서 불러올 수 있도록 내보내는 코드입니다.

  • script.js
import path from "path";
const outputPath = path.resolve(__dirname, "dist");
console.log(`Output path is "${outputPath}".`);
export { outputPath };

그런 다음 node 커맨드를 이용해서 콘솔에서 작성한 파일을 NodeJS 런타임으로 실행시켜봅니다.

$ node script.js
/temp/babel-test/script.js:1
(function (exports, require, module, __filename, __dirname) { import path from 'path'
                                                              ^^^^^^

SyntaxError: Unexpected token import
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:139:10)
    at Module._compile (module.js:616:28)
    at Object.Module._extensions..js (module.js:663:10)
    at Module.load (module.js:565:32)
    at tryModuleLoad (module.js:505:12)
    at Function.Module._load (module.js:497:3)
    at Function.Module.runMain (module.js:693:10)
    at startup (bootstrap_node.js:191:16)
    at bootstrap_node.js:612:3

ES6에서 자바스크립트 모듈을 불러오기 위해서 사용되는 import라는 키워드 때문에 구문 예외가 발생하였습니다.

웹 브라우저의 경우 자바스크립트 문법을 쫒아오는 속도가 중구난방이지만, NodeJS의 경우 최신 버전을 사용한다면 거의 왠만한 ES6 문법은 도입이 되어 있습니다. 그럼에도 불구하고 본 예제처럼 NodeJS는 아직 CommonJS 기반 모듈 시스템을 사용하기 때문에 ES6의 importexport와 같은 키워드는 디폴트로 지원되지 않고 있습니다.

아쉬운데로 파일의 확장자를 .mjs로 바꾸고 --experimental-modules 옵션을 이용하면 방법도 있긴한데 아직 실험적인(Experimental) 기능이므로 여기서는 다루지는 않겠습니다. 관심이 있으신 분들은 관련 NodeJS 레퍼런스인 ECMAScript Modules을 참조바랍니다.

Babel: JavaScript Transpiler

위에서 발생한 예외를 근본적으로 해결하려면 우리가 작성한 코드를 NodeJS 런타임이 실행가능한 형태로 변환해줘야 합니다. 이를 보통 transpile이라고 하는데요. 소스 코드를 머신 코드로 바꿔주는 compile과 달리, transpile은 같은 언어를 유지한체 다른 런타임에서 해당 코드가 정상적으로 해석될 수 있도록 형태만 바꿔준다는 차이가 있습니다. 실제 현장에서는 이 두 용어를 혼용해서 사용하고 있는 것 같습니다.

여기서는 JavaScript Transpiler 중에서 가장 널리 사용되는 Babel을 사용해서 위 코드를 transpile하여 NodeJS 런타임이 예외없이 실행할 수 있도록 해보겠습니다.

Babel CLI 실행

먼저 개발 의존성으로 바벨 커맨드 라인 도구를 설치하겠습니다.

$ npm i -D babel-cli

설치를 마치면 터미널에서 babelbabel-node 커맨드를 실행가능할 수 있게 됩니다. 첫번째 커맨드는 단순히 어떤 코드를 transpile 시킬 때 사용하며, 두번째 커맨드는 transpile 뿐만 아니라 transpile된 코드를 NodeJS 런타임으로 실행까지 해주게 됩니다.

먼저 babel 커맨드로 우리가 작성한 자바스크립트 파일을 transpile 해보겠습니다.

$ npx babel script.js
import path from 'path';
const outputPath = path.resolve(__dirname, 'dist');
console.log(`Output path is "${outputPath}".`);
export { outputPath };

어라… 이상하네요? 각 라인의 끝에 ;가 추가된 것 빼고는 소스 코드의 형태가 처음에 작성한 그대로 입니다. 그 이유는 아직 어떻게 변환할지에 대해서 바벨 설정을 해주지 않았기 때문입니다.

Babel Preset 설정

바벨 설정은 여러 플러그인을 스스로 조합하거나 미리 준비된 프레셋을 사용할 수가 있습니다. 여기서는 env라는 가장 범용적으로 사용되는 프리셋을 사용하도록 하겠습니다. env 프리셋은 ES2015뿐만 아니라 ES2016과 ES2017까지 적용되어 있습니다.

먼저 개발 의존성으로 프리셋을 설치합니다.

$ npm i -D babel-preset-env

그리고 프리셋 옵션을 줘서 다시 한 번 바벨로 우리가 작성한 자바스크립트 파일을 변환해보겠습니다.

$ npx babel --presets env script.js
'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.outputPath = undefined;

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var outputPath = _path2.default.resolve(__dirname, 'dist');
console.log('Output path is "' + outputPath + '".');
exports.outputPath = outputPath;

이번에는 다행히 원하는 형태로 소스 코드가 transpile 되었습니다. import를 사용하던 부분은 NodeJS에서 지원하는 require를 사용하도록 대체되었으며, export를 사용하던 부분 역시 exports 변수를 사용하도록 변경되었습니다. 그 밖에도 문자열 출력 부분이 interpolation 구문("${outputPath}")에서 concatenation("' + outputPath + '")으로 대체된 것도 확인할 수 있습니다.

NodeJS로 ES6 코드 실행 성공

마지막으로 바벨에 의해서 transpiple된 코드를 노드 런타임으로 실행해보겠습니다. 다음과 같이 pipeline (|) 심볼을 이용해서 바벨 실행 결과를 노드 커맨드의 입력으로 전달해주면 됩니다.

$ npx babel --presets env script.js | node
Output path is "/temp/babel-test/dist".

윈도우 같이 pipeline 심볼을 사용할 수 없는 경우에는 다음과 같이 바벨 변환과 노드 실행을 한 번에 해주는 babel-node 커맨드를 이용하면됩니다.

$ npx babel-node --presets env script.js
Output path is "/temp/babel-test/dist".

[TIP] 바벨 설정 파일

바벨 커맨드를 실행할 때 마다 매번 프리셋 옵션을 붙이기가 번거롭다면 바벨 설정 파일인 .babelrc를 프로젝트 최상위 디렉터리에 작성하시면 됩니다.

  • .babelrc
{
	"presets": ["env"]
}

다른 방법으로 .babelrc 파일 대신에 다음과 같이 package.json 파일에 바벨 설정을 추가할 수도 있습니다.

  • package.json
{
(... 생략 ...)
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-env": "^1.7.0"
  },
+  "babel": {
+    "presets": ["env"]
+  },
(... 생략 ...)
}

위 두가지 방법 중 원하시는 방법으로 설정 후에는 프리셋 옵션을 주지 않고도 바벨 커맨드를 실행할 수 있습니다.

$ npx babel-node script.js
Output path is "/temp/babel-test/dist".

아무래도 프로젝트 규모가 커지고 바벨 설정이 복잡해지는 상황에서는 설정 파일을 이용하는 편이 훨씬 이점이 많을 것입니다.

[TIP] NPM 스크립트로 등록

바벨 커맨드를 자주 사용하는 상황이라면 NPM 스크립트로 등록해두고 사용하시는 게 더 간편할 수 있습니다.

  • package.json
{
(... 생략 ...)
  "scripts": {
    "start": "babel-node script.js"
  },
(... 생략 ...)
}
$ npm start

> babel-test@1.0.0 start /temp/babel-test
> babel-node script.js

Output path is "/temp/babel-test/dist".

이상으로 NodeJS로 ES6 코드를 실행하는 방법에 대해서 알아보았습니다.