Gulp와 Express.js(Feat. Typescript)

긴가민가·2023년 1월 16일
0
post-thumbnail

Gulp란?

Gulp는 Node.js의 Task Runner로, 반복되는 일들을 자동화해주는 툴입니다.
Stream 기반의 빌드 시스템이기에 속도가 빠릅니다.

Gulp를 사용해 Typescript + Express + Nodemon의 개발환경을 구축하겠습니다.

💡 Gulp 버전은 4.0 이예요. :)

필요한 서비스 설치 및 설정

Typescript

💡 자세한 설정 방법은 Typescript 초기 설정을 확인해주세요 :)

필자의 tsconfig.json를 공유합니다. (아래 설정을 기준으로 설명할거에요.😁)

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "outDir": "dist", // 빌드된 결과물 저장 경로
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noImplicitAny": true,
    "skipLibCheck": true,
  },
  "include": ["src/**/*.ts"]
}

Express.js

# express 설치
$ npm i express

$ npm i -D @types/express

설치 후 간단한 express 구동 파일을 만듭니다.

// ./src/index.ts (필자의 파일 위치)
import express from "express";

const app = express();

const PORT = 3001;

app.listen(PORT, () => {
  console.log(`Listening on port ${PORT}`);
});

실행해봅시다!

💡 gulp 설치 후 gulpfile.ts 빌드를 위해 gulp 모듈 내부적으로 ts-node 모듈을 사용해요. 따라서 미리 설치를 권장드려요 :)

# ts-node를 설치했다면..
$ ./node_modules/.bin/ts-node [파일 경로]

# ts-node를 설치하지 않았다면..
$ ./node_modules/.bin/tsc -p [파일 경로]

잘 동작합니다.😄

Gulp

Gulp의 각종 기능들은 플러그인을 설치하여 확장성 있는 작업이 가능합니다.

Typescript를 사용하기에 gulp-typescript 플러그인도 함께 설치합니다.

$ npm i -D gulp @types/gulp gulp-typescript

설치 후 gulpfile.ts를 생성하고 아래와 같이 작성해주세요.

import { dest, lastRun, series, src } from "gulp";
import ts from "gulp-typescript";

const tsProject = ts.createProject("tsconfig.json");
const tsConfig = tsProject.config;
const OUT_PATH = tsConfig.compilerOptions.outDir as string;
const TRANSPILE_PATH = tsConfig.include || "./src/**/*.ts";

const transpile = () =>
  src(TRANSPILE_PATH, {
    allowEmpty: true,
    since: lastRun(transpile),
    sourcemaps: true,
  })
    .pipe(tsProject())
    .pipe(
      dest(OUT_PATH, {
        sourcemaps: ".",
      }),
    );

export default series(transpile);

위에 사용한 Gulp API에 대한 설명입니다.

  • src : 작업을 할 파일들 지정
    • since : 파일들을 가져올 때 타임스탬프를 체크하여 변경된 파일만 가져오기
    • sourcemaps : 추후 디버깅을 위한 소스맵 활성화 여부
    • allowEmpty : 작업할 대상이 파일 하나일 때, 실제 파일이 존재하지 않으면 에러 발생 여부
  • lastRun : 작업이 완료된 마지막 시간을 검색
  • dest : 작업한 결과물의 저장 경로 지정
  • series : 나열된 순서대로 처리

이제 실행해보겠습니다.

$ ./node_modules/.bin/gulp

빌드가 잘 됩니다!🙂

Nodemon

이제 서비스 실행을 위해 gulp-nodemon을 설치해주세요.

$ npm i -D gulp-nodemon @types/gulp-nodemon

gulpfile.ts 파일을 아래와 같이 수정해주세요.

import { dest, lastRun, series, src } from "gulp";
import nodemon from "gulp-nodemon";
import ts from "gulp-typescript";

const tsProject = ts.createProject("tsconfig.json");
const tsConfig = tsProject.config;
const OUT_PATH = tsConfig.compilerOptions.outDir as string;
const TRANSPILE_PATH = tsConfig.include || "./src/**/*.ts";

const transpile = () =>
  src(TRANSPILE_PATH, {
    allowEmpty: true,
    since: lastRun(transpile),
    sourcemaps: true,
  })
    .pipe(tsProject())
    .pipe(
      dest(OUT_PATH, {
        sourcemaps: ".",
      }),
    );

const start = () =>
  nodemon({
    delay: 500,
    script: OUT_PATH,
  });

export default series(transpile, start);

위에 사용한 nodemon 옵션에 대한 설명입니다.

  • delay : 재시작 간격 (ms 단위)
  • script : 실행 파일

이제 실행해보겠습니다.

$ ./node_modules/.bin/gulp

잘 동작합니다.🙂

Nodemon watch vs Gulp watch

둘 다 변경을 감지하여 자동 서버 재시작을 구현할 수 있습니다.

Nodemon watch

nodemon은 옵션으로 watch 설정을 할 수 있습니다.

import { dest, lastRun, series, src } from "gulp";
import nodemon from "gulp-nodemon";
import ts from "gulp-typescript";

