Languages/TypeScript

[노마드코더] #5 Typescript Blockchain

성중 2022. 5. 27. 23:37

TypeScript 프로젝트를 설정하고 블록체인 PoC(개념증명)를 객체지향으로 구현해보자!

* CRA나 Next.js의 타입스크립트 템플릿 등을 사용한다면 직접 설정하는 경우는 많지 않다

 

Targets

npm init -y

터미널에 위 명령어를 입력해 새로운 package.json 파일을 기본값으로 생성한다

 

npm install -D typescript

터미널에 위 명령어를 입력해 devDependencies에 typescript를 설치해준다

 

[package.json]

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

위와 같이 수정, npm run build 명령어 또는 tsc 명령어를 실행해 컴파일 할 수 있다

 

[tsconfig.json]

{
  "include": ["src"],
  "compilerOptions": {
    "outDir": "build",
    "target": "ES6",
    "moduleResolution": "node"
  }
}

tsconfig.json 파일을 생성, 위와 같이 내용을 넣어준다

  • include: 컴파일러가 TS 파일을 확인할 폴더를 지정
  • outDir: 컴파일된 JS 파일이 저장될 폴더를 지정
  • target: TS 파일을 어떤 버전의 JS로 컴파일할지 지정 (ES6 권장)
  • moduleResolution: module 관련 import 에러가 발생하는 경우 추가

 

[src/index.ts]

class Block {
  constructor(private data: string) {}
  static hello() {
    return "hi";
  }
}

src 폴더를 생성해 위와 같이 파일을 작성하고..

 

npm run build
# or
tsc

터미널에 컴파일 명령어를 입력하면..

 

[build/index.js]

class Block {
    constructor(data) {
        this.data = data;
    }
    static hello() {
        return "hi";
    }
}

지정한 폴더 및 컴파일된 파일이 생성/업데이트된다!

 

Lib Configuration

[tsconfig.json]

{
  "include": ["src"],
  "compilerOptions": {
    "outDir": "build",
    "target": "ES6",
    "moduleResolution": "node",
    "lib": ["ES6", "DOM"]
  }
}

lib 옵션은 TS 파일이 어떤 API를 사용하고 어떤 런타임 환경에서 실행될지 지정한다

  • ES6: Math 등 JS 기본 내장 API 사용을 명시
  • DOM: localStorage, document, window 등 브라우저 API 사용을 명시

 

 

TS 파일이 해당 API의 자동완성 및 메서드의 Call Signature, 피드백을 제공한다!

 

Declaration Files 

위와 같은 현상이 가능한 이유는 기본적인 JS API의 타입에 대한 Declaration File (정의 파일)이 지원되기 때문인데, JS 파일로 구성된 외부 패키지/라이브러리에 대해서는 정의 파일을 직접 작성해줘야 하는 경우가 있을 수 있다!

 

[tsconfig.json]

{
  "include": ["src"],
  "compilerOptions": {
    "outDir": "build",
    "target": "ES6",
    "moduleResolution": "node",
    "lib": ["ES6", "DOM"],
    "strict": true
  }
}

우선 타입을 엄격하게 검사하는 strict 옵션을 활성화한다

 

[src/myPackage.js]

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

JS 파일로 구성된 외부 패키지/라이브러리를 사용한다고 가정하고 파일을 생성해준다

 

[src/myPackage.d.ts]

interface Config {
  url: string;
}
declare module "myPackage" {
  function init(config: Config): boolean;
  function exit(code: number): number;
}

해당 파일에 대한 모듈 및 Call Signature를 선언하는 정의 파일을 생성해준다

 

[src/index.ts]

import { init, exit } from "myPackage";

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

exit(1);

TS 파일에서 import 가능, 외부 JS 패키지/라이브러리가 모듈로 인식된다

 

JSDoc

JS 파일과 TS 파일을 혼용하는 경우, 주석에 JSDoc을 작성해 TS 파일과 함께 사용할 수 있다

* JS 프로젝트를 TS로 마이그레이션하는 과정이나 JS 파일의 원본을 유지해야 하는 경우

 

