Rev. 2.73

최근 들어 더욱 가속화되어 발전하는 자바스크립트 트렌드는 이제 지켜보는 것조차 버겁네요. 최근 신규 프로젝트의 개발환경을 구축하면서 최종적으로 반영된 구성에 관해서 얘기해 보겠습니다. 브라우저에서 Node.js의 모듈 인프라를 이용할 수 있게 하고 실시간으로 트랜스파일(Transpile)이 되도록 개발환경을 구성해 보았습니다. 이 개발환경에 적응하면서 느끼는 것은 "이제 브라우저에서도 자바스크립트로 개발하기가 정말 좋은 세상이 되었구나!" 입니다. 그래서 다른 프로젝트에도 이를 적용할 수 있도록 사내에 전파를 시도하면서 정리했던 내용 일부를 공유합니다.

우리는 Node.js를 기반으로 한 개발환경에 BrowserifyWebpack과 같은 모듈 번들러를 이용하여 자바스크립트로 작성된 기능의 모듈화를 효율적이고 안정적으로 구현한 바 있습니다. 그리고 개발환경에서는 와쳐(Watcher)를 이용하여 파일이 변동되면 번들링한 후 브라우저에서 새로 고침까지 자동으로 되도록 했습니다.

그러나 답답합니다. 코드량이 많아질수록 눈에 띄게 느려지는 번들링 시간과 마이너 픽스에도 꼭 거쳐야만 하는 이 과정은 이제 지긋지긋합니다. 그냥 예전처럼 브라우저에서 새로 고치면 즉시 그 결과를 확인하고 싶습니다. 그래서 이처럼 불편하고 느려터진 번들링 과정을 개발하는 동안에는 하지 않아도 되도록 하는 것이 컨셉이라 하겠습니다. 즉, 실시간 트랜스파일이 되도록 한다는 것은, import 또는 require 문을 브라우저에서 직접 사용할 수 있도록 하는 것입니다.

Requirements

개발환경을 구성하는 주요한 설치 요구사항입니다. 테스트 라이브러리나 태스크 매니저는 취향에 맞게 사용하면 됩니다.

  • Node.js - 실시간으로 트랜스파일이 가능한 개발환경의 기반이 됩니다.
  • Electron - 구글 크롬 브라우저에서 import(require)문을 직접 이용하는 데 필요합니다.
  • Babel - ES6, ES7, JSX등 차세대 자바스크립트 코드를 구사하기 위해 사용합니다.
  • ESLint - 코드 스타일을 안내해주고, 빈번히 발생하는 개발 실수를 줄여줍니다.
  • Webpack - 프로덕션 빌드 과정에서 모듈 패키징에 사용됩니다.
  • Jest - 자바스크립트 유닛 테스트의 고통을 덜 수 있습니다.
  • Nightwatch.js - 구글 크롬 외 다른 브라우저에서의 작동 여부를 테스트하고 자동화를 위해 사용합니다.
  • Grunt - 이 모든 과정을 수월하게 관리할 수 있도록 도와주는 태스크 매니저입니다.

노트: Electron을 개발환경에 적합하다고 판단한 이유가 하나 더 있습니다. Node.js에 내장된 V8 버전(가장 최근에 나온 Node.js 5.1.1의 V8 버전은 4.6) 보다 Electron에 내장된 V8 버전이 4.8로 한참 앞서있기 때문입니다. 또한, V8의 블로그를 보면 ECMAScript 2015(ES6) 스펙을 구현하는 작업이 한창인 것을 알 수 있는데, 안정적인 버전의 크롬이 나오면 1, 2주 내로 Electron에 반영되는 장점은 덤입니다.

Configuration

이제 설정 파일을 작성해 봅시다. React와 jQuery를 사용하는 웹앱을 만든다고 가정합니다. 임의의 프로젝트 폴더에 package.json을 비롯한 프로젝트를 구성하는 파일과 폴더를 생성합니다.

