SPA(single page app)에서 webpack을 사용하는 이유

2000년대 초반의 웹페이지는 각 페이지마다 새로운 html을 요청해서 화면을 다시 그리는 방식이었다. 자바스크립트는 DOM을 조작하는 간단한 역할만 했었기 때문에 html의 script태그에 넣는 것으로도 충분했다. ajax가 유행했을 때는 자바스크립트의 비중이 조금 더 커졌지만 그래 봐야 페이지당 자바스크립트 파일 몇 개면 충분했다. SPA(single page app)는 하나의 html에 수십, 수백 개의 자바스크립트 파일을 포함하기 때문에 더 이상 기존 방식이 통할리 없었다.

<html>
<head>
<link rel="stylesheet" type="text/css" href="javascript_file_1.js">
<link rel="stylesheet" type="text/css" href="javascript_file_2.js">
...
<link rel="stylesheet" type="text/css" href="javascript_file_999.js">
</head>
...
</html>

위의 방식으로는 계속 늘어나는 자바스크립트 파일을 관리하기 힘들다. 또한 선언되는 순서도 신경 써야 하고, 기존에 생성된 전역 변수를 덮어쓰는 위험도 존재한다.

모듈 시스템이 필요하다.

자바스크립트는 태생이 브라우저에 종속적이었기 때문에 자체적인 모듈 시스템이 없었다. 모듈 시스템은 C++이나 자바에서 include나 import 키워드를 떠올리면 이해하기 쉽다. 자바스크립트 최신 표준인 ES6부터 모듈 시스템이 포함됐지만 아직 모든 브라우저가 지원하지 않고 있다. 모든 브라우저가 지원한다 하더라도 다수의 유명한 라이브러리가 ES6 표준으로 작성되지 않았다는 게 큰 걸림돌이다.

브라우저에 얽매이지 말자.

예전부터 자바스크립트를 브라우저 밖에서 사용할 수 있게 하려는 노력이 있었다. 대표적인 표준 위원회는 CommonJS, AMD가 있다. 이들이 자바스크립트를 범용적으로 사용하기 위해서 하는 대부분의 일은 모듈 시스템을 정의하는 일이었다. 그리하여 nodejs 같은 프레임워크가 등장할 수 있었고 자바스크립트가 서버에서 돌아가는 세상이 되었다.

SPA개발은 webpack과 함께

webpack은 CommonJS와 AMD 스펙 모두를 지원한다. webpack을 사용하면 여러 개의 자바스크립트 파일을 하나의 파일(원한다면 여러 개)로 컴파일할 수 있다. 그리하여 SPA개발 시 html에는 컴파일된 하나의 자바스크립트 파일만 포함시키면 된다. 고마운 점은 ES6 표준으로 작성되지 않은 라이브러리도 알아서 잘 처리해준다는 점이다. 따라서 필요한 라이브러리를 npm으로 설치 후 필요한 부분에 import만 하면 바로 사용할 수 있다.

webpack의 장점 1: 파일 분할 기능

수백 개의 자바스크립트 파일을 하나의 파일로 컴파일하는 건 좋은 생각이 아니다. 이렇게 하면 사용자가 첫 페이지를 보기 위해 몇 초를 기다려야 할 수도 있다. webpack에는 다양한 파일 분할 기능이 있다.

  • vendor코드만 따로 분리해서 하나의 파일로 만들 수 있다. vendor코드는 자주 변경되지 않기 때문에 사용자의 브라우저에 캐싱 후 오랫동안 재사용될 수 있다. 이 방법만 사용해도 사용자 경험을 상당 부분 끌어올릴 수 있다. 하지만 첫 방문자의 경우에는 의미가 없다.
  • require.ensure 구문을 사용해서 파일 분할을 지정할 수 있다. 이 방법은 require.ensure 구문을 실행할 때 필요한 파일을 on-demand로 다운로드한다(webpack이 알아서 해준다-_-b). 따라서 첫 방문자의 경우에도 전체 코드를 다운로드할 필요가 없기 때문에 반응 속도가 빠르다. 첫 페이지 로딩은 빠르지만 이후 필요한 부분의 파일을 다운로드해야 하기 때문에 분할된 코드의 크기가 너무 크지 않도록 주의해야 한다. react에서는 route별로 파일을 만드는 방법이 많이 사용된다.

webpack에서는 컴파일된 파일의 이름에 hash값을 붙이는 기능이 많이 사용된다. 해당 파일이 변경된 경우에만 hash값이 변경된다. 따라서 변경되지 않은 파일은 사용자의 브라우저에 캐싱된 파일이 재활용된다.

webpack의 장점 2: 다양한 loader를 사용할 수 있다.

대부분의 loader는 파일을 입력으로 받아서 자바스크립트 파일을 출력해준다. loader를 사용하면 webpack이 컴파일을 하기 전에 원하는 파일에 대해서 전처리를 할 수 있다. jsx라는 react 고유의 문법으로 작성된 파일을 자바스크립트 파일로 변환할 때 많이 쓰인다. 그 외에도 babel-loader를 이용해서 ES6, ES7과 같은 최신 자바스크립트 문법을 사용할 수도 있다.

여러 개의 loader를 체인 형식으로 묶을 수도 있다. 예를 들어 babel-loader와 eslint-loader를 묶어서 먼저 eslint로 정적 분석을 하고 성공하면 babel-loader가 jsx파일을 자바스크립트 파일로 변환하게 할 수 있다.

또 다른 대표적인 예는 style-loader와 css-loader를 같이 쓰는 경우이다. css를 사용할 때 어려운 점 중의 하나가 클래스 이름을 겹치지 않게 정의하는 것이다. css-loader는 import 하는 모듈의 id값을 클래스 이름에 자동으로 붙여주는 기능을 갖고 있다. 따라서 단순히 클래스 이름을 ‘button’으로 정의하더라도 이름이 겹칠까 봐 걱정할 필요가 없어진다. style-loader는 css-loader가 출력하는 css정보를 받아서 style태그로 만들고 그것을 DOM에 붙여준다.

// a.css
:local .button {
background-color: red;
}
// b.css
:local .button {
background-color: blue;
}
// app1.jsx
import styles from 'a.css'
...
<button className=styles.button>click me</button>
...
// app2.jsx
import styles from 'b.css'
...
<button className=styles.button>click me</button>
...

app1의 버튼은 빨간색이 되고, app2의 버튼은 파란색이 된다.

정리하며

프로그래밍의 시작을 c++로 했기 때문에 자바스크립트를 잘 모를 때는 프로그래머가 아닌 사람들을 위한 낮은 수준의 언어라고 생각했었다. 이 글에서 설명한 모듈 시스템의 경우 c++에서는 include 구문으로 너무나 당연하게 사용하던 기능이었다. 하지만 근래에 자바스크립트 표준의 발 빠른 행보와 풍부한 생태계를 바라보며 생각이 많이 바뀌고 있다.