개발/타입스크립트

TypeScript로 블록체인 만들기(4)

TTOLBE 2022. 12. 30. 14:26

Block Chain

이 글은 노마드 코더의 타입스크립트 무료 강의를 보고 노트 정리를 한 글입니다.
강의링크

5.0 Introduction

5.1 Targets

프로젝트 폴더 생성후 npm init -y를 통해 새 package.json 파일을 생성해준다.

package.json을 다음과 같이 수정해준다.


{
  "name": "blockchain",
  "version": "1.0.0",
  "description": "",
  "scripts": {

  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

그리고 타입스크립트를 설치해주자.

터미널에 npm install -D typescript 라고 적으면 된다. 그러면 package.json에 devDependencies 가 추가 된다.

프로젝트 폴더에 src 폴더를 생성 후 index.ts 파일을 그 안에 생성하자. 그리고 그안에 함수를 하나 만들어 컴파일이 되는지 테스트 해보자.


const hello = () => 'hi';

tsconfig.json 파일을 생성하자.


{
  "include": ["src"], //컴파일할 타입스크립트 파일 위치
  "compilerOptions": {
    "outDir": "build" //컴파일한 파일의 위치
  }
}

그리고 package 파일에 build script를 추가해주자.


{
  "name": "blockchain",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "build": "tsc"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^4.9.4"
  }
}

이제 터미널에 npm run build 라고 적으면 build 폴더가 생성 되고 그안에 자바스크립트로 컴파일된 파일이 생긴다.


var hello = function () { return 'hi'; };

위와 같이 컴파일 된 것을 확인 할수 있다. 자바스크립트로 변환하면서 더 낮은 버전의 자바스크립트가 되었는데 이는 호환성을 위해서다. 자바스크립트의 버전을 정하려면 tsconfig 파일을 아래와 같이 수정하면 된다.


{
  "include": ["src"], //컴파일할 타입스크립트 파일 위치
  "compilerOptions": {
    "outDir": "build", //컴파일한 파일의 위치
    "target": "ES3" //컴파일한 자바스크립트 버전
  }
}

대부분의 브라우저는 es6을 지원하기 때문에 es6을 쓰는게 추천 된다고 한다.

5.2 Lib Configuration

lib는 합쳐진 라이브러리의 정의 파일을 특정해주는 역할을 한다. 또한 자바스크립트 코드가 어디서 작동하는지 알려준다. tsconfig 파일을 열자.

{
  "include": ["src"], //컴파일할 타입스크립트 파일 위치
  "compilerOptions": {
    "outDir": "build", //컴파일한 파일의 위치
    "target": "ES6", //컴파일한 자바스크립트 버전
    "lib": ["ES6", "DOM"] //작동환경(es6, 브라우저)
  }
}

"DOM"을 추가해줌으로서 타입스크립트는 자체적으로 브라우저 전용 API, 함수등의 자동완성을 지원해준다.

5.3 Declaration Files

패키지와 라이브러리를 사용 할 때 대부분 자바스크립트로 만들어진 것들을 사용하게 된다. 그럴 땐 타입스크립트에게 자바스크립트 파일의 모양을 설명해 줘야 한다. src 폴더에 myPackage.js 파일을 만들어보자.


export function init(config) {
  return true;
}

export function exit(code) {
  return code + 1;
}

이제 index.ts 파일로 가서 위 파일이 node의 모듈 인 것처럼 사용해보자.


import { init } from 'myPackage';

init();

아직 strictmode를 설정하지 않았으므로 작동은 한다. 그러므로 tsconfig에서 strictmode를 활성시키자.

{
  "include": ["src"], //컴파일할 타입스크립트 파일 위치
  "compilerOptions": {
    "outDir": "build", //컴파일한 파일의 위치
    "target": "ES6", //컴파일한 자바스크립트 버전
    "lib": ["ES6", "DOM"], //작동환경(es6, 브라우저)
    "strict": true //strict mode
  }
}

위와 같이 변경해주면 index.ts 에 선언 파일을 찾을 수 없다고 에러가 뜰 것이다. 선언 파일을 만들어주자. src에 myPackage.d.ts라는 새 파일을 만들자. 여기서 d.ts 파일은 정의 파일을 의미 한다.


interface Config {
  urls: string;
}

declare module 'myPackage' {
  function init(config: Config): boolean;
}

위와 같이 모듈을 정의 해주자. 그럼 더이상 init을 import 하는데에 오류가 발생치 않는다.


import { init } from 'myPackage';

init({
  urls: true,
});

이번에는 exit도 추가 해주자.


interface Config {
  url: string;
}

declare module 'myPackage' {
  function init(config: Config): boolean;
  function exit(code: number): number;
}

이제 index.ts에서는 두 함수모두 사용 가능하다.


import { init, exit } from 'myPackage';

init({
  url: 'true',
});

exit(1);

5.4 JSDoc

이미 가지고 있는 자바스크립트 파일을 타입스크립트에서 사용하는 방법을 알아보자. 우선 myPackage.d.ts 파일을 삭제하고 index.ts에서의 import 방식을 변경해주자.


import { init, exit } from './myPackage';

그러면 오류가 난다.
tsconfig 파일을 아래와 같이 수정해주자.


{
  "include": ["src"], //컴파일할 타입스크립트 파일 위치
  "compilerOptions": {
    "outDir": "build", //컴파일한 파일의 위치
    "target": "ES6", //컴파일한 자바스크립트 버전
    "lib": ["ES6", "DOM"], //작동환경(es6, 브라우저)
    "strict": true, //strict mode
    "allowJs": true //자바스크립트 허용
  }
}

이제 자유롭게 자바스크립트 파일을 타입스크립트에서도 사용가능하다. 타입스크립트에 넣은 자바스크립트도 보호기능이 작동한다. 자바스크립트 맨위에 주석을 추가하면 된다.


//@ts-check

export function init(config) {
  return true;
}

export function exit(code) {
  return code + 1;
}

뜨는 에러들을 일일히 고칠 필요는 없고, JSDoc 이란 걸 사용하면 된다. JSDoc이란 코멘트로 이루어진 문법이고, 이걸 사용하면 타입스크립트가 검사할수 있다.

//@ts-check
/**
 * Initializes the project
 * @param {*} config
 * @returns
 */
export function init(config) {
  return true;
}

export function exit(code) {
  return code + 1;
}

/**을 적으면 vs 코드에서 제공하는 snippet을 사용가능하다.

//@ts-check
/**
 * Initializes the project
 * @param {object} config
 * @param {boolean} config.debug
 * @param {string} config.url
 * @returns {boolean}
 */
export function init(config) {
  return true;
}

/**
 *
 * @param {number} code
 * @returns {number}
 */

export function exit(code) {
  return code + 1;
}

위와 같이 @뒤에 param을 정해주면 타입스크립트가 자바스크립트를 읽는데 도와준다. 또한 코멘트기 때문에 이미 짜여진 코드가 손상될 일은 없다.

5.5 Blocks

블록체인을 만들어보자. 우선 package.json 파일에 start명령을 추가해주자.

{
  "name": "blockchain",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^4.9.4"
  }
}