$ mkdir my-project
$ cd my-project
$ touch .babelrc
$ touch .eslintrc
$ touch .gitignore
$ touch package.json
$ touch main.js
$ touch index.html
$ touch LICENSE.md
$ touch README.md
$ mkdir scripts
$ mkdir src
$ cd src
$ touch index.js

.babelrc Babel 트랜스파일러에서 사용할 플러그인을 지정합니다. 일반적으로 프리셋(babel-preset-*)을 사용하지만, 프리셋은 아주 많은 Babel 플러그인들을 포함하고 있어서 실시간으로 트랜스파일 하는 데에는 적합하지 않습니다. 오래 걸리기 때문입니다. 아래의 구성은 Electron 현재 버전 0.36.x(Node.js 5.1.1, 구글 크롬 47)에 내장된 V8 버전 4.7에서 ES2015 스펙이 미구현 되거나 일부만 구현되어 필요하게 된 플러그인을 개별적으로 로드하는 내용입니다. 이렇게 했을 때 프리셋을 사용할 때보다 매 새로 고침 마다 6초에서 4초 정도 시간을 절약할 수 있습니다. 보통 2초 정도면 페이지 로드가 완료됩니다.

노트: Node.js의 require를 훅(Hook)하는 babel-register는 캐시(Cache) 옵션이 기본으로 활성화되어 있습니다. 첫 로딩보다 그 다음 로딩이 훨씬 빠릅니다.

{
  "plugins": [
    "transform-es2015-destructuring",
    "transform-es2015-for-of",
    "transform-es2015-modules-commonjs",
    "transform-es2015-object-super",
    "transform-es2015-parameters",
    "transform-es2015-shorthand-properties",
    "transform-object-rest-spread",
    "transform-react-jsx"
  ]
}

노트: sticky-regexunicode-regex 그리고 typeof-symbol 플러그인은 테스트를 통과하지 못해 로드하지 않았습니다. 그리고 크롬의 최신 버전인 48(아마도 Electron 0.37.x)에서는 object-supertypeof-symbol 플러그인이 필요하지 않게 됩니다. 지난 26일 릴리즈된 V8 버전 4.9(크롬 49)에서는 destructuring, parameters, sticky-regex등이 구현되었습니다.

Electron 버전 0.35.x(Node 4.1.1, 크롬 45, v8 버전 4.5)에서는 아래의 3개 플러그인을 추가로 로드하여 ES6을 정상적으로 사용할 수 있습니다. Babel의 Github 리파지토리에 있는 380여 개 테스트 케이스를 돌려서 확인했으며 속도 역시 그럭저럭 나옵니다.

    ...
    // Require if using electron version 0.35.x
    "transform-es2015-block-scoping",
    "transform-es2015-classes",
    "transform-es2015-spread"
    ...

object-rest-spread는 ES2015의 공식 스펙은 아니지만 ES7의 꽃이라 할만한 멋진 연산자입니다. 이미 널리 사용되고 있으며, ESLint의 object-shorthand 룰에 영향을 받습니다.

.eslintrc 파일은 ESLint의 설정입니다. ESLint는 코드를 작성하는 과정에서 빈번하게 발생하는 실수를 예방하고, 엘레강스한 코드 스타일을 추천해 주며, 미래에 발생할 수 있는 잠재적 오류를 수정할 수 있도록 도와줍니다. 제가 사용하는 코드 편집기는 CODA 2인데, 여기에 ESLint JS Validator 플러그인을 추가하면 아래 설정에 기반을 두어 코드 검증기를 통해 꾸역꾸역 잔소리(?)해 대도록 꾸몄습니다. 만약 아톰(Atom) 편집기를 사용한다면, linter-eslint 패키지를 설치하여 사용할 수 있고 Sublime Text에도 비슷한 녀석이 있습니다.

eslint.gif

요런 느낌입니다. 가장 우선순위에 있는 룰은 얼마 전에 번역한 바 있는 Airbnb 코드 스타일이고, 그다음으로 JavaScript Standard Style의 프리셋과 React 플러그인을 적용하고, 개인적으로 탐탁지 못한 몇몇 규칙을 "rules"에 재정의한 것입니다.

