이번 포스팅은 숙제를 달성하는 과정에서 공부하고 적용한 것을 작성할 것입니다.
제목이 쓸때없이?? 고퀄
이란 수식어가 들어간 이유는 적용하는 것이 만들 결과물에 너무 Overfetting한 내용들이지만, 이번 숙제가 새로운 것들을 공부하고 적용해볼 기회라고 생각해 다룬 것들을 포스팅하겠습니다. 우선 주어진 숙제입니다.
숙제는 두 페이지로 구성됩니다.
메인 페이지이자 네비게이션바에 Todo 버튼을 누를 때의 페이지를 작성합니다.
일반적인 Todo List입니다.
기본적인 CRUD 페이지로, Update로 Item의 내용을 바꾸기, Item을 클릭할 때 취소선을 Toggle 시키는 기능이 있습니다.
네트워크 비동기 요청을 통해 API를 가져와 내용을 리스팅하는 페이지입니다.
리스팅하기전에 비동기 요청시의 Loading을 구현해야합니다.
숙제들이 요구하는 내용은 기본적인 내용입니다. 하지만 프론트 엔드 개발에서 요구되는 중요한 기본지식들이 모두 포함되어 있습니다. (전역 상태 관리, 비동기 로딩 처리, 라우팅, CRUD, Pure CSS 지식 등)
이번 기회에 숙제라고 생각하기 보단 공부하던 것, 공부하고 싶은 것들을 적용해 지식을 흡수하는 기회라고 생각하고 포스팅할 것입니다.
아토믹 디자인을 하는 것은 Overfetting이지만 차근차근 공부를 해보고 적용할 생각입니다.
그리고 원래 프론트 숙제라서 서버를 구성할 필요는 없지만 여유가 있다면, Nestjs를 공부해볼 마음으로 Backend
편을 작성할 것입니다.
이번 포스팅은 CRA 없이 환경 세팅을 다루겠습니다.
제가 다루는 언어는 JS보단 TS이기 때문에 TS + Webpack + EsLint + Prettier 환경을 구축할 것입니다.
우선 작업할 디렉토리에서 npm init -y
로 npm을 사용할 준비를 합니다.
typescript를 컴파일하기 위해선 typescript 모듈 설치와 tsconfig.json파일이 필요합니다.
그래서 npm i typescript
로 타입스크립트를 설치한 후 npx tsc --init
으로 tsconfig.json을 만듭니다.
제 tsconfig.json의 세팅 내용입니다.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es5", "es2015", "es2016", "es2017", "DOM"],
"jsx": "react",
"outDir": "./dist",
"strict": true,
"typeRoots": ["./node_modules/@types","./src/types"],
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
}
}
저는 호환성면을 생각해 es5와 commonjs로 세팅합니다.
저는 es5, es6, es6를 기반으로 async await를 사용하기 위해 es2017를 넣습니다. es2016은 그냥 덤으로 넣어줍니다.
jsx파일의 개발환경이 React인지 React-native인지 설정합니다.
빌드한 결과의 디렉토리 경로를 설정합니다.
저는 custom type이 생기면 src/types란 디렉토리에 type 파일을 만들 것이기 때문에 typeRoots에 추가했습니다.
esModuleInterop은 개인적인 취향이 default와 속성들을 한줄에 불러오는 것을 좋아해서 세팅을 합니다. 이 설정을 사용하려면 JS의 Module System을 파악하고 사용해야 합니다.
마지막 forceConsistentCasingInFileNames 옵션은 처음부터 세팅되어 있어 그대로 두었습니다. 대소문자 구분있게 파일의 상태가 다르면 다르게 구분하는 설정입니다.
React를 사용하기 위한 webpack 설정입니다. 전 개발을 할 땐 webpack-dev-sever를 사용하고 빌드할 땐 webpack -p
명령어를 사용합니다.
우선 webpack webpack-cli webpack-dev-server awesome-typescript-loader html-webpack-plugin 을 설치합니다.
제가 설정한 webpack.config.js
입니다.
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.tsx',
devServer: {
historyApiFallback: true,
inline: true,
port: 3000,
hot: true,
publicPath: '/'
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'awesome-typescript-loader'
}
]
},
resolve: {
extensions: ['.js', 'jsx', '.ts', '.tsx']
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'public/index.html'
})
]
};
우선 리액트를 사용하기 때문에 타입스크립트 컴파일은 웹팩이 로더를 사용해서 해야합니다. ts-loader란 것이 있지만 babel과 연동이 쉬운 awesome-typescript-loader
를 주로 사용합니다.
historyApiFallback
는 HTML5의 History API를 사용할 때, 라우팅 설정한 url에 접근할 때 html파일을 서빙할지를 결정합니다. 간단하게 말하자면
...
<Link to="/about"> About </Link>
<Route path="/about" render(() => (<div> About </div>)) />
...
라는 코드가 작성있습니다. historyApiFallback: true
일 때, About 링크를 누르면 url이 "/about" 으로 이동하면서 About이란 텍스트가 뜨고 새로고침을 해도 About 텍스트가 뜹니다. 하지만 설정 값이 false
이면 404 not found가 발생합니다. 즉, 일반 <a>
태그로도 라우트에 접근할 수 있게합니다.
inline: true
설정은 프로그램이 바뀌면 전체 페이지를 리로딩하는 옵션, hot: true
설정은 바뀐 컴포넌트만 새로 리로딩하는 옵션입니다. 밑에서 언급할 HMR을 사용할 수 있게 해주는 것입니다. 이 두개를 설정하면 hot mode를 적용하다 적용이 되지 않을 때 inline mode로 리로딩이 발생하죠
publicPath
는 webpack plugin들이나 html, css등의 루트 경로를 설정합니다.
resolve의 extensions
는 import
하는 파일의 가능한 확장자명을 나열합니다. 즉 index.tsx에서 import App from './App'
를 할 때 해당 경로에서 App이란 이름을 가지고 extensions에서 나열한 확장자를 가진 파일들이 있는지를 확인하고 가져옵니다.
new webpack.HotModuleReplacementPlugin() 통칭 HMR
은 hot 설정에 필요한 플러그인으로, 코드가 변경되도 브라우저 리로딩 없이 변경된 모듈만 가져와 기존의 상태들이 유지할 수 있게 됩니다.
HtmlWebpackPlugin
은 template
으로 지정한 html파일을 기반으로 webpack의 번들링 파일을 묶은 html파일을 자체적으로 서빙하는 플러그인입니다. 그래서 프로젝트 루트만 봤을 때 dist같은 번들링 결과가 없이 플러그인이 자체적으로 번들링하고 그 결과를 script로 갖는 html을 브라우저에 보여줍니다.
그리고 참조할 html 파일을 만들기 위해서 ./public 디렉토리를 생성하고 그 안에 index.html를 만들어 내용을 채워줍니다.
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" name="format-detection" content="telephone=no" />
<meta name="viewport" content="width=device-width, user-scalable=no" />
<meta name="description" content="Prography pre task" />
<meta name="google" content="notranslate" />
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon" />
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<title>Todo and Move List</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
그리고 개발을 위한 dev-server를 실행하는 script, production용 빌드 script를 package.json에 추가합니다.
"scripts": {
...
"dev": "webpack-dev-server",
"build": "webpack -p",
...
}
웹팩설정을 했으니, 잘 돌아가는지 확인을 해야겠죠
우선 react @types/react react-dom @types/react-dom 을 설치합니다.
그리고 루트 디렉토리에서 ./src 디렉토리를 생성하고 그 안에 index.tsx, App.tsx를 생성합니다.
// index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
// App.tsx
import React from 'react';
const App = () => {
return <div>Hello React!</div>;
};
export default App;
그리고 npm run dev
를 실행하면 localhost:3000 페이지에 hello React라는 텍스트가 뜰 것입니다.
Typescript를 사용하는 입장에서 tslint란 것도 있지만 이 lint는 deprecated 상태가 되고 eslint로 마이그레이션되어 eslint를 사용해야 합니다.
module.exports = {
env: {
browser: true,
es6: true
},
extends: ['plugin:react/recommended', 'airbnb', 'plugin:prettier/recommended'],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true
},
ecmaVersion: 2018,
sourceType: 'module'
},
settings: {
'import/resolver': {
node: {
paths: ['src'],
extensions: ['.d.ts', '.ts', '.tsx']
}
}
},
plugins: ['react', '@typescript-eslint'],
rules: {
'react/jsx-filename-extension': [1, { extensions: ['.tsx'] }],
'import/extensions': [
'error',
'ignorePackages',
{ js: 'never', jsx: 'never', ts: 'never', tsx: 'never', json: 'never' }
],
'react/jsx-indent': [2, 'tab'],
'react/jsx-indent-props': [2, 'tab']
}
};
.eslintignore.js 파일도 만들어 node_modules/를 추가합시다.
lint쪽의 설정 내용은 아직 관심이 없고 삽질을 많이 하면서 얻은 결과물이라 지식이 정확하지 않아 다루지 않겠습니다.
rules는 hello world를 찍는 도중에도 발생하는 이유모를 eslint 오류가 생겨 추가한 것들입니다.
{
"singleQuote": true,
"parser": "typescript",
"semi": true,
"useTabs": true,
"printWidth": 120
}
그리고 vscode에서 eslint, prettier 플러그인이 없을 때 명령어를 통해 스타일 에러를 잡을 수 있게 스크립트를 추가합니다.
// package.json
{
"lint": "eslint \"./src/**/*.{ts,tsx,js,jsx}\" || true",
"lint:fix": "eslint --fix \"./src/**/*.{ts,tsx,js,jsx}\" || true",
"prettier": "prettier --write --config ./.prettierrc \"./src/**/*.{ts,tsx,js,jsx}\" || true"
}
각 스크립트 명령어에 true를 붙이는 이유는 스타일 에러가 발생하면 이상한 npm 에러가 콘솔에 발생해 추가한 것입니다.
vscode를 사용하시는 분들 중에 설정을 다해도 IDE에 eslint 경고나 오류가 발생하지 않으면 IDE를 끄고 다시 실행시켜보세요.
이것으로 환경 세팅을 마쳤습니다. 웹팩을 공부해야지 공부해야지 하다가 이번에 노가다 세팅을 해보면서 설정들이 무슨 역할을 하는지 알아보았네요.
그 과정이 너무 힘들어서 그런지 lint의 공부는 나중으로 미뤄야겠네요 ㅜㅜ
공부하고 있는 사람으로써 완벽한 지식이 아닐 수 있을 것입니다. 그래서 잘못된 것이 있으면 지적해주시거나 피드백을 주시면 더욱 발전하는 개발자가 될 수 있을 것 같습니다!