Skip to main content

VanillaJS로 SPA 환경 구축하기 - Step2: TypeScript

· 6 min read

typescript를 이용하여 개발하기 위한 환경을 구성하는 과정이다.

Installation TypeScript

아래와 같은 의존성 모듈들을 설치한다.

yarn add -D typescript @types/express @types/node
  • @types/express : 앞의 과정에서 express를 설치해주어 typescript 사용을 위해 추가로 설치

그리고 VSCode를 사용하는 경우, 아래와 같은 명령어를 수행한다.

yarn dlx @yarnpkg/sdks vscode

PnP(플러그앤플레이) 설정을 위해 .yarnrc.yml 파일 상단에 다음을 추가해준다. (해당 과정은 생략해도 된다)

# .yarnrc.yml
nodeLinker: pnp
yarnPath: ".yarn/releases/yarn-berry.cjs"

typescript 관련 plugin을 주입하기 위해 아래와 같은 명령어를 수행해준다. (이 과정 또한 선택 사항이지만, @types/ 관련 패키지를 package.json에 추가해주므로 유용하다.)

yarn plugin import typescript

Configure tsconfig.json

다음과 같은 커맨드를 실행하여 tsconfig.json을 생성한다.

npx tsc --init

생성된 tsconfig.json 파일을 아래와 같이 수정하여 설정해주었다.

outDir의 경우에는 기존에 작성한 html과 css 파일까지는 현재 빌드 산출물 경로에 복사하도록 구성을 하지 않아 임의로 주석처리를 하여 ts 파일과 동일한 경로에 생성되도록 설정하였다.

{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */

/* Language and Environment */
"target": "es6" /* 사용할 특정 ECMAScript 버전 설정: 'ES3' (기본), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 혹은 'ESNEXT'. */,
"jsx": "react" /* JSX 코드 생성 설정: 'preserve', 'react-native', 혹은 'react'. */,

/* Modules */
"module": "esnext" /* 모듈을 위한 코드 생성 설정: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"moduleResolution": "node" /* 모듈 해석 방법 설정: 'node' (Node.js) 혹은 'classic' (TypeScript pre-1.6). */,
"baseUrl": "./" /* non-absolute한 모듈 이름을 처리할 기준 디렉토리 */,
"resolveJsonModule": true /* Enable importing .json files */,

/* JavaScript Support */
"allowJs": true /* 자바스크립트 파일 컴파일 허용 여부 */,

/* Emit */
"sourceMap": true /* 소스 위치 대신 디버거가 알아야 할 TypeScript 파일이 위치할 곳 */,
// "outDir": "./dist" /* 해당 디렉토리로 결과 구조를 보냅니다. */,
"noEmit": false /* 결과 파일 내보낼지 여부 */,
"downlevelIteration": true /* 타겟이 'ES5', 'ES3'일 때에도 'for-of', spread 그리고 destructuring 문법 모두 지원 */,

/* Interop Constraints */
"esModuleInterop": true /* 모든 imports에 대한 namespace 생성을 통해 CommonJS와 ES Modules 간의 상호 운용성이 생기게할 지 여부, 'allowSyntheticDefaultImports'를 암시적으로 승인합니다. */,
"forceConsistentCasingInFileNames": true /* 같은 파일에 대한 일관되지 않은 참조를 허용하지 않을 지 여부 */,

/* Type Checking */
"strict": true /* 모든 엄격한 타입-체킹 옵션 활성화 여부 */,

/* Completeness */
"skipLibCheck": true /* 정의 파일의 타입 확인을 건너 뛸 지 여부 */
},
"include": ["src"]
}

Modify src/server.js to src/server.ts

앞에서 작성한 <project_root_dir>/src/server.js을 ts 파일로 수정한다.

// server.ts
import express from 'express';
import path from 'path';

const app = express();
const resolve = (dir) => path.resolve(__dirname, 'frontend', dir);

/* Ensure any requests prefixed with /static will serve our "src/static" directory */
app.use('/static', express.static(resolve('static')));

/* Redirect all routes to our (soon to exist) "index.html" file */
app.get('/*', (req, res) => {
res.sendFile(resolve('index.html'));
});

app.listen(process.env.PORT || 3000, () => {
console.log('Server running...');
});

Modify package.json

script에 아래와 같이 추가해준다.

"start": "yarn build;node src/server.js",
"build": "tsc -p .",

Run server

yarn start를 실행하여 서버가 정상적으로 실행이 되고, 웹 브라우저로 접속이 되는 지 확인해본다.

이제 다음번에는 webpack을 이용하여 빌드된 산출물을 경로를 분리하고, 코드 수정 시에 수정된 코드를 반영하여 재컴파일이 될 수 있도록 구성해보고자 한다.

Trouble Shootings

Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.

node dist/server.js와 같이 실행하였을 때에 아래와 같은 에러가 발생하였다.

(node:53574) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)

해결방법은 package.jsontype: "module"을 추가해준다.

{
"name": "cocoa-clone",
"version": "1.0.0",
"packageManager": "yarn@3.2.0",
//...SKIP...
"license": "MIT",
"type": "module"
//...SKIP...
}

ReferenceError: __dirname is not defined

commonjs에서 사용하던 __dirname 변수가 ES 모듈에는 없으므로 발생하는 에러이다.

file:///Users/user/Documents/mio/cocoa-clone/dist/server.js:4
var resolve = function (dir) { return path.resolve(__dirname, "frontend", dir); };
^

ReferenceError: __dirname is not defined
at resolve (file:///Users/user/Documents/mio/cocoa-clone/dist/server.js:4:52)
at file:///Users/user/Documents/mio/cocoa-clone/dist/server.js:7:35
at ModuleJob.run (node:internal/modules/esm/module_job:154:23)
at async Loader.import (node:internal/modules/esm/loader:177:24)
at async Object.loadESM (node:internal/process/esm_loader:68:5)

아래와 같이 수정하여 해결해주었다.

import path from 'path';
import { fileURLToPath } from 'url';

const app = express();
const resolve = (dir: string) => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

return path.resolve(__dirname, 'frontend', dir);
};