{
  "ecmaFeatures": {
    "jsx": true,
    "modules": true,
    "experimentalObjectRestSpread": true
  },
  "env": { "es6": true, "node": true, "browser": true },
  "extends": ["standard", "airbnb"],
  "globals": { "$": true },
  "parser": "babel-eslint",
  "plugins": ["standard", "react"],
  "rules": {
    "comma-dangle": [2, "never"],
    "default-case": 0,
    "func-names": 0,
    "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
    "no-console": 0,
    "object-curly-spacing": 0,
    "react/prop-types": 0,
    "react/sort-comp": 0,
    "space-before-function-paren": [2, "never"],
    "strict": 0
  }
}

노트: 경우에 따라서는 ESLint관련 패키지를 글로벌에 설치해야 할 수도 있습니다.

$ npm install -g eslint
$ npm install -g eslint-config-airbnb eslint-config-standard
$ npm install -g eslint-plugin-react eslint-plugin-standard

package.json 파일의 내용은 다음과 같습니다. 프로젝트에서 필요한 모듈들의 정보를 포함한 여러 내용으로 구성됩니다. 더 자세한 내용은 이곳을 참고하세요.

{
  "name": "MyProject",
  "version": "0.0.1",
  "license": "MIT",
  "description": "My Awesome Project",
  "author": "firejune",
  "main": "main.js",
  "dependencies": {
    "jquery": "^2.2.0",
    "jquery-ui": "^1.10.5",
    "react": "^0.14.3",
    "react-dom": "^0.14.3"
  },
  "devDependencies": {
    "babel-core": "^6.4.5",
    "babel-eslint": "^5.0.0-beta6",
    "babel-loader": "^6.2.1",
    "babel-plugin-transform-es2015-destructuring": "^6.4.0",
    "babel-plugin-transform-es2015-for-of": "^6.3.13",
    "babel-plugin-transform-es2015-modules-commonjs": "^6.4.5",
    "babel-plugin-transform-es2015-object-super": "^6.4.0",
    "babel-plugin-transform-es2015-parameters": "^6.4.5",
    "babel-plugin-transform-es2015-shorthand-properties": "^6.3.13",
    "babel-plugin-transform-object-rest-spread": "^6.3.13",
    "babel-plugin-transform-react-jsx": "^6.4.0",
    "electron-prebuilt": "^0.36.5",
    "eslint": "^1.10.3",
    "eslint-config-airbnb": "^3.1.0",
    "eslint-config-standard": "^4.4.0",
    "eslint-plugin-react": "^3.15.0",
    "eslint-plugin-standard": "^1.3.1",
    "grunt": "^0.4.5",
    "jest-cli": "^0.8.2",
    "nightwatch": "^0.8.15",
    "webpack": "^1.12.11"
  },
  "scripts": {
    "start": "electron .",
    "lint": "eslint ./src",
    "test:unit": "npm run lint && jest -c ./scripts/unit-test.json",
    "test:ui": "npm run test:unit  && nightwatch --test ./scripts/ui-test.js",
    "build": "npm run test:ui  && webpack --config ./scripts/package.js --release"
  }
}

굳이 ES6 문법까지는 필요는 없고, JSX 트랜스파일만 필요한 상황이라면 "transform-react-jsx" 플러그인만 남기거나 Babel이 아닌 node-jsx 모듈을 이용하는 방법도 있습니다. node-jsx가 사용법도 간단하고 빠르긴 한데, Babel로 이관되면서 deprecated 되었습니다.

Electron Starter

이제 Electron에서의 작업환경을 구성할 차례입니다. main.js 파일은 package.json에 명시되어 Electron이 처음으로 접근하는 파일이며, Electron에 의해 브라우저 윈도를 만들어줍니다. 아쉽지만, 이 파일은 ES6으로 작성할 수 없습니다.

'use strict';

const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;

