들어가며
Browserify 에서 bundle() 을 수행하면, Readable stream 이 생성된다. 단순하게 생각했을 때 Gulp 도 Stream 기반이니 아래와 같이 browserify 해주는 task를 작성할 수 있지 않을까 싶지만, 결론부터 말하자면 안된다.
var gulp = require("gulp"); var browserify = require("browserify"); gulp.task("bundle-javascript", function () { return browserify("src/scripts/main.js") .bundle() .pipe(gulp.dest("dist/scripts/")); });
Gulp와 Vinyl
Gulp 는 Stream 기반이긴 하지만 또한 Vinyl 이라는 일종의 가상 파일 포맷에도 기반하고 있다. Vinyl은 파일을 경로와 컨텐츠 등의 메타데이터로 추상화해서 그 파일이 Github 이든 S3이든 로컬 파일시스템이든 관계없이 다룰 수 있도록 해준다.
우리가 흔히 Gulp에서 Task를 작성할 때 파일을 가져오는 방식으로 gulp.src 를 사용하는데, 보통은 아무생각없이 gulp.src를 사용하지만 사실 이 부분이 파일 시스템에서 파일을 읽어서 vinyl object 로 변환해주는 과정이다.
다시 Browserify 로 돌아가서 생각해보면, Task의 시작을 gulp.src 를 통해 vinyl object를 생성하는 것이 아닌 Browserify 를 사용해서 node.js의 Stream을 생성하는 것으로 시작했다는 것을 알 수 있다. 때문에 gulp.dest 를 만났을 때, vinyl object 를 실제 파일로 변환해주는 과정에서 vinyl object가 아닌 Readable stream 만 전달되어 오류가 발생하는 것이다.
gulp-browserify?
그렇다면 Browserify 에서 bundle() 을 수행한 뒤 반환해주는 Readable stream 을 vinyl object 로 변환해주는 장치가 필요한데, 초기에는 아래와 같이 gulp-browserify 플러그인이 널리 쓰였다.
var gulp = require("gulp"); var browserify = require("gulp-browserify"); var uglify = require("gulp-uglify"); gulp.task("browserify", function () { return gulp.src(["./src/*.js"]) .pipe(browserify()) .pipe(uglify()) .pipe(gulp.dest("./dist")); });
하지만 gulp-browserify 는 현재 블랙리스트에 올라와 있는 상태이다.
단순하게 Gulp의 철학 때문이 아니라, gulp-browserify가 기본적으로 모든 룰을 깨고 있고 이를 수정하기 위한 다양한 Pull Request들도 무시하고 있기 때문에 블랙리스트 상태에 머물고 있다고 한다. gulp-browserify를 사용해선 안되는 상황이라면 다른 방법은 없는걸까?
vinyl-source-stream
Browserify 에서 bundle() 을 수행한 뒤 반환해주는 Readable stream 을 vinyl object 로 변환해주는 목적으로 많이 사용되고 있는 플러그인 중 하나는 vinyl-source-stream 이다. 아래와 같이 vinyl-source-stream 플러그인을 사용해서 Browserify 가 반환한 Readable stream을 vinyl object 로 변경해주면 우리가 처음 목적했던 task 를 정상적으로 실행할 수 있다.
var gulp = require("gulp"); var browserify = require("browserify"); var source = require("vinyl-source-stream"); gulp.task("bundle-javascript", function () { return browserify("src/scripts/main.js") .bundle() .pipe(source("main.js")) .pipe(gulp.dest("dist/scripts/")); });
앞서 vinyl object는 경로와 컨텐츠 등의 메타데이터로 이루어진 일종의 가상 파일 포맷이라고 설명했던걸 기억한다면, vinyl-source-stream 플러그인을 사용할 때 왜 파라메터로 파일명을 주입해주는지 이해할 수 있을 것이다. Browserify가 반환한 Readable stream 으로 컨텐츠는 충족이 되지만 경로는 주입해주지 않으면 알 수 없기 때문이다.
vinyl-buffer
일반적으로 JavaScript 파일은 배포시에 minify를 하게 되는데, 이때 주로 나는 gulp-uglify 를 사용한다. Browserify 를 한 뒤 vinyl object로 변환까지 했으니 간단하게 gulp-uglify 플러그인과 pipe로 연결만 하면 minify까지 순조롭게 마무리될까 싶었지만 아래와 같이 task를 구성할 경우 또다시 오류가 발생하게 된다.
var gulp = require("gulp"); var uglify = require("gulp-uglify"); var browserify = require("browserify"); var source = require("vinyl-source-stream"); gulp.task("bundle-javascript", function () { return browserify("src/scripts/main.js") .bundle() .pipe(source("main.js")) .pipe(uglify()) .pipe(gulp.dest("dist/scripts/")); });
일부 Gulp 플러그인들은 vinyl object를 input 으로 받는 것이 아니라, buffered vinyl object를 input 받아 동작하도록 개발되어 있기도 한데, 그 중 하나가 gulp-uglify 플러그인이다. 따라서 vinyl object 를 buffered vinyl object로 변환해주는 작업이 필요한데, 이 작업을 위해 많이 사용되는 대표적인 플러그인이 vinyl-buffer다. vinyl-buffer 플러그인을 아래와 같이 추가해주면 browserify 한 파일을 minify까지 해서 dist 폴더에 생성하는 task가 완성된다.
var gulp = require("gulp"); var uglify = require("gulp-uglify"); var browserify = require("browserify"); var source = require("vinyl-source-stream"); var buffer = require("vinyl-buffer"); gulp.task("bundle-javascript", function () { return browserify("src/scripts/main.js") .bundle() .pipe(source("main.js")) .pipe(buffer()) .pipe(uglify()) .pipe(gulp.dest("dist/scripts/")); });
마치며
지금까지 Gulp는 스트림 기반이지만 또한 Vinyl 이라는 가상 파일 포맷에도 기반하고 있다는 것과 이를 위해 스트림을 vinyl-source-stream, vinyl-buffer 플러그인 등을 활용해 vinyl object 와 buffered vinyl object 로 변환해서 Gulp 와 연동하는 방법에 대해 살펴 보았다.
여기에서는 Browserify 만 예로 들었지만, 사실 많은 node.js 모듈들은 Gulp를 위해 vinyl object로 변경해주는 기능보다는 기본적으로 stream을 리턴해주는 경우가 더 많을 것이다. 그때마다 gulp 플러그인이 개발되기까지 기다리거나 gulp 플러그인을 직접 만들기보다는 Gulp 가 vinyl 에 기반하고 있다는 점을 이해하고 vinyl-source-stream, vinyl-buffer 등의 플러그인을 적절히 활용한다면 좀더 Gulp를 확장성 있게 사용할 수 있지 않을까 생각해본다.
위에서 다룬 소스는 물론 Gulp 관련 사용 방법을 정리해둔 Github 레포에도 추가해두었다.
https://github.com/eu81273/gulp-step-by-step/tree/master/step09_browserify
함께 읽어보면 좋은 포스트
https://wehavefaces.net/gulp-browserify-the-gulp-y-way-bb359b3f9623