이제 터미널에 npm run build && npm run start 라고 적으면 실행된다. 하지만 너무 비효율적이므로 npm i -D ts-node를 설치해주자. ts node는 빌드없이 타입스크립트를 바로 쓸수 있게 해주는 프로덕션 툴이다. 거기에 npm i nodemon으로 서버를 자동으로 재시작 시켜주는 패키지도 하나깔자. package 에 dev 스크립트를 하나 추가해주자.

{
  "name": "blockchain",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "build": "tsc",
     "dev": "nodemon --exec ts-node src/index.ts",
    "start": "node build/index.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "ts-node": "^10.9.1",
    "typescript": "^4.9.4"
  }
}

기본 설정이 끝났고 본격적으로 블록체인을 디자인 해보자. 이하는 코딩이 대부분이므로 생략한다.


import crypto from 'crypto';

interface BlockShape {
  hash: string;
  prevHash: string;
  height: number;
  data: string;
}

class Block implements BlockShape {
  public hash: string;
  constructor(
    public prevHash: string,
    public height: number,
    public data: string
  ) {
    this.hash = Block.calculateHash(prevHash, height, data);
  }
  static calculateHash(prevHash: string, height: number, data: string) {
    const toHash = `${prevHash}${height}${data}`;
    return crypto.createHash('sha256').update(toHash).digest('hex');
  }
}

class Blockchain {
  private blocks: Block[];
  constructor() {
    this.blocks = [];
  }
  private getPrevHash() {
    if (this.blocks.length === 0) return '';
    return this.blocks[this.blocks.length - 1].hash;
  }
  public addBlock(data: string) {
    const newBlock = new Block(
      this.getPrevHash(),
      this.blocks.length - 1,
      data
    );
    this.blocks.push(newBlock);
  }
  public getBlocks() {
    return [...this.blocks];
  }
}

const blockchain = new Blockchain();

blockchain.addBlock('first one');
blockchain.addBlock('second one');
blockchain.addBlock('third one');

console.log(blockchain.getBlocks());