// 실행 준비를 마치면 브라우저 창 생성
app.on('ready', function () {
  // 브라우저 생성
  const mainWindow = new BrowserWindow({width: 800, height: 600});
  // 브라우저에서 처음으로 그려질 페이지
  mainWindow.loadURL('file://' + __dirname + '/index.html');
  // 브라우저의 개발자 도구 자동으로 열기
  mainWindow.webContents.openDevTools();
  // 창이 닫히면 프로세스 종료
  mainWindow.on('closed', function() {
    app.quit();
  });
});

index.html 파일은 main.js에 의해 브라우저(BrowserWindow)에서 처음으로 그려질 페이지입니다. 이 브라우저가 바로 앞으로 동고동락할 작업용 브라우저입니다. 일단 다음과 같이 내용을 작성합니다.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <div id="example">Hello</div>
    <script>
      // Install babel hooks to the browser process
      require('babel-core/register')();
      require('./src');
    </script>
  </body>
</html>

노트: Electron은 main과 browser로 구분된 프로세스 두 개를 실행합니다. 따라서 두 프로세스 간에는 IPC 스타일의 통신을 이용해야 하지만, Electron 앱이 아닌, 일반적인 웹앱 개발에 Electron을 이용하는 것이므로 main 프로세스에서 하는 일에 대해서는 크게 걱정하지 않아도 됩니다. main.js는 main 프로세서에서 작동하고 index.html 및 하위 참조 스크립트들은 browser 프로세스에서 작동합니다.

자, 이제 기본적인 프로젝트 파일의 구성과 작업 실행 환경이 완료되었습니다. scripts 폴더는 정적 또는 동적 테스트 코드, 자동화 관련 코드, 작업 태스크 관리, 빌드 스크립트 등을 넣어둘 장소입니다. 이 글은 개발 환경을 구성하는 데 목적을 두기 때문에 이와 관련한 자세한 내용은 다루지 않을 것입니다. (엄청나게 다양하고 복잡하고 일일이 설명하기가 귀찮기도 하고 뭐 그렇습니다) LICENSE.md, README.md 파일에는 프로젝트와 관련된 내용을 작성하면 됩니다. 이제, 커멘드 라인에 개발에 필요한 모듈들을 설치하고 작업 결과를 확인할 수 있는 브라우저(Electron)를 실행해 봅시다. 'Hello'문자가 보이나요?

$ npm install
$ npm start

Enjoying Web Development with Electron

모든 준비는 끝났습니다. 멋들어지게 최신 자바스크립트 문법을 이용하여 본격적으로 개발을 시작해 봅시다. src/index.js 파일을 열고 간단한 React 기반의 'Hello, world!' 애플리케이션을 만들겠습니다.

import React from 'react';
import ReactDOM from 'react-dom';
import $ from 'jquery';
import 'jquery-ui/draggable';

class Title extends React.Component {
  constructor() {
    super();
    this.state = {
      value: 'Hello, world!'
    };
  }
  componentDidMount() {
    $(this.refs.el).draggable();
  }
  render() {
    return (
      <h1 ref="el">
        {this.state.value}
      </h1>
    );
  }
}

ReactDOM.render(
  <Title />,
  document.getElementById('example')
);

파일 작성 후 브라우저 창에서 새로 고칩니다. 단축키는 맥인 경우 CMD+R(윈도: Ctrl+R)입니다. 브라우저 개발자 도구는 CMD+ALT+I(윈도: Ctrl+Shift+I)로 열 수 있습니다. 'Hello, world!' 문자가 보이나요? 정상적으로 작동하는 것입니다. 놀랍죠? 문자의 드래그 앤 드롭 기능도 확인해 보세요!

크로스-브라우저는 어찌할까요? Browserify나 Webpack을 이용해서 번들링 한 후 실제 브라우저에서 동작하는 동적-테스트 도구(Nightwatch 와 같은)로 테스트 수행을 자동화하고 오류가 보고되면 그때 처리합니다.

Comments

Got something to add? You can just leave a comment.

Your Reaction Time!

captcha

avatar