모듈이란 파일 간 코드를 공유하는 방법
타입스크립트 네임스페이스.
타입스크립트의 특정된 모듈 포맷이다.
https://www.typescriptlang.org/ko/docs/handbook/namespaces.html
요새는 많이 쓰이지 않는 추세이다. ES 모듈을 많이 쓴다.
TypeScript는 ES 모듈 구문, 즉 ES6 모듈을 지원하며
이때 import / export 키워드를 사용한다
이 키워드들로 파일 간 코드를 공유할 수 있다.
같은 디렉토리에
index.ts
utils.ts
두 파일이 있다.
utils.ts 에 다음과 같은 함수가 있다고 하자.
function add(x: number, y: number) {
return x + y;
}
function sample<T>(arr: T[]): T {
const idx = Math.floor(Math.random() * arr.length);
return arr[idx];
}
const pi = 3.14;
index.ts에서 import 없이 바로 sample() 과 add()를 쓰면 어떻게 될까?
정상적으로 작동한다.
스크립트 내부에서 작업 중인 것. 모듈이 아니다. export가 없기 때문.
스크립트 안에서 작업할 때 변수와 타입은 공유 전역 스코프(global scope) 내부에 있다고 선언된다.
위 코드가 다른 파일에서 작동하는 것은 공유 전역 스코프에 있기 때문.
utils.ts에서 const x =1 이라고 하고 index.ts에서 const x = 2 라고 입력하면 오류가 발생한다.
블록 스코프 변수 x를 또다시 선언할 수 없다고 나옴.
반대로 코드를 분리 및 구성하고 다른 네임스페이스를 가질려면 모듈을 쓴다.
위 코드들을 자바스크립트로 컴파일 하면 자바스크립트 파일들은 서로 아무 관련이 없다.
실제 브라우저에서 사용되려면 파일들이 올바른 순서로 HTML에 스크립트로 포함되어야 한다.
TypeScript는 모든 코드가 한 전역 스코프(global scope)에 있다고 간주한다.
namespace가 없으므로 문제가 생기게 된다.
예를 들면 변수를 선언할 때 다른 파일에서 정의한 이름과 같거나
다른 파일과 같은 이름의 함수를 정의한다면 헷갈린다.
그리고 많은 파일을 가지고 있을 때는 알맞은 순서로 파일을 추가해야 한다.
자바스크립트는 파일별로 스크립트가 달라서 파일에서 다른 항목에 엑세스하려면
올바른 순서로 포함시켜야 한다. 모듈도 신경써야함.
타입스크립트는 export나 최상위 await가 없는 파일을 모듈이 아닌 스크립트로 간주한다고 선언한다
반대로 말하면 export 또는 최상위 await 키워드가 있으면 모듈로 인식된다는 뜻이다.
utils.ts
export function add(x: number, y: number) {
return x + y;
}
export function sample<T>(arr: T[]): T {
const idx = Math.floor(Math.random() * arr.length);
return arr[idx];
}
이렇게 export를 넣으면
index.ts에서 add()와 sample()이 오류가 난다.
타입스크립트가 이제 ES 모듈 모드로 전환되어서 각 파일이 각자 독립된 파일과 네임스페이스로 분리되어
공유하려는 함수 기능을 일일이 가져오고 내보내야 한다.
index.ts
import { add, sample } from "./utils.js";
add(1,2);
sample([12,44,5]);
이제 에러가 사라진다.
utils.ts가 아닌 utils.js인 이유는 어차피 컴파일되어서 사용되기 때문. ts로 적으면 에러가 난다.
이제 자바스크립트로 컴파일 하면
index.js
Object.defineProperty(exports, "__esModule", { value: true });
var utils_js_1 = require("./utils.js");
(0, utils_js_1.sample)([12, 44, 5]);
(0, utils.js_1.add)(1, 2);
이렇게 복잡한 코드들이 많이 보인다.
utils.js
Object.defineProperty(exports, "__esModule", { value: true });
exports.sample = exports.add = void 0;
function add(x, y) {
return x + y;
}
exports.add = add;
function sample(arr) {
var idx = Math.floor(Math.random() * arr.length)'
return arr[idx];
}
exports.sample = sample;
이렇게 자바스크립트로 컴파일되면 import와 require를 사용하지 않고
module.exports와 require를 사용한다.
위의 코드들은 node 환경에서 잘 작동한다.
그러나 브라우저에 실행시키기 위해 HTML 파일을 만들어 실행시키면 오류가 난다.
node 환경과 브라우저 환경이 다르기 때문.
브라우저 내 JavaScript는 CommonJS 모듈을 이해하지 못해서
exports, require, 모듈을 이해하지 못한다.
webpack이나 RequireJS 와 같은 여러 도구를 사용하면 작동하도록 만들 수 있지만
모듈을 사용하지 않도록 해도 된다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Modules</title>
</head>
<body>
<script src="./dist/index.js"></script>
<script src="./dist/utils.js"></script>
</body>
</html>
index.js와 utils.js에서 export와 import를 모두 없애고
이렇게
브라우저에서도 코드가 동작한다.
하지만 이런 방식으론 파일이 수가 많아지거나 로직과 의존성이 복잡하게 얽혀 있다면 제대로 돌아가기가 어렵다.
결국 export와 import는 쓰되. ES6 모듈 구문을 사용할 수 있게 만들어달라고 해야 한다.
tsconfig.json 파일에서 모듈 설정(”module”)을 찾아야 한다.
기본값은 “module”: “commonjs” 로 되어 있지만
"module": "commonjs" // 기본값.
"module": "ES6" // 이렇게 바꿔준다.
이렇게 바꿔주고
모듈 타입을 특정해 준다.
<script type="module" src="./dist/index.js"></script>
이렇게 하고 실행하면 파일 프로토콜이 이 모듈에서 허용되지 않는다고 뜨는데
서버를 가동해야 한다.
npm init -y로 실행하고 lite-server를 가동.
package.json에서
"scripts": {
"start": "lite-server"
}
이렇게 추가하면 작동을 한다.
브라우저에서 모듈 구문을 사용하려면 거쳐야 하는 절차.
import와 export 구문은 뭐든 내보낼 수 있다
함수, 클래스, 변수도 가능하다.
export const pi = 3.14;
이렇게 내보낸 변수를 다른 파일에서 사용하려면
import { pi } from "./utils.js";
중괄호를 써줘야 한다.
한 파일에서 여러가지를 동시에 import 하려면 다음처럼 쓸 수 있다.
import { add, sample as randomSample, pi } from "./utils.js";
const sample = 134134;
중괄호 안에 나열하고 쉼표로 구분한다.
만약 받아들이는 파일에서 이미 받아와야할 것과 같은 이름을 가진 변수나 함수가 있다면 (sample 처럼)
내보내는 쪽의 이름 as 받는 쪽에서 새로 쓸 이름
형식으로 사용할 수 있다.
또한 한 파일에서 내보내는 것이 하나 뿐이거나, 또는 주로 내보내는 것이 하나 분명한게 있다면
User.ts
export default class User {
constructor(public username: string, public email: string) {}
logout() {
console.log(`${this.username} logs out!!`);
}
}
export function userHelper() {
console.log("USER HELPER!");
}
이렇게 export default 라고 쓰고, 받는 쪽에서는
import User, { userHelper } from "./User.js";
이렇게 쓸 수 있다. export default라고 쓴 부분은 중괄호 필요없이 꼭 User라는 이름 말고도 어느 이름을 써도 default로 지정한 것이 들어온다. default 이외의 것들은 중괄호를 쓰고 맞는 이름을 써줘야 한다.
현재 작업하는 모듈은 TypeScript모듈이며 ES모듈과 완전히 똑같지는 않다.
타입을 가져올때는 다음과 같은 구문을 사용할 수 있다.
types.ts
export interface Person {
username: string;
email: string;
}
export type Color = "red" | "green" | "blue";
User.ts
import type { Person } from "./types.js";
export default class User implements Person {
constructor(public username: string, public email: string) {}
logout() {
console.log(`${this.username} logs out!!`);
}
}
export function userHelper() {
console.log("USER HELPER!");
}
import type으로 타입을 가져온다. 그냥 써도 되지만 TypeScript가 아닌 Babel같은 트랜스파일러를 사용하면 오류가 날 수 있다.
여러개를 가져오면 다음과 같이 써도 된다.
import { type Person, Color } from "./types.js";
중괄호 안에 type이라고 써도 된다.