Typescript, React, Webpack 세팅하기

1TBhard·2022년 12월 30일
0

패키지 설치

먼저 typescript, react, react-dom을 설치한다.

npm install typescript react react-dom

막상 .tsx, .ts 파일에 react 패키지를 사용하려하면 아래와 같이 에러가 발생한다.

그 이유는 타입이 없기 때문이다.
typescript 를 사용하기 위해서는 타입 정의가 필요한데 react, react-dom 패키지에는 타입이 없다.

따라서 아래와 같이 type을 정의한 패키지를 설치해야한다. 이때 devDependencies 설치한다.

왜냐하면 타입스크립트가 JS로 트랜스파일되어 파입 정의가 필요없기 때문이다.

npm install -D @types/react @types/react-dom

@types 패키지

다른 패키지의 타입을 정의해 둔 패키지이다.
@types 패키지는 *.d.ts파일들이 있는데 *.d.ts 파일은 모듈의 외부 API를 설명하는 유형 정의 파일이다.

webpack과 그에 관련된 것들을 설치한다.
이들도 실제 애플리케이션 동작에는 상관이 없으므로 devDependencies 설치한다.

# webpack 패키지 및 cli를 사용하기 위해 설치
npm install -D webpack webpack-cli webpack-dev-server

# webpack 설정에 사용할 플러그인 설치
npm install -D autoprefixer css-loader html-webpack-plugin mini-css-extract-plugin postcss postcss-loader style-loader ts-loader

플러그인에 대한 설명은 아래와 같다.

플러그인설명
autoprefixerPostCSS의 플러그인으로 CSS에서 vender-prfix 를 붙여준다.
style-loaderCSS파일을 html 문서의 head 안에 <style>요소로 변환한다.
css-loaderjs 파일에서 CSS 파일을 불러올 수 있게 해준다.
html-webpack-plugin번들링시 html파일을 생성한다.
mini-css-extract-pluginCSS를 포함하는 js파일마다 CSS파일을 생성한다.
postcss-loader- postcss를 wepack에서 사용할 수 있게 한다.
- postcss가 설치되어 있어야한다.
ts-loadertypescript를 webpack에서 사용할 수 있게 한다.

tsconfig 설정

{
	"compilerOptions": {
      	// 패키지 import를 Node처럼 한다.
      	"moduleResolution": "Node"
		"allowSyntheticDefaultImports": true,
		"noImplicitAny": true,
		"module": "es6",
		"target": "es5",
		"allowJs": true,
		// import React from "react" 쓸 필요 없게하기
		"jsx": "react-jsx",
		"baseUrl": "./",
      	// 경로를 별칭으로 사용하게 한다.
      	// 코드에서 경로 src/app 은 ./src/app 가 된다.
		"paths": {
			"src/*": ["./src/*"],
			"public/*": ["./public/*"]
		}
      	// @types 에 대한 폴더 설정
		"typeRoots": ["./node_modules/@types", "./src/@types"]
	},
	"include": ["./src", "public/testFolder"],
	"exclude": ["./node_modules", "./dist", "./public"]
}

여기서 중요한 것은 paths 설정이 webpack의 설정에서도 동일하게 이루어져야 하는 것이다.
왜냐하면 tsconfig가 path를 인식해도 webpack은 인식하지 못하기 때문이다.

이미지 설정

이미지 파일을 import 하는 경우 에러가 발생한다.
이 이유도 typescript 가 해당 파일에 대해 타입 선언이 없기 때문이다.

이 경우 *.파일타입에 대한 .d.ts파일을 설정해주어야 한다.

// src/@types/import-ipg.d.ts

declare module "*.jpg" {
	const content: string;
	export default content;
}

.d.ts 파일?

  • typescript 코드의 타입 추론을 돕는 파일이다.
  • typescript는 전역 변수로 선언한 변수를 특정 파일에서 import 구문 없이 사용하는 경우 해당 변수를 인식하지 못해 에러가 난다.
  • 이는 해당 변수를 선언해서 해결할 수 있다.

webpack 설정

const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

const isProduction = process.env.NODE_ENV == "production";

const stylesHandler = isProduction
	? MiniCssExtractPlugin.loader
	: "style-loader";

