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 에러

이미지 파일을 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을 통하여 페이지를 이동하면 아래와 같은 에러가 발생한다.

SPA-url-error

에러가 나는 이유는 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개의 댓글