참고 링크 :
1. CRA없이 React+TypeScript 환경 구축하기
2. [Babel] Transfile과 Pollyfill 그리고 Babel에서 Pollyfill 적용 방식
create-react-app으로 생성하면 앱이 너무 무겁게 돌아가니 좀 가볍게 꼭 필요한것만 넣어서 구성해보자.
먼저 npm 환경을 만들어준다.
npm init -y
npm i react react-dom react-router-dom
리액트 관련 모듈을 설치한다.
react
react 컴포넌트를 정의 하는데 필요한 기능이 포함되어 있다.
react-dom
DOM과의 연결점을 제공한다.
ex) DOM에 Element 생성
react-router-dom
라우터(페이지 이동)에 필요한 기능이 포함되어있다.
Babel은 현재 및 이전 브라우저 또는 환경에서 ECMAScript 2015+ 코드를 이전 버전과 호환되는 JavaScript 버전으로 변환하는 데 주로 사용되는 도구 체인이다.
즉 최신 버전의 JavaScript를 이전 버전과 호환되도록 다시 변환시켜주는 일종의 컴파일러라고 보면 된다.
npm i @babel/core @babel/preset-react @babel/preset-env @babel/preset-typescript @babel/plugin-transform-runtime
바벨 관련 모듈을 설치한다.
@babel/core
babel 기본 기능이 포함되어 있다.
@babel/preset-react
@babel/preset-env
@babel/preset-typescript
많이 쓰이는 툴은 프리셋이 npm에 올라와있다. env, react, typescript에 대해 바벨 프리셋을 설치해 사용한다.
@babel/plugin-transform-runtime
이미 사용한 helper code를 재사용 할 수 있게 도와준다.
npm i typescript @types/react @types/react-dom
typescript
typescript를 읽을 수 있는 표준 기반 JavaScript로 컴파일한다.
@types/react
react에서 사용되는 type 정의를 추가한다.
@types/react-dom
react-dom에서 사용되는 type 정의를 추가한다.
기본 설정 파일을 생성한다.
tsc --init
기본 설정 파일 내부에서는 기능이 주석으로 포함되어있으므로 잘 보고 필요한 기능을 추가하면 된다.
{ "compilerOptions": { /* 기본 옵션 */ "incremental": true, /* incremental compilation 사용 */ "target": "es5", /* ECMA스크립트 대상 버전 지정: 'ES3'(기본값), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020' 또는 'ESNEXT' */ "module": "commonjs", /* 모듈 코드 생성을 지정. 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020' 또는 'ESNext'. */ "lib": [], /* 컴파일에 포함할 라이브러리 파일을 지정. */ "allowJs": true, /* Javascript 파일의 컴파일을 허용. */ "checkJs": true, /* .js 파일의 오류를 보고. */ "jsx": "preserve", /* JSX 코드 생성을 지정. 'preserve', 'react-native', 'react-jsx' 또는 'react-jsxdev'. */ "declaration": true, /* 해당하는 '.d.ts' 파일을 생성. */ "declarationMap": true, /* 해당하는 각 '.d.ts' 파일에 대한 소스 맵을 생성. */ "sourceMap": true, /* 해당하는 '.map' 파일을 생성. */ "outFile": "./", /* 연결하여 단일 파일로 출력. */ "outDir": "./", /* 출력 위치 지정. */ "rootDir": "./", /* 입력 파일의 루트 디렉터리를 지정. --outDir로 출력 디렉터리 구조를 제어하는 데 사용. */ "composite": true, /* 프로젝트 컴파일 활성화 */ "tsBuildInfoFile": "./", /* 증분 컴파일 정보를 저장할 파일 지정 */ "removeComments": true, /* 출력할 주석을 내보내지 않음. */ "noEmit": true, /* emit을 출력하지 않음. */ "importHelpers": true, /* 'tslib'에서 emit helpers를 가져옵니다. */ "downlevelIteration": true, /* 'ES5' 또는 'ES3'를 대상으로 할 때 'for-of', spread 및 destructuring에서 반복 가능한 항목을 완벽하게 지원. */ "isolatedModules": true, /* 각 파일을 별도의 모듈로 변환('ts.transpileModule'과 유사). */ /* 엄격한 유형 확인 옵션 */ "strict": true, /* 모든 엄격한 유형 검사 옵션을 활성화. */ "noInmplicitAny": true, /* 'Any' 유형의 표현식 및 선언에 오류를 발생 시킴*/ "strictNullChecks": true, /* 엄격한 Null 검사를 사용. */ "strictFunctionTypes": true, /* 기능 유형을 엄격하게 검사. */ "strictBindCallApply": true, /* 함수에 대해 엄격한 'bind', 'call' 및 'apply' 메서드를 활성화. */ "strictPropertyInitialization": true, /* 클래스에서 속성 초기화를 엄격하게 검사합. */ "No Implicit"true, /* 'Any' 유형을 암시하는 'this' 표현식에 오류를 제기. */ "alwaysStrict": true, /* strict 모드에서 구문 분석하고 각 소스 파일에 대해 "엄격하게 사용"을 내보냄. */ /* 추가 점검*/ "no Unused Locals": true, /* 사용하지 않는 로컬 변수에 대한 오류를 보고. */ "noUnusedParameters": true, /* 사용되지 않는 매개 변수에 대한 오류를 보고. */ "noImplicitReturns": true, /* 함수의 모든 코드 경로가 값을 반환하지 않을 때 오류를 보고. */ "noFallthroughCasesInSwitch": true, /* 스위치 문에서 누락케이스 오류를 보고. */ "noUncheckedIndexedAccess": true, /* index signature 결과에 'undefined' 포함 */ "noPropertyAccessFromIndexSignature": true, /* 요소 액세스를 사용하려면 index signature에서 선언되지 않은 속성이 필요. */ /* 모듈 Resolution 옵션 */ "moduleResolution": "node", /* 모듈 Resolution 전략을 지정. 'node'(Node.js) 또는 'classic'(TypeScript 이전 1.6). */ "baseUrl": "./", /* 절대적이지 않은 모듈 이름을 확인하는 기본 디렉터리. */ "paths": {}, /* 가져오기를 'baseUrl'을 기준으로 조회 위치에 다시 매핑하는 일련의 항목. */ "rootDirs": [], /* 런타임에 결합된 내용이 프로젝트 구조를 나타내는 루트 폴더 목록. */ "typeRoots": [], /* 형식 정의를 포함할 폴더 목록. */ "types": [], /* 컴파일에 포함할 선언 파일을 입력. */ "allowSyntheticDefaultImports": true, /* 기본 내보내기 없이 모듈에서 기본 가져오기를 허용. 코드 출력에는 영향을 주지 않고 검사만 입력. */ "esModuleInterop": true, /* 모든 가져오기에 대해 네임스페이스 개체를 생성하여 CommonJS와 ES 모듈 간의 상호 운용성을 내보냄. 'AllowSyntheticDefaultImports'를 의미. */ "preserveSymlinks": true, /* Symlinks의 실제 경로를 확인하지 않음. */ "allowUmdGlobalAccess": true, /* 모듈에서 UMD 글로벌 액세스를 허용. */ /* 소스 맵 옵션 */ "sourceRoot": /* 디버거가 소스 위치 대신 TypeScript 파일을 찾아야 하는 위치를 지정. */ "mapRoot": /* 디버거가 생성된 위치 대신 지도 파일을 찾아야 하는 위치를 지정. */ "inlineSourceMap": true, /* 별도의 파일 대신 소스 맵이 있는 단일 파일을 내보냄. */ "inlineSources": true, /* 단일 파일 내에서 소스 맵과 함께 소스를 내보냄. '--inlineSourceMap' 또는 '--sourceMap'을 설정해야함. */ /* 실험 옵션 */ "experimentalDecorators": true, /* ES7 장식자에 대한 실험적 지원을 가능하게 함. */ "emitDecoratorMetadata": true, /* 데코레이터에 대한 방출형 메타데이터에 대한 실험적 지원을 가능하게 함. */ /* 고급 옵션 */ "skipLibCheck": true, /* 선언 파일의 형식 검사를 스킵. */ "forceConsistentCasingInFileNames": true /* 동일한 파일에 대한 대소문자가 일치하지 않는 참조를 허용하지 않음. */ } }
생성된 tsconfig.json
의 내용을 다음과 같이 채워넣는다.
{
"compilerOptions": {
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "node",
"jsx": "react-jsx",
"baseUrl": "./",
"isolatedModules": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
}
}
webpack 버전이 올라가면서 더이상 node의 기본 모듈을 불러오지 않게 되었는데, 그로 인해 os, fs, url등을 사용하려는 경우 오류가 발생한다.
module
과 target
을 잘못설정하면 fs모듈을 찾을 수 없다는 오류가 뜬다.
npm i webpack webpack-cli webpack-dev-server webpack-merge
npm i babel-loader css-loader style-loader ts-loader
npm i interpolate-html-plugin html-webpack-plugin clean-webpack-plugin
npm i dotenv
webpack
Webpack은 모듈 번들러다. 브라우저에서 사용하기 위해 JavaScript 파일을 번들로 묶어준다.
webpack-cli
webpack CLI는 사용자 지정 webpack 프로젝트를 설정할 때 개발자가 속도를 높일 수 있도록 유연한 커맨드를 제공한다.
자세한 커맨드 내용은 링크를 참조하면 된다.
webpack-dev-server
실시간 재로딩을 제공하는 개발 서버를 실행시킨다.
webpack-merge
구성 객체를 병합해준다. 개발 웹팩 설정과 배포 웹팩 설정을 다르게 사용해야 할 때 사용한다.
babel-loader
css-loader
style-loader
ts-loader
웹팩용 각종 로더이다. 다른 확장자의 파일을 모듈처럼 임포트 할 수 있게 돕는다.
html-webpack-plugin
기본이 되는 html 파일 위치 지정을 웹팩을 통해 지정한다.
interpolate-html-plugin
html에서 웹팩에서 지정한 변수를 %변수명%
형식으로 사용할 수 있게 한다.
clean-webpack-plugin
빌드가 성공하면 빌드 결과물 중 사용되지 않는 모든 웹팩 자산을 제거한다.
dotenv
환경변수를 편하게 사용하게 해주는 모듈이다.
루트 위치에 .env 파일을 생성하고 다음과 같이 변수명 앞에 REACT_APP
을 붙여 값을 지정한다.
REACT_APP_PORT=3000
REACT_APP_MODE=development
먼저 모든 웹팩 설정의 기본이 될 webpack.common.js
파일을 생성한다.
내용은 다음과 같다.
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const InterpolateHtmlPlugin = require("interpolate-html-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const dotenv = require("dotenv");
dotenv.config();
module.exports = {
mode: process.env.MODE,
output: {
path: path.join(__dirname, "/dist"),
filename: "index.bundle.js",
publicPath: "/",
},
devServer: {
allowedHosts: "all",
port: process.env.PORT,
liveReload: true,
historyApiFallback: true,
historyApiFallback: {
disableDotRule: true,
},
},
resolve: {
fallback: { path: false, fs: false, os: false },
extensions: [".js", ".jsx", ".ts", ".tsx"],
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /nodeModules/,
use: ["babel-loader", "ts-loader"],
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(png|svg)$/,
use: [
{
loader: "file-loader",
options: {
name: "images/[name].[ext]?[hash]",
},
},
],
},
],
},
plugins: [
new webpack.DefinePlugin({
"process.env": JSON.stringify(process.env),
}),
new InterpolateHtmlPlugin({
PUBLIC_URL: "static",
}),
new HtmlWebpackPlugin({ template: "./public/index.html" }),
new CleanWebpackPlugin(),
],
};
다음으로 webpack.dev.js에 개발용 설정을 추가한다.
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "development",
devtool: "eval",
devServer: {
historyApiFallback: true,
port: 3000,
hot: true,
},
});
다음으로 webpack.prod.js에 배포용 설정을 추가한다.
const { merge } = require("webpack-merge");
const common = require("./webpack.common.js");
module.exports = merge(common, {
mode: "production",
devtool: "hidden-source-map",
});
{
...
"scripts": {
"build": "webpack --config webpack.prod.js",
"start-prod": "webpack serve --open --config webpack.prod.js",
"start": "webpack-dev-server --config webpack.dev.js --open --hot"
},
...
}
디렉토리는 다음과 같이 구성했다.
index.tsx
import React from "react";
import {createRoot} from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root") as HTMLElement).render(<App />);
App.tsx
import React from "react";
import { BrowserRouter, Route, Routes, Link } from "react-router-dom";
import Home from "./pages/Page_Home"
import Sub from "./pages/Page_Sub";
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />}></Route>
<Route path="/world" element={<Sub />}></Route>
</Routes>
</BrowserRouter>
);
}
export default App;
pages\Page_Home.tsx
import React from "react";
const Home = () => {
return (
<>
hello
</>
);
};
export default Home;
pages\Page_Sub.tsx
import React from "react";
const Sub = () => {
return <>world</>;
};
export default Sub;
위와 같이 작성한 뒤 npm start
해주면 다음과 같은 결과를 확인할 수 있다.
코드 링크:
https://github.com/hokim2407/react-typescript/tree/891768c6fe3695a54fe9f89e87f071847651b473