타입스크립트 개발은 Node.js 프로젝트를 만든 다음, 개발 언어를 타입스크립트로 설정하는 방식으로 진행.
> npm init -y
Wrote to......
> npm i -D typescript ts-node @types/node
--save (-S)
프로젝트를 실행할 때 필요한 패키지로 설치.
패키지 정보가 package.json 파일의 "dependencies"에 등록
--save-dev (-D)
프로젝트를 개발할 때만 필요한 패키지로 설치.
패키지 정보가 package.json 파일의 "devDependencies"에 등록
node-dev
nodemon과 같은 watch(실시간 동작) 관련된 모듈
> npm i -D ts-node-dev
> ts-node-dev --respawn --transpile-only index.ts
## 축약 > tsnd --respawn server.ts
// 개발환경 구성, package.json scripts 항목 "scripts": { // 전체 ts 파일들을 컴파일 "build": "tsc", // 실시간 ts 컴파일 "build:watch": "tsc -w", // nodemon 실행 // --watch src/ : src 폴더안에 변화가 있으면 감지해서 재시작함 // --exec ts-node : ts파일을 nodemon으로 실행시킬 수 있게함 "dev": "NODE_ENV=dev nodemon --watch src/ --delay 500ms --exec ts-node src/start.ts", // ts-node-dev 실행 // dev 환경에서 ts파일로 서버 실행 "start:dev": "NODE_ENV=dev ts-node-dev --respawn --transpile-only src/start.ts", // ts를 빌드한 js file로 node 서버 시작 "start": "NODE_ENV=production node build/start.js" }
> tsc --init
--init
으로 생성한 파일에 미리 써져있는건 tsc 명령어의 옵션들(compilerOptions)이고, 이외에도 몇몇 전역 설정 파일 속성들이 존재한다.
tsconfig
의 최상위 속성으로는 compilerOptions
, files
, include
, exclude
, compileOnSave
, extends
가 있다.
컴파일 대상 경로를 정의하는 속성의 우선 순위 files > include = exclude
{
"compilerOptions": {
"target": "es2016"
// ... options
},
"files": ["app.ts", "./utils/math.ts"],
"include": ["src/**/*"] ,
"exclude": ["node_modules", "**/*.(spec|test).ts"],
"extends": "./config/base",
}
아래의 옵션들은 자주 사용하는 옵션들
"compilerOptions": {
"target": "ES6" // 어떤 버전의 자바스크립트로 컴파일 될 것인지 설정
// 'es3', 'es5', 'es2015', 'es2016', 'es2017','es2018', 'esnext' 가능
}
"compilerOptions": {
"lib": ["es5", "es2015.promise", "dom"], // 컴파일 과정에 사용될 라이브러리 파일 설정
/*
es5: global type을 사용하기 위함 (Array, Boolean, Function 등..)
es2015.promise: Promise를 사용하기 위함
dom: setTimeout, console.log 등 dom에서 지원해주는 기능들을 사용하기 위함
*/
}
@Decorator
를 사용하기 위해서는 true로 둬야 작동된다."compilerOptions": {
"experimentalDecorators": true /* ES7 데코레이터(decorators) 실험 기능 지원 설정 */
"emitDecoratorMetadata": true /* 데코레이터를 위한 유형 메타데이터 방출 실험 기능 지원 설정 */
}
function methodDecorator() {
console.log('method');
return function (target: any, property: any, descriptor: any) {};
}
function propertyDecorator(writable: boolean = true) {
console.log('property');
return function (target: any, decoratedPropertyName: any): any {};
}
class Test {
@propertyDecorator()
property = 'property';
@methodDecorator()
test(param1: string) {
console.log('test1');
}
}
react
: .js 파일로 컴파일 된다. (JSX 코드는 React.createElement() 함수의 호출로 변환됨)react-jsx
: .js 파일로 컴파일 된다. (JSX 코드는 _jsx() 함수의 호출로 변환됨)react-jsxdev
: .js 파일로 컴파일 된다. (JSX 코드는 _jsx() 함수의 호출로 변환됨)preserve
: .jsx 파일로 컴파일 된다. (JSX 코드가 그대로 유지됨)react-native
: .js 파일로 컴파일 된다. (JSX 코드가 그대로 유지됨)"compilerOptions": {
"jsx": "preserve" // tsx 파일을 jsx로 어떻게 컴파일할 것인지 'preserve', 'react-native', 'react'
}
.js
vs.jsx
- '확장자로서의 차이가 없다'
- 하지만 JSX는 자바스크립트 표준 문법이 아니기에 논쟁이 있을 수 있다.
- 리액트로 작성한 코드들은 자바스크립트로 변환된되기 때문에 컴포넌트 파일을 만들어 사용하는 리액트의 JSX 관련 코드는
.jsx
로 만들고 나머지 일반 JavaScript 코드들은.js
로 만들면 된다.
.ts
vs.tsx
- 자바스크립트 외에 타입스크립트의 경우,
.ts
와.tsx
확장자로 사용한다..tsx
에서는 JSX 문법을 사용할 수 있지만,.ts
는 JSX 문법이 불가능하다. 오직 TypeScript만 사용 가능하다.
"compilerOptions": {
"module": "commonjs", /* 생성될 모듈 코드 설정: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"moduleResolution": "node", /* 모듈 분석 방법 설정: 'node' (Node.js) 또는 'classic' (TypeScript pre-1.6). */
}
import styled from 'styled-components'; // 노드 패키지일 경우 최상단 경로에 있는 node_modules 폴더를 자동 인식
import { TextField } from '../../../../components/textfield'; // 직접 만든 사용사 소스 파일이 있을 경우 상대경로로 가져와야 한다.
...
../../../../index.ts
같이 import 하는 이런 방식이 좀 예쁘진 않아도 일단 작동하는데는 문제가 없지만, 추후 계속 프로젝트를 작성하거나 리팩토링 할 때 문제가 발생할 수 있다.import styled from 'styled-components';
import { TextField } from '@components/textfield';
...
{
"compilerOptions": {
"baseUrl": "./", // 절대 경로 모듈이 아닌, 모듈이 기본적으로 위치한 디렉토리 설정
"paths": { // 'baseUrl'을 기준으로 상대 위치로 가져오기를 다시 매핑하는 항목 설정
"@components/*": [
"src/components/*" // import {} from '@components/파일' 할때 어느 경로로 찾아들어갈지 paths 설정
],
"@utils/*": [
"src/utils/*"
],
},
"outDir": "./dist", // 컴파일할때 js 파일 위치 경로 지정
},
}
그러나 실제 ts-node를 이용해 소스 파일을 실행해보면 오류가 난다. 왜냐하면 tsconfig.json 설정은 경로 alias만 준것이지 실제 경로를 바꾼게 아니기 때문이다.
따라서 별도로 tsconfig-paths
와 tsc-alias
라는 모듈이 설치해주어야 한다.
tsconfig-paths
: tsconfig.json 내에 baseurl 이나 paths필드에 명시된 모듈을 실제로 호출하게 도와주는 라이브러리 tsc-alias
: js 파일로 컴파일되면 baseurl 이나 paths 필드로 명시된 경로가 그대로 트랜스파일링 되서 js 모듈을 인식할수 없는 현상이 일어나게 되는데, 이 패키지를 이용하면 컴파일된 경로를 상대경로로 바꿔서 해결이 가능하다.> npm i -D tsconfig-paths tsc-alias
{
"compilerOptions": {
"baseUrl": "./"
"path": ...
},
// 전역 속성으로 추가해준다.
"ts-node": {
"require": ["tsconfig-paths/register"]
}
}
조심해야 할점은,
@
로 시작하는 라이브러리를 npm을 통해 받아서 사용할때 alias 명칭과 중복되지 않게 등록해야 한다.
그리고 ts-node index.ts 명령어를 실행해보면, @경로를 잘 찾아들어가는 걸 확인 할 수 있다.
> tsc && tsc-alias
"scripts": {
"build": "tsc --project tsconfig.json && npx tsc-alias -p tsconfig.json",
}
└─ node_modules
├─ @types => 컴파일에 포함
├─ lodash => 컴파일에서 제외
"compilerOptions": {
"typeRoots": ["./my-types"], // 컴파일 목록에 자동으로 포함시킬 패키지들의 기준 디렉토리
// .d.ts 파일을 읽을 폴더를 node_modules/@types에서 node_modules/my-types로 변경
"types": ["node", "jest", "express"], // typeRoots 디렉토리 내에 존재하는 패키지들 중 어떤 패키지들을 컴파일 목록에 포함시킬지
// 만약 types가 지정되어 있지 않다면 typeRoots에 지정된 경로의 패키지들은 전부 컴파일 목록에 자동으로 포함
}
확장자가 .json인 모듈의 import를 허용하는 설정이다.
생각해 보면 Node.js 자바스크립트 프로젝트에서 json 설정 파일을 import해서 자주 사용해온걸 떠올릴 것이다.
타입스크립트도 가능할 것이라 생각하지만, json의 프로퍼티들 모두 타입을 지정해야 사용이 가능하다.
이 옵션은 json의 타입을 자동으로 설정해줘서 따로 변환 작업없이 타입스크립트에서 json 파일 데이터들을 곧바로 사용할 수 있도록 해준다.
{
"name":"홍길동",
"age": 54,
"height": 170,
"married": false
}
import settings from "./test.json" // ./test.json 모듈을 찾을 수 없습니다.
"compilerOptions": {
"resolveJsonModule": true, // ts 파일에서 json imoprt 허용
}
// .josn
const s: {
"name":"홍길동",
"age": 54,
"height": 170,
"married": false
}
// .ts
import settings from "./test.json"
...
const s = settings;
console.log(s.name); // 홍길동
"compilerOptions": {
"esModuleInterop": true, /* 모든 가져오기에 대한 네임스페이스 객체 생성을 통해 CommonJS와 ES 모듈 간의 상호 운용성을 제공 */
}
// esModuleInterop: true일 경우 가능함
import React from "react";
// false인 경우 다음과 같이 import 해야 함
import * from React from "react";
"compilerOptions": {
"allowJs": true,
}
"compilerOptions": {
"allowJs": true, // js 파일들 ts에서 import해서 쓸 수 있는지
"checkJs": true, // 일반 js 파일에서도 에러체크 여부
}
"compilerOptions": {
"outDir": "./dist"
}
"compilerOptions": {
"noEmit": true,
}
"compilerOptions": {
"sourceMap": true, /* 소스맵 '.map' 파일 생성 설정 */
}
"compilerOptions": {
"downlevelIteration": true, //* 'ES5' 혹은 'ES3' 타겟 설정 시 Iterables 'for-of', 'spread', 'destructuring' 완벽 지원 설정 */
}
"compilerOptions": {
removeComments": true, // true면 컴파일된 js에 기존의 주석을 모두 제거
}
"compilerOptions": {
noEmitOnError": true, // 컴파일 에러가 있으면 js 컴파일 하지 않음
}
"compilerOptions": {
"declaration": true, //컴파일시 .d.ts 파일도 자동으로 함께생성 (현재쓰는 모든 타입이 정의된 파일)
}
"compilerOptions": {
"strict": true /* 모든 엄격한 유형 검사 옵션 활성화 */
}
"compilerOptions": {
"strict": true,
"noImplicitAny": false /* 명시적이지 않은 'any' 유형으로 표현식 및 선언 사용 시 오류 발생 */
}
function methodDecorator() {
console.log('method');
return function (target, property, descriptor) {}; // noImplicitAny: false로 하면, 명시적 any를 안써도 에러가 안난다.
}
person['age']
를 에러를 내주지 않는다."compilerOptions": {
"strict": true,
"suppressImplicitAnyIndexErrors": true,
}
const person = {
name: '홍길동',
};
console.log(person['age']); // suppressImplicitAnyIndexErrors: true로 하면 그냥 undefined를 출력한다
"compilerOptions": {
"strict": true,
"noImplicitThis": false, /* 명시적이지 않은 'any'유형으로 'this' 표현식 사용 시 오류 발생 */
}
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
getAreaFunction() {
return function () {
return this.width * this.height; // 원래는 명시적 this를 써야하지만, "noImplicitThis": false 하면 안써줘도 된다.
};
}
}
"compilerOptions": {
"strict": true,
"strictNullChecks": false, /* 엄격한 null 검사 사용 */
}
"compilerOptions": {
"strict": true,
"strictFunctionTypes": false, /* 엄격한 함수 유형 검사 사용 */
}
function fn(x: string) {
console.log('Hello, ' + x.toLowerCase());
}
type StringOrNumberFunc = (ns: string | number) => void;
// Unsafe assignment
let func: StringOrNumberFunc = fn; // 대입이 가능해진다.
func(10); // Unsafe call - will crash
"compilerOptions": {
"strict": true,
"strictPropertyInitialization": false, /* 클래스에서 속성 초기화 엄격 검사 사용 */
}
class UserAccount {
name: string;
accountType = 'user';
address: string | undefined;
email: string; // strictPropertyInitialization: false로 하면 빨간줄이 사라진다.
constructor(name: string) {
this.name = name;
}
}
email!: string; // 어디선가 할당될 것이므로 에러를 무시하라는 의미
"compilerOptions": {
"strict": true,
"strictBindCallApply": false, /* 엄격한 'bind', 'call', 'apply' 함수 메서드 사용 */
}
function fn(x: string) {
return parseInt(x);
}
const n1 = fn.call(undefined, '10');
const n2 = fn.call(undefined, false); // strictBindCallApply: false 로 하면 아무거나 넣을 수 있다.
"compilerOptions": {
"strict": true,
"alwaysStrict": false, /* 엄격모드에서 구문 분석 후, 각 소스 파일에 "use strict" 코드를 출력 */
}
noUnusedLocals
noUnusedParameters
noImplicitReturns
noFallthroughCasesInSwitch
"compilerOptions": {
"skipLibCheck": true, /* 선언 파일 유형 검사 스킵 */
}
include
& exclude
는 컴파일할 경로, 파일들과 혹은 컴파일 제외할 대상을 경로와 파일들을 배열로 정할 수 있다.
include
컴파일 과정에서 포함할 파일
include
는 files
속성과는 달리 exclude
보다 약해 include
에 명시되어 있어도 exclude
에 의해 제외된다.{
"include": ["src/**/*"] // src 디렉터리와 src의 하위 디렉터리에 있는 모든 파일을 컴파일 대상으로 포함한다는 의미
}
exclude
빌드(컴파일) 제외 항목을 기입
exclude
는 아무 설정을 하지 않아도 node_modules, bower_components, jspm_packages, outDir를 기본으로 제외한다. {
"exclude": ["node_modules"]
}
include
, exclude
의 편리한 점은, include
에 *
만 쓰고 확장자를 적지 않아도 자동으로 .ts
.tsx.
.d.ts
만 트랜스파일한다는 것이다.
(allowJS
: true
로 지정되어 있다면 .js
, .jsx
확장자까지 지원이 된다.)
- 자주 사용되는
include
&exclude
의 glob 패턴
- 와일드카드 문자 패턴
*
: 해당 디렉토리의 모든 파일 검색?
: 해당 디렉토리 안에 파일의 이름 중 한 글자라도 맞으면 해당**
: 하위 디렉토리를 재귀적으로 접근(하위 디렉토리의 하위 디렉토리가 존재하는 경우 반복해서 접근)Glob 패턴
특정 규칙을 가진 파일 명 또는 디렉터리 명을 대상으로 검색하는 기술
위 와일드카드에 해당하는 파일의 확장자는js
jsx
ts
tsx
.d.ts
{
"files": ["app.ts", "./utils/math.ts"]
}
// config/base.json
{
"compilerOptions": {
"noImplicitAny": true
}
}
// tsconfig.json
{
"extends": "./config/base"
}
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "ES5",
"moduleResolution": "node",
"outDir": "dist",
"baseUrl": ".",
"sourceMap": true,
"downlevelIteration": true,
"noImplicitAny": false,
"paths": { "*": ["node_modules"] }
},
"include": ["src/**/*"] // ./src와 ./src/utils 디렉터리에 프로젝트의 모든 타입스크립트 소스 파일이 있다는 뜻
}
include
옵션에서 ./src와 ./src/utils 디렉터리에 프로젝트의 모든 타입스크립트 소스 파일 설정
> mkdir -p src/utils
> touch src/index.ts src/utils/makePerson.ts
-p (--parents) 상위 경로도 함께 생성
// src/utils/makePerson.ts
export function makePerson(name: string, age: number) {
return { name: name, age: age };
}
export function testMakePerson() {
console.log(makePerson("Son", 30), makePerson("Kane", 28));
}
// src/index.ts
import { testMakePerson } from "./utils/makePerson";
testMakePerson();
// 수정 전
{
...
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
...
}
ts-node
를 사용하지만, 개발이 완료되면 타입스크립트 소스를 ES5 자바스크립트 코드로 변환해 Node.js로 실행해야한다.// 수정 후
{
...
"main": "src/index.js",
"scripts": {
"dev": "ts-node src",
"bulid": "tsc && node dist"
},
...
}
dev
명령은 개발 중에 src 디렉터리에 있는 index.ts 파일을 샐힝하는 용도로 사용하며,build
명령은 개발이 완료된 후 프로그램을 배포하기 위해 dist 디렉터리에 ES5 자바스크립트 파일을 만들때 사용> npm run dev
> 01@1.0.0 dev
> ts-node src ## dev 명령에 정의된 명렁
{ name: 'Son', age: 30 } { name: 'Kane', age: 28 } ## 코드 실행 결과
> npm run build
> 01@1.0.0 build
> tsc && node dist ## dev 명령에 정의된 명렁
{ name: 'Son', age: 30 } { name: 'Kane', age: 28 } ## 코드 실행 결과
dist/index.js
, dist/utils/makePerson.js
로 컴파일 됨