[tsconfig.json]

{
  "include": ["src"],
  "compilerOptions": {
    "outDir": "build",
    "target": "ES6",
    "moduleResolution": "node",
    "lib": ["ES6", "DOM"],
    "strict": true,
    "allowJs": true
  }
}

정의 파일을 삭제, JS 파일 사용을 허용하는 allowJS 옵션을 활성화한다

 

[src/index.ts]

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

TS 파일에서 JS 파일을 import할 수 있게 되지만 아직 타입을 체크하진 않는다

 

[src/myPackage.js]

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

위와 같이 JS 파일에서 주석으로 함수의 매개변수 및 반환값의 타입을 지정할 수 있다

 

결과적으로 JS 파일에서 import한 함수가 TS 파일에서 보호받는다!

 

Blocks

npm i -D ts-node

빌드와 관계없이 TS 파일을 바로 실행할 수 있도록 해주는 ts-node를 설치해준다

* 프로덕션이 아닌 개발 환경에서만 대신 실행

 

npm i nodemon

파일을 모니터링, 저장하면 node.js 서버를 자동 재실행해주는 nodemon을 설치해준다

 

[package.json]

{
  "name": "typechain",
  "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.8.0",
    "typescript": "^4.6.4"
  },
  "dependencies": {
    "nodemon": "^2.0.16"
  }
}

scripts를 위와 같이 추가, npm run dev를 실행하면 개발 환경에서 빌드 없이 파일을 수정/저장할 때마다 터미널에서 index.ts 파일을 실행해주는 상태를 활성화한다

 

[tsconfig.json]

{
  "include": ["src"],
  "compilerOptions": {
    "outDir": "build",
    "target": "ES6",
    "moduleResolution": "node",
    "lib": ["ES6"],
    "strict": true,
    "esModuleInterop": true,
    "module": "CommonJS"
  }
}
  • module: JS 파일간 import 문법을 구현할 때 사용할 문법
  • esModuleInterop: CommonJS 모듈을 ES6 모듈로 가져오려고 할 때 발생하는 문제 해결
  • 브라우저가 아닌 node.js 서버에서 돌아가기 때문에 lib 옵션에서 DOM을 제거
  • allowJS 옵션 제거

 

[src/index.ts]

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): string {
    return `${prevHash}${height}${data}`;
  }
}

블록체인을 생성하기 위한 형태를 Block 클래스로 정의해주자

 

블록체인 개념 간단 정리🔽

 

Definitely Typed

JS로 작성된 npm 패키지를 import할 때, 일일이 타입 정의 파일을 작성해줄 수는 없다

 

DefinitelyTyped🔽

 

GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions.

The repository for high quality TypeScript type definitions. - GitHub - DefinitelyTyped/DefinitelyTyped: The repository for high quality TypeScript type definitions.

github.com

DefinitelyTyped 저장소에는 npm에 존재하는 거의 모든 패키지에 대한 타입 정의 파일이 저장되어 있다

 

npm i -D @types/(패키지 이름)

터미널을 통해 해당 패키지에 대한 타입 정의 파일을 불러올 수 있다

* 최근에는 npm 패키지에 자체에 d.ts 파일을 포함하는 경향

 

Chain

[src/index.ts]

import crypto from "crypto";

interface BlockShape {
  hash: string;
  prevHash: string;
  height: number;
  data: string;
}
class Block implements BlockShape {
  public readonly hash: string;
  constructor(
    public readonly prevHash: string,
    public readonly height: number,
    public readonly 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(): readonly Block[] {
    return [...this.blocks];
  }
}

const blockchain = new Blockchain();

blockchain.addBlock("First one");
blockchain.addBlock("Second one");
blockchain.addBlock("Third one");
blockchain.addBlock("Fourth one");

console.log(blockchain.getBlocks());

crypto 패키지를 활용해 간단한 블록체인 PoC를 구현했다!

 

본 내용은 노마드코더의 'Typescript로 블록체인 만들기'를 바탕으로 작성되었습니다.