const tsProject = ts.createProject("tsconfig.json");
const tsConfig = tsProject.config;
const OUT_PATH = tsConfig.compilerOptions.outDir as string;
const TRANSPILE_PATH = tsConfig.include || "./src/**/*.ts";

const transpile = () =>
  src(TRANSPILE_PATH, {,
    allowEmpty: true,
    since: lastRun(transpile),
    sourcemaps: true
  })
    .pipe(tsProject())
    .pipe(
      dest(OUT_PATH, {
        sourcemaps: ".",
      }),
    );

const start = () => {
  const nodemonStream = nodemon({
    delay: 500,
    ext: "ts",
    script: OUT_PATH,
    watch: ["src"],
  });

  nodemonStream.on("restart", () => {
  	console.log("Restart event nodemon");
    
    transpile();
  });
};

export default series(transpile, start);
  • ext : 감지할 확장자
  • watch : 변경을 감지할 폴더

src 하위 파일이 수정되면 restart 이벤트가 발생하고, 변경내용 빌드 후 재시작합니다.
확장자는 기본 js이기 때문에, ts로 선언해야 정상 동작합니다.

Gulp watch

Gulp는 watch를 내장 API로 제공합니다.

import { dest, lastRun, parallel, series, src, watch } from "gulp";
import nodemon from "gulp-nodemon";
import ts from "gulp-typescript";

const tsProject = ts.createProject("tsconfig.json");
const tsConfig = tsProject.config;
const OUT_PATH = tsConfig.compilerOptions.outDir as string;
const TRANSPILE_PATH = tsConfig.include || "./src/**/*.ts";

const transpile = () =>
  src(TRANSPILE_PATH, {
    since: lastRun(transpile),
    sourcemaps: true,
    allowEmpty: true,
  })
    .pipe(tsProject())
    .pipe(
      dest(OUT_PATH, {
        sourcemaps: ".",
      }),
    );

const monit = () => {
  const watcher = watch(TRANSPILE_PATH, transpile);

  watcher.on("change", (path) => {
    // 파일 내용 변경 이벤트
    console.log(`File ${path} was changed`);
  });

  watcher.on("add", (path) => {
    // 파일 추가 이벤트
    console.log(`File ${path} was added`);
  });

  watcher.on("unlink", (path) => {
    // 파일 삭제 이벤트
    console.log(`File ${path} was removed`);
  });
};

const start = () =>
  nodemon({
    delay: 500,
    script: OUT_PATH,
  })

export default series(transpile, parallel(monit, start));
  • watch : 변경을 감지할 폴더
  • parallel : 순서에 상관없이 병렬 처리

monit(변경 감지)와 start(서버 시작)를 병렬로 처리하여, 하위 파일들(필자 기준 src)이 변경되면, 변경내용 빌드 후 재시작합니다.

뭐가 더 변경 감지가 빠를까?

두 개를 합쳐서 실행 후 수정해보겠습니다.

import { dest, lastRun, parallel, series, src, watch } from "gulp";
import nodemon from "gulp-nodemon";
import ts from "gulp-typescript";

const tsProject = ts.createProject("tsconfig.json");
const tsConfig = tsProject.config;
const OUT_PATH = tsConfig.compilerOptions.outDir as string;
const TRANSPILE_PATH = tsConfig.include || "./src/**/*.ts";

const transpile = () =>
  src(TRANSPILE_PATH, {
    allowEmpty: true,
    since: lastRun(transpile),
    sourcemaps: true,
  })
    .pipe(tsProject())
    .pipe(
      dest(OUT_PATH, {
        sourcemaps: ".",
      }),
    );

const monit = () => {
  const watcher = watch(TRANSPILE_PATH, transpile);

  watcher.on("change", (path) => {
    // 파일 내용 변경 이벤트
    console.log(`File ${path} was changed`);
  });

  watcher.on("add", (path) => {
    // 파일 추가 이벤트
    console.log(`File ${path} was added`);
  });

  watcher.on("unlink", (path) => {
    // 파일 삭제 이벤트
    console.log(`File ${path} was removed`);
  });
};

const start = () => {
  const nodemonStream = nodemon({
    delay: 500,
    ext: "ts",
    script: OUT_PATH,
    watch: ["src"],
  });

  nodemonStream.on("restart", () => {
    console.log("Restart event nodemon");

    transpile();
  });
};

export default series(transpile, parallel(monit, start));

아래는 실행 결과입니다.

로그의 순서로 보았을 때, Gulp의 watcher가 먼저 호출되고, 그 후 nodemon restart event가 발생하였습니다.
이 테스트를 통해 Gulp의 watch가 조금 더 빠르다고 판단하였습니다.

필자는 Stream 기반의 Gulp 기법을 사용해 속도가 좀 더 빠른 Gulp의 watch 기능을 사용합니다.

Script 추가

명령어 입력할 때 마다 ./node_modules/.bin/~ 입력하기 귀찮습니다.
package.json의 script에 작성하면 쉽게 사용할 수 있어요.😄

"scripts" {
  ...,
  "dev": "gulp"
}

이제 테스트 해볼까요?

$ npm run dev

잘 동작하네요:)


의견은 언제든 댓글로 남겨주세요. 🙂

profile
미래의 내가 참고하려고 모아가는 중 :)

0개의 댓글