2014년부터 이 사이트를 만들기 시작해 2017년에 이르렀다. 짧다면 짧은 시간이지만 웹은 그 사이 거듭된 발전을 지속했다. 그 중 가장 큰 트렌드로 자리잡은 것이 모듈화이다. 이전에는 자바스크립트 코드로 작업을 하면 각각의 파일로 나눠 따로 불러와야했다.

이는 HTTP/1.1 프로토콜의 전송과정을 생각했을 때 굉장히 비효율적이었다. 한 번에 하나씩 밖에 전송할 수 없는 구조상 하나의 파일이 아닌 이상 많은 시간 지체가 필수적이었다. 특히 jQuery와 같은 의존성 모듈의 경우 이를 무조건 먼저 불러와야 하기 때문에 힘든 점이 많았다.

이를 해결하고자 UMD, AMD, CommonJS 등이 등장했다. 하나의 파일 안에 다른 파일을 불러와 나눠 작업할 수 있게 도운 것이다. 하지만 이를 사용하기 위해선 Browserify와 같은 플러그인을 또 불러와야 했고 불편을 초래할 수 밖에 없었다.

그리고 이를 결정적으로 해결할 webpack이 등장했다. 자바스크립트 파일을 포함해 모든 파일을 불러와 하나로 모으고 이를 내보낼 수 있게 만든 것이다. 특히 Tree Shaking, 사용하지 않는 함수나 변수를 코드에서 제거하는 기술, 등을 기본적으로 지원하기 때문에 자바스크립트의 대표적인 모듈러로 자리잡았다.

jekyll + webpack

문제는 이를 jekyll에 어떻게 적용하느냐였다. jekyll-assets 과 jekyll-browserify 등 Rubygems에 의존하던 것들을 모두 webpack으로 처리하려고 하니 생각할 것이 많았다. 그래서 우선 두 가지를 없애기로 정하는 것을 목표로 잡았다.

  1. jekyll-assets
  2. jekyll-browserify

우선 가장 골칫거리였던 jekyll-assets부터 제거하기로 했다. jekyll-assets에서 수행하던 작업은 크게 두가지였다. 캐시 처리를 위해 chunkhash를 만들어 이를 불러오는 것. 그리고 불러온 파일을 압축하여 production 모델로 만드는 것이었다.

JS나 CSS 파일을 압축하는 것은 어렵지 않았다. 캐시 처리를 위한 해시 처리가 고민이었다. 항상 바뀌는 해시를 매번 수정할 수는 없는 문제였다. 다행히 나와 같이 고민한 한 블로거의 글1을 통해 해결할 수 있었다. jekyll이 제공하는 data 폴더를 이용하는 것이다.

데이터 폴더에 들어있는 자료는 모두 liquid 코드로 불러올 수 있다. 즉 manifest 파일을 만들어 value, key 방식으로 저장하는 것이다. 그리고 value 값을 참조하여 key 값을 가져온다면 jekyll-assets과 같이 제공할 수 있을 것이고 실제로 가능했다.

{
  "app.css": "/build/style-000ee75ec04fff4c249605e78d799b8b.css",
  "app.js": "/build/app-914a44ecd8827d1596fd.js"
}

이를 위해 webpack-assets-manifest 플러그인을 사용했다. 그리고 entry값으로 JS 코드와 CSS 코드를 받아 해시를 붙인 이름을 저장토록 설정했다. 코드 압축은 webpack에서 기본적으로 제공하기 때문에 webpack -p라고 매개변수만 정의하면 되었다.

두번째로 해결해야 할 것이 jekyll-browserify이었다. 많은 부분에서 활용하진 않지만, npm 코드를 불러와야할 경우 require 코드가 필수적이었다. 더욱이 browserify는 빌드 속도가 꽤 느린 편이기 때문에 불만이 많았다.

webpack은 이런 부분에서 더 나은 해결책을 제공했다. browserify는 기본적으로 ES5 코드만 인식할 수 있다. 즉 ES6 문법을 사용하려면 babelify와 같은 transform을 추가적으로 이용해야한다. 하지만 webpack은 loader 옵션을 통해 간단히 ES6 코드를 컴파일 할 수 있다. gulp와 같은 task runner가 할 수 있었던 Critical CSS와 같은 작업도 플러그인으로 지원하기 때문에 편리했다.

다만 문제는 webpack이 항상 jekyll을 빌드하기 전에 위치해야 했다는 점이었다. data 폴더는 빌드하는 과정에서 처리 되기 때문에 그 전에 정의할 필요성이 있었다. 하지만 liquid 코드를 사용하는 코드의 경우 먼저 jekyll에서 선처리가 되어야 했기에 고민이었다.

이 문제도 고민은 오래했지만 간단히 해결했다. webpack은 config 파일을 여러개 생성해도 실행에 문제가 되지 않는다. 따라서 webpack.config.js와 더불어 webpack.prod.js를 만들어 기본 작업은 jekyll 빌드 이전에 되도록 하였고, 빌드 한 이후엔 prod 설정을 불러와 처리하도록 했다.

"scripts": {
  "build:assets": "webpack -p",
  "build:contents": "bundle exec jekyll build",
  "build:production": "webpack --config webpack.prod.js -p",
  "build": "run-s build:* "
}

마지막으로 남은 과정은 gitlab에서 작동하도록 docker 파일을 수정하는 것이었다. 이 부분에서 가장 많은 시간을 소요했다. Rubygems 중에는 native extension으로 작동하는 gem들이 종종 있는데 이를 빌드하기 위해선 OS 내 의존성이 깔려있어야 했다. 수많은 검색으로 꼭 필요한 것만 끄집어냈다.

Docker의 파일을 줄이는 것도 중요했다. 그래서 무조건적으로 alpine 리눅스를 선상에 두고 작업했다. 그리고 이 점 때문에 후회했다. 다른 gem들은 힘들긴 해도 빌드하는데 필요한 의존성을 찾아낼 수 있었다. 하지만 PhantomJS는 어떻게 해도 alpine 리눅스에선 빌드되지 않았다.

다행히 이를 해결할 방법은 있었다. Phantomized를 이용해 alpine 리눅스에서 빌드할 수 있도록 환경을 만들고 npm으로 설치하는 것이었다. 다행히 완벽히 작동했고, 빌드도 성공적으로 마칠 수 있었다. 지금 적용된 사이트가 그 결과물이다.

후기

webpack과 jekyll을 통합하고나서 개발 환경도 많이 달라졌다. webpack-dev-server는 jekyll 빌드와 webpack 필드를 동시에 수행할 수 있다. 그 덕분에 CSS를 수정하고 다시 jekyll를 빌드해야 하는 상황에도 이를 인식하고 자동으로 재빌드해주는 것이 굉장히 편리해졌다.

아직 작업거리는 남아있다. 적절한 해결책을 찾지 못한 jekyll-srcset-tag을 제거하는 것이다. 해당 gem은 imagemagick 의존성을 지니고 있어 Docker 크기를 상당수 늘렸다. 이를 webpack화하여 좀 더 가볍게 빌드할 수 있도록 만들고 싶다.