서론

저번 포스트에서 크롤러에서 필요한 모듈들을 설치하는 것까지 진행했었죠? 이번 포스트에서는 프로젝트 세팅을 이어서 해볼 것입니다.

이번 포스트에서 할 작업들은 다음과 같습니다.

  1. tsconfig 세팅
  2. tslint 세팅
  3. jest 세팅

tsconfig 세팅

Typescript를 해보신 분들도 계시겠지만, 이전 포스트에서도 말했다시피 저는 초보자들의 관점에서 포스트를 작성하기 때문에 모르시는 분들을 위해 Typescript에 대해서 설명을 잠시 해드리도록 하겠습니다.

Typescript는 간단하게 설명하면 동적 타입언어인 자바스크립트에서 변수에 타입을 지정할 수 있는 형태라고 이해하시면 될 것 같습니다. Typescript가 작동하는 방식을 자세하게 설명하자면 다음과 같습니다.

Typescript는 Typescript 자체로 실행되지 않고, 자바스크립트 파일로 트랜스파일(컴파일)되어서 자바스크립트의 형태로 동작하게 됩니다. 그렇다면 우리가 Typescript를 개발할 때에는 Typescript를 자바스크립트로 변환해줄 트랜스파일러(컴파일러)가 필요하겠죠? 그게 바로 typescript 모듈입니다.

그래서 typescript 모듈을 통해서 Typescript를 자바스크립트로 트랜스파일을 하는데
다음과 같은 명령어로 트랜스파일이 진행됩니다.

> tsc a.ts

간단하죠? 그런데 저희가 만들 Typescript 파일들이 많아지면 저 트랜스파일 명령어를 어느 세월에 다 쓰고 있나요? 그래서 필요한 게 바로 tsconfig.json입니다.

그러면 프로젝트 내에서 tsconfig를 만들어봅시다! 프로젝트 내에서 tsconfig.json 파일을 직접 만들 수도 있지만 typescript 모듈에서 간단한 명령어를 통해서 잘 세팅된 tsconfig.json 파일을 만들 수 있습니다.

C://projects/my-first-ts-express-croller
> npx tsc --init
message TS6071: Successfully created a tsconfig.json file.

npx에 대한 설명은 이 글에서 정말 잘 되어 있으니 생략하도록 하겠습니다. 참고로 npx는 npm 버전이 5.2.0 이상이어야 사용할 수 있으니 참고해주세요.

아무튼 저렇게 명령어를 입력해주면 위의 메시지처럼 tsconfig.json 파일이 생겼다고 메시지가 뜰 겁니다. 이제 그 파일을 코드 에디터로 열어보면 다음과 같은 형태를 가지게 될 것입니다.

// C://projects/my-first-ts-express-croller/tsconfig.json
{
  "compilerOptions": {
    /* Basic Options */
    // "incremental": true,                   /* Enable incremental compilation */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation. */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
  }
}

옵션이 정말 많은데요. 여기서 제가 사용할 옵션은 다음과 같습니다.

// C://projects/my-first-ts-express-croller/tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",
        "esModuleInterop": true,
        "allowSyntheticDefaultImports": true,
        "target": "es6",
        "noImplicitAny": true,
        "moduleResolution": "node",
        "sourceMap": true,
        "outDir": "dist",
        "baseUrl": ".",
        "paths": {
            "*": [
                "node_modules/*",
                "src/types/*"
            ]
        }
    },
    "include": [
        "src/**/*"
    ]
}

tsconfig.json에 대한 자세한 설명은 docs를 읽어보시면 되십니다. 사실 이렇게 할 작성할 거라면 굳이 tsc --init을 할 필요가 없었지만 자세한 설명이 적혀있으니까 여러분이 원하는 옵션을 더 잘 선택할 수 있겠죠?

tslint 세팅

