front 폴더 생성 후 아래와 같이 패키지 설치
$ git init
$ npm i react react-dom typescript
$ npm i @types/react @types/react-dom
$ npm i -D eslint # eslint는 코드 검사 도구
$ npm i -D prettier eslint-plugin-prettier eslint-config-prettier #prettier 코드 정렬 도구
front 폴더 하위에 .eslintrc
, .prettierrc
, tsconfig.json
파일 생성
.eslintrc
{
"extends": ["plugin:prettier/recommended"] # prettier 추천을 따르겠다.
}
.prettierrc
{
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "all", # 객체 뒤 콤마 추가 여부
"semi": true # 항상 세미콜론(;) 추가 여부
}
tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true, // import * as React from 'react'; -> import React from 'react';
"sourceMap": true, // 에러났을 때 에러난 위치 찾아가기 편하다.
"lib": ["ES2020", "DOM"], // 최신문법 라이브러리 켜놓기
"jsx": "react", // jsx 를 reactㄹㅎ
"module": "esnext", // 최신 모듈(import, export)
"moduleResolution": "Node", // import, export를 Node가 해석할 수 있도록 함
"target": "es5", // es5로 변환
"strict": true, // 엄격한 타입체크
"resolveJsonModule": true, // import Json file 허용
"baseUrl": ".",
"paths": {
// 임포트 편리하게 하기 위한 설정, import A from @src/hello.js
"@hooks/*": ["hooks/*"],
"@components/*": ["components/*"],
"@layouts/*": ["layouts/*"],
"@pages/*": ["pages/*"],
"@utils/*": ["utils/*"],
"@typings/*": ["typings/*"]
}
}
}
설정하기 앞서 babel, webpack 설정 관련 라이브러리를 설치해준다.
$ npm i -D webpack webpack-cli @babel/core babel-loader @babel/preset-env @babel/preset-react
$ npm i -D @types/webpack @types/node @babel/preset-typescript
$ npm i -D style-loader css-loader
@ npm i cross-env ts-node
이번 프로젝트는 JavaScript가 아닌 TypeScript로 구현하므로 webpack 설정도 .ts로 맞춰준다. 타입스크립트는 자바스크립트의 변수, 함수의 매개변수, 함수의 반환값에 타입이 붙어있는 것을 의미한다.
front/webpack.config.ts
import path from 'path';
import webpack from 'webpack';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
const isDevelopment = process.env.NODE_ENV !== 'production';
const config: webpack.Configuration = {
name: 'sleact',
mode: isDevelopment ? 'development' : 'production',
devtool: !isDevelopment ? 'hidden-source-map' : 'eval',
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], // 바벨이 처리할 확장자 목록
alias: { // tsconfig 설정처럼 ../../ 를 없애기 위한 설정 (typescript, webpack 모두 설정이 필요하다.)
'@hooks': path.resolve(__dirname, 'hooks'),
'@components': path.resolve(__dirname, 'components'),
'@layouts': path.resolve(__dirname, 'layouts'),
'@pages': path.resolve(__dirname, 'pages'),
'@utils': path.resolve(__dirname, 'utils'),
'@typings': path.resolve(__dirname, 'typings'),
},
},
entry: {
app: './client', // main 파일
},
module: {
rules: [
{
test: /\\.tsx?$/, // ts파일이나 tsx 파일을 babel-loader가 자바스크립트로 변환한다.
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: { browsers: ['last 2 chrome versions', 'IE11'] }, // 타겟 브라우저 설정
debug: isDevelopment,
},
],
'@babel/preset-react', // react code 변환
'@babel/preset-typescript', // typescript code 변환
],
},
exclude: path.join(__dirname, 'node_modules'),
},
{
test: /\\.css?$/,
use: ['style-loader', 'css-loader'], // css 파일을 style-loader, css-loader가 자바스크립트로 변환한다.
},
],
},
plugins: [
// typescript 변환 시 필요
new ForkTsCheckerWebpackPlugin({
async: false,
// eslint: {
// files: "./src/**/*",
// },
}),
new webpack.EnvironmentPlugin({ NODE_ENV: isDevelopment ? 'development' : 'production' }), // react에서 NODE_ENV를 사용할 수 있도록 해 줌
],
output: { // client.tsx부터 만들어진 모든 파일이 나오는 경로
path: path.join(__dirname, 'dist'),
filename: '[name].js',
publicPath: '/dist/',
},
devServer: {
historyApiFallback: true, // react router
port: 3090,
publicPath: '/dist/',
proxy: {
'/api/': {
target: '<http://localhost:3095>',
changeOrigin: true,
},
},
},
};
// 개발 환경 플러그인
if (isDevelopment && config.plugins) {
config.plugins.push(new webpack.HotModuleReplacementPlugin());
config.plugins.push(new ReactRefreshWebpackPlugin());
config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'server', openAnalyzer: true }));
}
// 런타임 환경 플러그인
if (!isDevelopment && config.plugins) {
config.plugins.push(new webpack.LoaderOptionsPlugin({ minimize: true }));
config.plugins.push(new BundleAnalyzerPlugin({ analyzerMode: 'static' }));
}
export default config;
front/index.html
모든 서비스의 시작점이 된다. 성능 최적화, 검색엔진 등의 이슈에서 굉장히 중요한 역할을 함.
<html>
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>슬리액</title>
<style>
html,
body {
margin: 0;
padding: 0;
overflow: initial !important;
}
body {
font-size: 15px;
line-height: 1.46668;
font-weight: 400;
font-variant-ligatures: common-ligatures;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
}
* {
box-sizing: border-box;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="./dist/app.js"></script>
</body>
</html>
#app 엘리먼트에 추가될 시작점 client.tsx
와 App.tsx
를 생성해준다.
front/client.tsx
import React from 'react';
import { render } from 'react-dom';
import App from './layouts/App';
render(<App />, document.querySelector('#app'));
front/layouts/App.tsx
import React from 'react';
const App = () => {
return <div>HELLO WORLD!</div>;
};
export default App;
또한 webpack-config.ts
에 typescript 설정을 별도로 해줘야한다.
front/tsconfig-for-webpack-config.json
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "Node",
"target": "ES5",
"esModuleInterop": true
}
}