package.json은 npm init을 통해 생성한다.
package.json은 node.js가 관리하는 패키지 관리 파일로서 프로젝트 정보와 관련 패키지가 기록된다.
다양한 오픈소스 패키지를 npm install 혹은 npm i 로 설치한다.
이 옵션으로 설치하면, 해당 패키지 정보가 package.json 파일에 자동으로 기록된다.
-g 옵션으로 설치하면 전역에 설치할 수 있다.
그러나, 이 프로젝트를 전달받아서 사용하는 다른 개발자의 컴퓨터에는 두 패키지가 전역에 설치되지 않았을 수도 있다.
따라서, - D 옵션을 이용해서 설치하는 것이 좋다.
npm i -D typescript ts-node
타입스크립트의 컴파일러는 자바스크립트와 달리, 타입이 명시적으로 설정되어 있어야만 코드가 문법에 맞게 작성되었는지를 검증해 코드를 동작시킨다.
이 때문에, 자바스크립트로 개발된 chance와 ramda와 같은 라이브러리들은 추가로 @types/chance, @types/ramda 와 같은 타입 라이브러리들을 제공해야 한다.
프로젝트를 만드는 과정에서 패키지를 설치하면, 자연스럽게 프로젝트 디렉터리 아래에 node_modules라는 디렉터리가 생기고, 여기에 해당 패키지가 설치된다.
그러나, 여러 패키지를 설치하다보면 node_modules의 용량이 늘어난다.
이때, node_modules을 삭제하고 다른 사람에게 전달하면, 프로젝트 이용자는 package.json파일이 있는 디렉터리에서 npm i 를 실행하면 package.json에 등록된 패키지들이 node_modules디렉토리에 자동으로 설치된다.
tsconfig.json는 타입스크립트 컴파일러 설정 파일이다. 이 파일은 tsc —init명령으로 만들수 있다.
기본적으로 이 파일에는 실제 개발을 진행하는 데 필요한 많은 옵션이 비활성화 되어있다.
따라서, 옵션을 알맞게 설정하여 간략하게 하자.
이 책에서는 이렇게 되어있다.
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es2015",
"moduleResolution": "node",
"outDir": "dist",
"baseUrl": ".",
"sourceMap": true,
"downlevelIteration": true,
"noImplicitAny": false,
"paths": { "*": ["node_modules/*"] }
},
"include": ["src/**/*"]
}
tsconfig.json에서 14행인 "include": ["src/*/"] 는 ./src 와 ./src/utils 디렉토리에 이 프로젝트의 모든 타입스크립트 소스 파일이 있다는 뜻이다.
src/utils 디렉토리르 만들고 실습에 필요한 소스 파일을 만들자.
mkdir -p src/utils
touch src/index.ts src/utils/makePerson.ts
// src/utils/makePerson.ts
export function makePerson(name:string, age: number){
return {name:name, age:age}
}
export function testMakePerson(){
console.log(
makePerson('Jane', 22),
makePerson('Jack', 33)
)
}
// src/index.ts
import { testMakePerson } from "./utils/makePerson"
testMakePerson()
node 나 ts-node로 소스파일을 실행하려면 ts-node ./src/index.ts 명령을 사용한다.
하지만, 소스파일명이 index면 ts-node ./src로 실행할 수 있다.
이는 프로젝트 시작 함수 (엔트리 함수)가 있는 소스 파일명은 보통 index라고 짓기 떄문이다.
타입스크립트 프로젝트를 개발할 때는 ts-node를 사용하지만, 막상 개발이 완료되면 타입스크립트 소스코드를 ES5 자바스크립트 코드로 변환해 node로 실행해야한다.
그러기 위해서 다음과 같은 명령을 추가해준다.
{
"name": "ch02-1",
"version": "1.0.0",
"description": "",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
**"dev": "ts-node src",
"build": "tsc && node dist"**
},
... 생략 ...
dev는 index.ts를 실행하기 위해서, 그리고 build는 개발이 완료된 후 프로그램을 배포하기 위해 dist 디렉토리에 ES5 자바스크립트 파일을 만들 떄 사용한다.
이 명령들은 다음과 같이 실행한다.
npm run 명령
// npm run dev
// npm run build
타입스크립트에서 index.ts와 같은 소스파일을 모듈이라고 한다.
여러 모듈을 하나의 파일로 합쳐도 되지만, 보통 코드 관리와 유지, 보수를 편리하게 하려고 모듈마다 고유한 기능을 구현하는 방식으로 소스코드를 분할한다. ( 모듈화 )
이때, 어떤 모듈이 어디있는지를 알려주기 위해, export 와 import를 사용한다.
export는 기능을 제공하는 쪽에서 사용하고, import는 딴 모듈의 기능을 이용하는 쪽에서 사용한다.
index.ts 파일을 모듈화 하기 위해 src 디렉토리 아래에 person 디렉토리를 생성 후 Person.ts라는 이름의 파일을 만든다.
// src/person/Person.ts
class Person implements IPerson {
constructor(public name: string, public age: number = makeRandomNumber()) {}
}
const makePerson = (name: string,
age:number = makeRandomNumber()): IPerson => ({name, age});
이렇게 되면 index.ts 파일이 간단해진다.
// index.ts
const testMakePerson = (): void => {
let jane: **IPerson** = **makePerson**("Jane");
let jack: **IPerson** = new Person("Jack");
console.log(jane, jack);
}
testMakePerson();
그러나 여기서 오류가 발생한다.
IPerson과 makePerson이라는 심벌의 의미를 알 수 없기 때문이다.
여기서, export와 import가 필요한 것이다.
index.ts파일이 동작하려면, Person.ts 파일에 선언한 IPerson과 makePerson심벌을 index.ts로 전해야 한다. 이때, export 키워드가 쓰인다.
// src/person/Person.ts
**export default** class Person implements IPerson {
constructor(public name: string, public age: number = makeRandomNumber()) {}
}
**export** const makePerson = (name: string,
age:number = makeRandomNumber()): IPerson => ({name, age});
import 키워드는 아래처럼 쓰인다.
import { 심벌 목록 } from '파일의 상대 경로'
index.ts 파일을 열고 다음처럼 첫 줄에 import 구문을 추가한다.
// src/index.ts
import IPerson from './person/IPerson'
import Person, {makePerson} from './person/Person'
const testMakePerson = (): void => {
let jane: IPerson = makePerson("Jane");
let jack: IPerson = new Person("Jack");
console.log(jane, jack);
}
testMakePerson();
심벌을 사용하는 방법도 존재한다.
import * as 심벌 from '파일 상대 경로'
이런식으로 사용한다.
// src/person/Person.ts
import * as U from '../utils/makeRandomNumber'
import IPerson from './IPerson'
export default class Person implements IPerson {
constructor(public name: string, public age: number = **U.makeRandomNumber()**) {}
}
export const makePerson = (name: string,
age:number = **U.makeRandomNumber()**): IPerson => ({name, age});
타입스크립트는 자바스크립트와 호환하기 위해 export default 구문을 제공한다.
// src/person/IPerson.ts
export default interface IPerson {
name: string,
age: number
}
export default 키워드는 한 모듈이 내보내는 기능 중 오직 한 개만 붙일 수 있다.
export default가 붙은 기능은 import문으로 불러올 떄 중괄호 {} 없이 사용할 수 있다.
이제, Person.ts를 다음과 같이 수정하자.
// src/person/Person.ts
import {makeRandomNumber} from '../utils/makeRandomNumber'
import IPerson from './IPerson'
export default class Person implements IPerson {
constructor(public name: string, public age: number = makeRandomNumber()) {}
}
export const makePerson = (name: string,
age:number = makeRandomNumber()): IPerson => ({name, age});
2행은 IPerson.ts에서 export default 키워드로 지정한 IPerson을 중괄호 없이 import문에 지정했다.
지금까지의 내용을 모두 반영한 index.ts모습은 이렇다.
// src/index.ts
import IPerson from './person/IPerson'
import Person, {makePerson} from './person/Person'
const testMakePerson = (): void => {
let jane: IPerson = makePerson("Jane");
let jack: IPerson = new Person("Jack");
console.log(jane, jack);
}
testMakePerson();
실습을 위해 chance와 ramda 패키지를 설치하자.
npm i -S chance ramda
npm i -D @types/chance @types/ramda
chance패키지는 그럴듯한 가짜 데이터를 만들어 주는데 사용되며, ramda는 함수형 유틸리티 패키지 이다.
이 패키지를 이용하면 index.ts는 다음과 같이 바뀐다.
// src/index.ts
import IPerson from './person/IPerson'
import Person from './person/Person'
import Chance from 'chance'
import * as R from 'ramda'
const chance = new Chance()
let persons: IPerson[] = R.range(0, 2).map(
(n: number) => new Person(chance.name(), chance.age())
)
console.log(persons)
chance는 Chance 클래스 하나만 export default 형태로 제공하므로 03행 같이 쓰인다.
그리고 ramda패키지는 다양한 기능을 제공하므로 04행 처럼 쓰인다.
!!!!!!!!!!!!!!!!!!!!!!1 tsc — help 해볼것
앞에서 만든 tsconfig.json파일은 다음처럼 되어있다.
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es2015",
"moduleResolution": "node",
"outDir": "dist",
"baseUrl": ".",
"sourceMap": true,
"downlevelIteration": true,
"noImplicitAny": false,
"paths": { "*": ["node_modules/*"] }
},
"include": ["src/**/*"]
}
compilerOptions항목은 tsc 명령 형시겡서 옵션을 나타내고, include 항목은 대상 파일 목록을 나타낸다.
src/*/ 은 src 디렉토리는 물론 src의 하위 디렉토리에 있는 모든 파일을 컴파일 대상으로 포함한다는 의미다.
각각 콜론을 기준으로 "키 : 키값"으로 되어있다.
타입스크립트 소스코드가 컴파일 되어 만들어진 ES5 자바스크립트 코드는 웹 브라우저와 노드제이에스 양쪽에서 모두 동작해야한다.
그러나, 둘은 물리적인 동작방식이 달라서 모듈로 분할된 자바스크립트 코드도 양쪽에서 다르게 동작한다.
자바스크립트 모듈은 웹 브라우저에서 AMD방식으로, 노드제이에스처럼 웹 브라우저가 아닌 환경에서는 CommonJS방식으로 동작한다.
tsconfig.ts 파일에서동작 대상 플랫폼을 구분해 그에 맞는 모듈 방식으로 컴파일하려는 목적을 설정한다.
module 키의 값이 commonJS면 노드제이에스에서 동작하는 것이므로, modeResolution키값은 항상 node로 설정핟나. 반면 module 키값이 amd 이면 moduleResolution키 값은 classic으로 설정한다.
트랜스파일할 대상 자바스크립트 버전을 설정한다. 대부분 es5를 키값으로 하나, 최신 버전 노드제이에스를 쓴다면 es6으로 할 수도 있다.
baseUrl과 outDir키에는 트랜스 파일된 ES5 자바스크립트 파일을 저장하는 디렉터리를 설정한다. 그래서, 현재 디렉터리를 의미하는 ".'로 baseUrl키값을 설정하는게 보통이다. OutDir키는 baseUrl설정값을 기준으로 했을 때 하위 디렉터리의 이름이다.
이 키에는 소스파일의 import 문에서 from 부분을 해석할 때 찾아야하는 디레거리를 설정한다.
import 문이 찾아야하는 소스가 외부 패키지면 node_modules 디렉토리에서 찾아야 하므로, node_modules/* 도 포함한다.
오픈소스 자바스크립트 라이브러리 중에는 웹 브라우저에서 동작한다는 가정으로 만들어 진것들이 있다. 이들은 CommonJS방식으로 동작하는 타입스크립트 코드에 혼란을 줄 수 있다.
예를 들어, 02-2에서 chance가 바로 AMD방식을 전제로 해서 구현된 라이브러리이다.
따라서, chance를 실행하기 위해서는 esModuleInterop키 값을 반드시 true로 설정해야 한다.
sourceMap키 값이 true면 트랜스파일 디렉터리에는 .js파일 이외에도 .js.map 파일이 만들어진다.
이 소스맵 파일은 변환된 자바스크립트 코드가 타입스크립트 코드의 어디에 해당하는지 알려준다.
이 소스맵 파일은 주로 디버깅 할 때 사용된다.
이 책의 06장에서는 생성기 라는 타입스크립트 구문을 설명하는데, 이 생성기 구문이 정상적으로 동작하려면, downlevelIteration키가 true로 되어있어야한다.
타입스크립트의 컴파일러는 기본적으로 f(a, b)처럼 매개변수 a,b에 타입을 명시하지 않는경우 자동으로 any타입으로 설정한다. 이런 코드는 타입스크립트를 사용하는 의미를 퇴색시키므로, 이런 코드가 있을 때 문제가 있음을 알려준다.