이제 tslint를 설정할 시간입니다! 자바스크립트로 개발을 하셨던 분들은 eslint에 대해서 들어본 적이 있으실 겁니다. tslint는 Typescript의 린터입니다. 하지만 2019년 2월에 tslint 개발 팀에서 2019년 안에 개발을 중단할 것이라는 발표를 하게 되었습니다. eslint와 합쳐져서 eslint에서 Typescript와 자바스크립트 모두를 지원할 수 있는 표준 린터로 만들 계획이라고 하네요. 하지만 아직! 개발 중단이 되지 않았으니 마음껏 tslint를 즐겨봅시다!

tslint는 eslint처럼 다양한 스타일들이 존재합니다. airbnb, prettier, google 등 다양한 스타일들이 있는데 저는 airbnb 스타일을 선호하기 때문에 airbnb 스타일을 상속해서 사용하겠습니다. 그러면 tslint-config-airbnb 모듈을 설치해봅시다. 꼭 devDependency로 설치해주시구요.

// C://projects/my-first-ts-express-croller
> npm install -D tslint-config-airbnb

이제 tslint.json file을 만들어서 다음과 같이 내용을 작성하면 우리는 airbnb와 같은 코드 스타일로 코딩하게 되는거죠!

// C://projects/my-first-ts-express-croller/tslint.json
{
  "extends": "tslint-config-airbnb"
}

만약에 자신이 airbnb의 코드 스타일과 자신의 스타일 안맞는다고 느껴지는 규칙이 있다면 다음과 같이 작성해주시면 됩니다.

{
  "extends": "tslint-config-airbnb",
  "rules": {
    "ter-arrow-parens": false
  }
}

자세한 tslint 규칙은 이 링크를 참고해주세요.

jest 설정

이제 우리가 마지막으로 해야할 설정입니다. jest가 무엇인지 모르시는 분들은 이 링크를 참고해주세요. jest는 지난 번에 우리가 사용할 모듈들에 대해서 잠깐 설명할 때 간단하게 테스트 프레임워크라고 말씀드렸습니다. 사실 테스트를 한 번도 안해보신 분들은 테스트가 정확히 어떤 개념인지 알기 힘들거에요. 직접 경험해보시면 좀 더 어떤 개념인지 와닿으실 겁니다. 따라서 지금 단계에서는 우리가 만드는 어플리케이션이 조금 더 안정적이고 확실한 검증이 되어있음을 증명하기 위해서 하는 것이다라고 이해하시면 될 것 같습니다.

그래서 다시 본론으로 돌아와서 jest를 세팅해보기 이전에! jest는 자바스크립트 테스트 프레임워크이기 때문에 우리가 작성할 Typescript 파일들을 직접 실행할 수 없습니다. 하지만 그렇다고 자바스크립트로 트랜스파일된 결과를 가지고 테스트하기엔 디버깅이 어렵죠. 그래서 필요한 게 바로 ts-jest모듈 입니다. 이 모듈은 우리가 작성한 Typescript 파일들을 먼저 컴파일하고 나온 자바스크립트 코드를 jest에서 테스트를 실행한 이후에 발생한 에러들이 Typescript 파일의 어떤 곳에서 발생하는 것인지 알려주는(sourceMap) 모듈입니다. 더 자세히 아시고 싶다면 이 링크를 참고해보세요. 그럼 설치해봅시다. 꼭 devDependency로 설치해주세요.

// C://projects/my-first-ts-express-croller/
> npm install -D ts-jest

이제 jest.config.js 파일만 작성하면 오늘의 세팅은 모두 끝이 납니다! 파일은 다음과 같이 작성해주세요.

module.exports = {
  globals: {
    "ts-jest": {
      tsConfig: "tsconfig.json"
    }
  },
  moduleFileExtensions: ["ts", "js"],
  transform: {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
  testMatch: ["**/test/**/*.test.(ts|js)"],
  testEnvironment: "node"
};

여기 위에서 보이는 "**/test/**/*.test.(ts|js)" 이런 문자열들을 개발하면서 많이 보게 되실텐데요. 바로 glob file pattern이라고 하는데요. 이에 대해서는 따로 포스트로 작성해서 공유해드리겠습니다.

이제 모든 준비는 끝이 났습니다! 이제 어서 크롤러를 만들어서 재밌는 일들을 해보자구요!