const config = {
	entry: "./src/index.tsx",
	output: {
		path: path.resolve(__dirname, "dist"),
        // 이로 인해 clean-webpack-plugin 이 필요 없어졌다.
		clean: true,
	},
	devServer: {
		static: path.join(__dirname, "dist"),
		open: true,
		host: "localhost",
	},
	plugins: [
		new HtmlWebpackPlugin({
            // 번들링시 그대로 가져갈 html 파일
          	// 해당 html 파일에는 react 가 렌더링할 HTMLElement 가 있어야한다.
			template: "./public/index.html",
		}),
	],
	module: {
		rules: [
			{
              	// typscript를 webpack이 이해하게 한다.
				test: /\.(ts|tsx)$/i,
				loader: "ts-loader",
				exclude: ["/node_modules/"],
			},
			{
              	// css에 대한 설정
				test: /\.css$/i,
				use: [stylesHandler, "css-loader", "postcss-loader"],
			},
			{
				test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
                // 번들링시 파일을 base64로 인코딩 하지 않고 파일째로 나오게함
                // "url-loader" 플러그인이 필요하지 않게 됨!
				type: "asset/resource",
                generator: {
                    // 번들링 된 파일을 이름 그대로 생성함
					filename: "public/[name][ext]",
				},
			},
		],
	},
	resolve: {
      	// 중요! tsconfig 와 경로가 같아야한다.
		alias: {
			src: path.resolve(__dirname, "src"),
			public: path.resolve(__dirname, "public"),
		},
		extensions: [".tsx", ".ts", ".jsx", ".js", "..."],
	},
};

// production 이냐 develop에 따라 동작이 달라진다.
module.exports = () => {
	if (isProduction) {
		config.mode = "production";

		config.plugins.push(new MiniCssExtractPlugin());
	} else {
		config.mode = "development";
	}
	return config;
};

여기에서 중요한 것은 resolve.alias이다.
이 옵션은 tsconfig와 같아야 webpack에서 제대로 된 경로를 찾을 수 있다.


render을 위한 html, tsx파일

html

<!-- public/index.html -->

<!DOCTYPE html>
<html>
	<head>
		<title></title>
	</head>
	<body>
      	<!-- id를 root로 index.tsx에서 랜더링힐 것입니다. -->
		<div id="root"></div>
	</body>
</html>

html 파일에 대한 설정이다.
이 파일이 있어야 webpack이 번들링시 해당 html을 template 삼아 페이지를 생성한다.

만약 index.html 파일의 경로가 public/index.html가 아닌 경우 webpack.config.jsHtmlWebpackPlugin.template을 바꿔야한다.

tsx 파일

  • src/index.tsx

    import React from "react";
    import { createRoot } from "react-dom/client";
    import { App } from "src/App";
    
    const root = createRoot(document.querySelector("#root") as Element);
    
    root.render(
    		  <React.StrictMode>
    			  <App />
    		  </React.StrictMode>
    );
    
  • src/App.tsx

    export const App = () => {
      return (
        <div>
          <h1>App</h1>
        </div>
      );
    };

package.json

package.json은 아래와 같다.

{
  ...,
  "scripts": {
    "dev": "webpack serve",
    "build": "webpack --mode=production --node-env=production",
    "build:dev": "webpack --mode=development",
    "build:prod": "webpack --mode=production --node-env=production",
    "watch": "webpack --watch",
    "serve": "webpack serve"
  },
  "dependencies": {
    "@types/react": "^18.0.26",
    "@types/react-dom": "^18.0.10",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "autoprefixer": "^10.4.13",
    "css-loader": "^6.7.3",
    "html-webpack-plugin": "^5.5.0",
    "mini-css-extract-plugin": "^2.7.2",
    "postcss": "^8.4.20",
    "postcss-loader": "^7.0.2",
    "style-loader": "^3.3.1",
    "ts-loader": "^9.4.2",
    "typescript": "^4.9.4",
    "webpack": "^5.75.0",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.11.1"
  },
}

이로서 개발시 npm run devwebpack-dev-server가 실행된다.

repo를 참조


SPA 개발시(React-Router 사용)

React-router 등을 이용하여 SPA(Single Page Application)을 구축시 URL을 통하여 페이지를 이동하면 아래와 같은 에러가 발생한다.

에러가 나는 이유는 Webpack 설정으로 인해 localhost:포트의 주소만 웹을 띄우고 있기 때문에 locahost:포트/others 같은 URL은 없기 때문이다.

이 문제를 해결하기 위해 Webpack 설정을 아래와 같이 추가해야한다.

// webpack.config.js

const config = {
	...
	devServer: {
		open: true,
		host: "localhost",
		port: process.env.PORT,
        // 미지정 경로로 이동 및 새로고침 시 적절히 렌더링 여부
		historyApiFallback: true,
	},
	...

historyApiFallback: true시 미지정 경로로 이동하게 된다면 index.html을 serving 한다.


추가로 알아낸 사실

import React from "react";
import { createRoot } from "react-dom/client";
import { App } from "./src/App";

const root = createRoot(document.querySelector("#root") as Element);

// 개발 환경에서 2번 렌더됨!
root.render(
	<React.StrictMode>
		<App />
	</React.StrictMode>
);

react에서 <React.StrictMode>를 사용한 경우 초기 render가 두번씩 된다. (facebook 에서 의도적인 기능이라고 한다. 부작용 등을 감지하려고 하는 것 같다.)

  • <React.StrictMode> 제거하면 이런 현상이 사라진다고 한다.
  • dev 모드에서는 이 현상이 일어나지만 production에서는 안일어난다.
// 1번 렌더됨
root.render(
	<App />
);

참조

profile
기억을 넘어 습관으로

0개의 댓글