Gulpfile.js를 뜯어보다: Ghost 테마 빌드 도구를 공부해봤다

Ghost 테마에서 사용되는 gulpfile.js를 직접 살펴보며 Gulp가 어떤 역할을 하는지, 어떻게 CSS/JS/템플릿 빌드와 압축을 관리하는지 단계별로 정리해봤습니다.

Gulpfile.js를 뜯어보다: Ghost 테마 빌드 도구를 공부해봤다

Gulp는 정확히 어떤 일을 할까?

웹 개발을 하다 보면 생각보다 반복 작업이 많다.
CSS 파일 여러 개를 하나로 합쳐야 하고, 브라우저 호환성을 위해 접두어를 붙여야 하고, JS도 압축해야 하고, 테마 파일은 ZIP으로 만들어야 한다.
매번 손으로 하면 귀찮고, 실수도 생긴다.

이런 일을 자동화해주는 게 바로 Gulp.js다.
Node.js 기반 빌드 도구이고, gulpfile.js 파일 안에 작업 순서와 방법을 코드로 정의하면, 명령어 한 줄로 여러 일을 자동으로 해준다.

Ghost 테마에도 이 Gulp가 쓰인다.
사실 처음엔 단순히 yarn dev를 실행하면 알아서 잘 되니까 그냥 넘겼는데, 그게 뭘 하는 건지 궁금해서 gulpfile을 직접 열어보게 됐다.


gulpfile.js 구조를 살펴보니

우선 구조는 생각보다 단순했다. (검정색은 배경이요. 하얀색은 글자였다.) 딱 필요한 작업만 잘 정리되어 있다.

1. 주요 플러그인들

  • gulp-livereload: 변경 사항을 감지해서 브라우저를 자동 새로고침
  • gulp-postcss: 여러 CSS 관련 작업을 묶는 도구
  • gulp-concat: JS 파일을 하나로 합침
  • gulp-uglify: JS 코드 압축
  • gulp-zip: 테마를 압축(zip) 파일로 만듬
  • postcss-easy-import, autoprefixer, cssnano: CSS용 추가 기능

const {series, parallel, watch, src, dest} = require('gulp');
const pump = require('pump');
const fs = require('fs');
const order = require('ordered-read-streams');

// gulp plugins and utils
const livereload = require('gulp-livereload');
const postcss = require('gulp-postcss');
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');
const beeper = require('beeper');
const zip = require('gulp-zip');

처음엔 모르는 게 많았지만 (지금도 여전히 모르겠지만), 이름을 보고 기능을 대충 추측할 수 있었다.


작업별로 어떤 일이 일어나는가?

📁 1. css() 함수: CSS 빌드 작업

  • assets/css/screen.css 파일을 읽고,
  • @import 구문을 실제로 해석하고,
  • 자동 접두어(autoprefixer)를 붙이고,
  • cssnano로 압축까지 한 후,
  • 결과물을 assets/built/ 폴더에 저장한다.
function css(done) {
    pump([
        src('assets/css/screen.css', {sourcemaps: true}),
        postcss([
            easyimport,
            autoprefixer(),
            cssnano()
        ]),
        dest('assets/built/', {sourcemaps: '.'}),
        livereload()
    ], handleError(done));
}

이걸 수동으로 했다면 꽤 번거로웠을 것 같다.


🧩 2. js() 함수: JS 압축 빌드

  • Ghost가 사용하는 shared-theme-assets의 공통 JS 파일들과,
  • 현재 테마에서 작성한 JS 파일들을 순서대로 읽고,
  • 하나로 합쳐서(minify) main.min.js로 만든다.
  • 마찬가지로 assets/built/ 폴더에 저장된다.
function js(done) {
    pump([
        order(getJsFiles('v1'), {sourcemaps: true}),
        concat('main.min.js'),
        uglify(),
        dest('assets/built/', {sourcemaps: '.'}),
        livereload()
    ], handleError(done));
}

여기서 ordered-read-streams 라는 조금 생소한 모듈을 쓴 게 인상적이었다. 여러 스트림을 순서대로 읽는 역할을 한다.


💡 3. hbs() 함수: 템플릿 변경 감지

  • .hbs 파일이 변경되면 livereload로 브라우저가 자동 갱신되도록 한다.
  • 실제로 테마 구조를 바꾸면서 이 자동 새로고침 기능이 꽤 유용했다.
function hbs(done) {
    pump([
        src(['*.hbs', 'partials/**/*.hbs']),
        livereload()
    ], handleError(done));
}

📦 4. zipper() 함수: 테마 압축

  • 최종적으로 테마를 .zip 파일로 만들어주는 함수다.
  • dist/ 폴더에 zip 파일이 생긴다.
  • node_modules, dist, yarn-error.log 같은 불필요한 파일은 제외되도록 잘 정리돼 있다.
function zipper(done) {
    const filename = require('./package.json').name + '.zip';

    pump([
        src([
            '**',
            '!node_modules', '!node_modules/**',
            '!dist', '!dist/**',
            '!yarn-error.log'
        ]),
        zip(filename),
        dest('dist/')
    ], handleError(done));
}

이건 테마를 운영 블로그에 업로드할 때 아주 유용하다.


기본 명령어 정리

이 gulpfile에 정의된 기본 명령어들은 아래처럼 정리된다:

  • yarn dev: gulp default → build(css, js) + livereload 서버 + 파일 감지
  • yarn build: CSS/JS만 빌드하고 끝
  • yarn zip: 빌드 후 테마를 ZIP 파일로 압축

내가 이해한 Gulp의 핵심 장점

이번에 처음 gulpfile을 뜯어보면서 느낀 건 다음과 같다:

  1. 수동으로 하던 작업을 자동화
    → 반복되는 build, 압축, 새로고침을 명령어 한 줄로 끝낼 수 있다.
  2. 결과물이 예측 가능하다
    → CSS/JS 압축은 항상 동일한 결과가 나오고, 문제가 생겨도 어디서 꼬였는지 추적이 쉬워진다.
  3. 실수 방지
    → 잘못된 순서로 파일을 합치거나 누락할 일이 없다.

마무리하며

Ghost 테마를 수정하면서 단순히 CSS 몇 줄 고치는 게 아니라, 빌드 도구까지 같이 보게 된 건 꽤 재미있는 경험이었다.
Gulp는 생각보다 오래된 도구지만, 구조가 단순해서 빠르게 이해할 수 있었고, 개인 블로그 같은 규모에도 딱 적당한 자동화를 제공해준다.

지금까지는 yarn devyarn zip을 무의식적으로 썼다면, 이젠 그 명령어가 어떤 과정을 거쳐 어떤 결과를 내는지 조금은 이해하게 된 것 같다.
그리고 이런 파일 하나에도 작은 논리와 철학이 담겨 있다는 걸 느꼈다.