티스토리 뷰
[webpack] babel, TS, webpack 세팅하기 1(+ proxy로 CORS에러 해결하기)
blueprint-12 2023. 5. 17. 22:11사용 패키지 매니저: yarn
우선 기본적으로 사용해야할 라이브러리를 설치해준다.
D 는 yarn add -D 옵션으로 dev dependency를 의미한다.
- react
- react-dom
- typescript // TS적용시
- eslint (D) //문법체킹
- prettier eslint-plugin-prettier eslint-config-prettier (D) //코드 포맷체킹과 eslint 과 연동을 위한 플러그인
웹팩의 동작원리
웹팩은 Node.js 환경에서 실행되는 JS어플리케이션의 일종으로, 복잡한 의존성 관계들을 가지는 여러 정적 파일(JS, CSS, Sass 등)들을 번들링하여 적은 정적 파일들을 만들어내는 모듈 번들러이다.
tsx파일을 TS로 변환해주고 그 다음 webpack이 babel로 처리하여 JS 파일(JS로 변환해주는건 웹팩)로 만든다.
웹팩 장점
웹팩을 사용하면 모듈들 간의 의존성이 해결된 상태의 번들링 파일들을 클라이언트에게 제공하는 것이되기 때문에 별도의 모듈 로더 라이브러리가 필요없고, 일일이 <script>태그로 여러 개의 JS파일들을 로드하거나 <link>태그로 여러 개의 스타일 시트들을 로드하는 번거로움을 겪지 않아도 된다.
우선 모든 것을 설명하기에는 너무 많은 내용이기도 하고 아직 이해가 안가는 부분도 많기 때문에 기본적으로 아래의 세팅이 되어있다는 것을 가정한다.
[만들어야하는 세팅파일] :여기서 eslint와 prettier 관련 세팅은 본글의 포스팅에서 제외(간단하니 찾아보시는 걸 추천합니다.), TS를 사용
참고로 JS는 오타를 잡아주지 않는다. TS의 경우는 오타를 잡아주는데 JS에서 오타를 잡고싶다면 eslint 설정을 통해 가능하다.
eslintrcprettierc- tsconfig.json
- webpack.config.ts
tsconfign.json
{
"compilerOptions": {
"esModuleInterop": true,
"sourceMap": true,
"lib": ["ES2020", "DOM"],
"jsx": "react", //jsx가 다른 곳에서도 인식될 수 있기 때문에
"module": "esnext", //최신 모듈시스템(import export을 쓰겠다는 것
"moduleResolution": "Node",
"target": "es5",
"strict": true,
"resolveJsonModule": true, //import json 허락
"baseUrl": ".", //import 할 때, 절대경로처럼 간편하게 설정
"paths": {
"@hooks/*": ["hooks/*"],
"@components/*": ["components/*"],
"@layouts/*": ["layouts/*"],
"@pages/*": ["pages/*"],
"@utils/*": ["utils/*"],
"@typings/*": ["typings/*"]
}
}
}
webpack 설정을 위해서는 webpack 과 babel을 dev환경에 설치해줘야 한다.
yarn add -D webpack @babel/core babel-loader @babel/preset-env @babel/preset-react
타입스크립트를 사용하고 있기 때문에 타입 패키지도 추가해준다.
yarn add -D @types/webpack @types/node @babel/preset-typescript
웹팩은 js와 css 등 정적 아셋을 번들해준다고 했으니 css 관련 처리를 위해 css 처리 패키지도 추가해준다.
yarn add style-loader css-loader
webpack.config.json
import path from 'path';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
import webpack, { Configuration as WebpackConfiguration } from 'webpack';
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';
import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
interface Configuration extends WebpackConfiguration {
devServer?: WebpackDevServerConfiguration;
}
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
const isDevelopment = process.env.NODE_ENV !== 'production';
const config: Configuration = {
name: 'my-front',
mode: isDevelopment ? 'development' : 'production',
devtool: isDevelopment ? 'source-map' :'hidden-source-map' , //eval -> source-map으로 변경
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'],
alias: {
//alias에 들어가는 경로와 tsconfig에서도 같이 설정해줘야 한다
'@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',
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
targets: { browsers: ['IE 10'] },
debug: isDevelopment,
},
],
'@babel/preset-react',
'@babel/preset-typescript',
],
env: {
development: {
plugins: [require.resolve('react-refresh/babel')],
},
},
},
exclude: path.join(__dirname, 'node_modules'),
},
{
test: /\.css?$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new ForkTsCheckerWebpackPlugin({
async: false,
// eslint: {
// files: "./src/**/*",
// },
}),
new webpack.EnvironmentPlugin({ NODE_ENV: isDevelopment ? 'development' : 'production' }),
],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].js', // 대괄호에 들어가는 name은 상단의 entry의 키값과 동일하다. app.js
publicPath: '/dist/',
},
devServer: {
historyApiFallback: true, // react router
port: 3090,
devMiddleware: { publicPath: '/dist/' },
static: { directory: path.resolve(__dirname) },
//CORS에러 프론트에서 해결하는 방법
// ? /api/주소로 보내는 요청은 주소를 target으로 바꿔서 보내겠다 라는 의미
proxy: {
'/api/': {
target: 'http://localhost:3095',
changeOrigin: true,
},
},
},
};
//개발 환경에서 쓸 plugin
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;
여기서 tsconfig의 paths와 webpack.config의 alias가 내용이 겹치는데 왜 경로를 둘 다 설정해야 할까?
- 소스 코드는 타입스크립트로 하면서 TS검사기가 있는데 이 검사기는 tsconfig에 있는 path를 검사하고 웹팩은 alias를 보고 판단한다. 그렇기 때문에 경로설정은 두 군데에서 해줘야 한다.
proxy로 CORS에러 해결하기
서버도 localhost, 프론트도 localhost일 경우에만 적용되는 간이 해결법이다. 만일 서버가 이미 배포된 주소라면 proxy 설정으로도 해결되지 않으니 유의해야 한다.
devServer 세팅에 보면 proxy가 있다.
axios로 http 통신을 한다고 했을 때 기존코드는 아래와 같다.
🔺localhost:3090 (프론트 서버) , 3095 (백엔드 서버)
axios.post('http://localhost:3095/api/users', data)
- 이렇게 되면 localhost:3090(프론트 주소)가 localhost:3095/api/users에 요청을 보낸 것이다. (CORS에러 발생)
- CORS에러의 해결 방법으로는 2가지가 있다. 1)프론트에서 proxy설정을 해주거나 2)백엔드에 CORS에러 해결을 해달라고 요청하는 것
proxy 설정을 통한 변경
webpack.config.ts
devServer: {
historyApiFallback: true, // react router(원래 서버는 root url밖에 모른다.)
port: 3090,
devMiddleware: { publicPath: '/dist/' },
static: { directory: path.resolve(__dirname) },
//CORS에러 프론트에서 해결하는 방법
// ? /api/주소로 보내는 요청은 주소를 target으로 바꿔서 보내겠다 라는 의미
// 그렇다면 axios.post요청에 url을 /api/something으로 보내면 target으로 변환된다.
proxy: {
'/api/': {
target: 'http://localhost:3095',
changeOrigin: true,
},
},
},
- 위와 같이 프록시 설정을 해주면 '/api/' 로 요청하는 값들은 target으로 변경하여 요청을 보낸다는 의미이다.
- 즉, localhost:3090 에서 보내는 요청이 아니라 주소를 아예 localhost:3095로 변경하여 요청을 보낸다는 소리이다.
- 변경 전에는 localhost:3090 이 3095에 요청한 거라면 변경 후에는 프록시를 통해 3095가 3095에게 요청한 것처럼 할 수 있다.
- axios요청 url도 아래와 같이 변경해주면 CORS에러가 해결된다.
axios.post('/api/users', {
email,
nickname,
password,
})
✅ webpack의 config를 수정하면 핫 리로딩이 안되므로 서버를 재실행해주어야 한다. (ctrl + c 를 통해 서버를 종료하고 다시 재실행 필요)
웹팩 빌드 명령어 세팅하기(배포환경 세팅)
webpack을 설치할 때, webpack-cli도 같이 설치한다. 이렇게하면 webpack 명령어를 실행할 수 있다.
- webpack을 명령어로 쓰고 싶다면 웹팩 자체를 -g 옵션으로 전역으로 설치하면 npx없이 webpack 명령어로 실행이 가능하다. 그러나, -g 옵션으로 글로벌하게 라이브러리를 설치하는 것은 지양되는 추세이므로 굳이 웹팩을 전역으로 설치하지 않는다.
- `npm i webpack` 혹은 `yarn add webpack` 을 통해 로컬 환경에 웹팩을 설치하고 그 다음 `npx webpack`명령어를 입력하면 명령어 실행이 가능하다. => 이때, webpack-cli가 설치되어있지 않는다면 `npx webpack` 입력시 설치할 거냐고 물어본다. 이때 설치해주면 된다. (내가 패키지 매니저를 yarn으로 사용하고 있기 때문에 물어봤을 수도 있다. 만약에 물어보지 않는다면 webpack-cli도 처음부터 같이 설치해주면 된다.)
- 설치 이후에 아래와 같은 에러 메세지가 나왔다.
[webpack-cli] Unable load 'C:\react-slack-clone\front\webpack.config.ts'
[webpack-cli] Unable to use specified module loaders for ".ts".
[webpack-cli] Cannot find module 'ts-node/register' from 'C:\react-slack-clone\front'
[webpack-cli] Cannot find module 'sucrase/register/ts' from 'C:\react-slack-clone\front'
[webpack-cli] Cannot find module '@babel/register' from 'C:\react-slack-clone\front'
[webpack-cli] Cannot find module 'esbuild-register/dist/node' from 'C:\react-slack-clone\front'
[webpack-cli] Cannot find module '@swc/register' from 'C:\react-slack-clone\front'
[webpack-cli] Please install one of them
이 에러 메세지에서 알 수 있는 사실은 바로 `웹팩은 원래 TS를 인식하지 못한다는 점`이다.
그렇기 때문에 ts-node 라이브러리를 설치(yarn add ts-node)하고 tsconfig.json의 내용에 ts-node 설정을 아래와 같이 추가해줘야 한다.
tsconfig.json
{
"compilerOptions": {
"esModuleInterop": true,
"sourceMap": true,
"lib": ["ES2020", "DOM"],
"jsx": "react", //jsx가 다른 곳에서도 인식될 수 있기 때문에
"module": "esnext", //최신 모듈시스템(import export을 쓰겠다는 것
"moduleResolution": "Node",
"target": "es5",
"strict": true,
"resolveJsonModule": true, //import json 허락
"baseUrl": ".", //import 할 때, 절대경로처럼 간편하게 설정
"paths": {
"@hooks/*": ["hooks/*"],
"@components/*": ["components/*"],
"@layouts/*": ["layouts/*"],
"@pages/*": ["pages/*"],
"@utils/*": ["utils/*"],
"@typings/*": ["typings/*"]
}
},
"ts-node": {
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "Node",
"target": "es5",
"esModuleInterop": true
}
}
}
webpack 명령어를 통해 빌드할 때, `NODE_ENV=production npx webpack` 라고 옵션값을 주면 자바스크립트 용량이 줄어드는 좋은 효과가 있다.
- 주의점이 있다면 NODE_ENV=production 옵션은 맥과 리눅스 환경에서만 돌아가기 때문에 윈도우의 경우는 추가로 라이브러리(cross-env)를 설치해줘야 이 옵션을 사용할 수 있다. `yarn add cross-env`
- 즉 윈도우에서의 웹팩 빌드 명령어는 `yarn cross-env NODE_ENV=production webpack` 가 된다.
해당 명령어를 실행해보니 바로 에러를 마주하게 됐다. ^ ^
$ yarn cross-env NODE_ENV=production webpack
yarn run v1.22.19
$ C:\react-slack-clone\front\node_modules\.bin\cross-env NODE_ENV=production webpack
[webpack-cli] Failed to load 'C:\react-slack-clone\front\webpack.config.ts' config
[webpack-cli] Error: Cannot find module 'react-refresh'
Require stack:
- C:\react-slack-clone\front\node_modules\@pmmmwh\react-refresh-webpack-plugin\lib\utils\injectRefreshLoader.js
- C:\react-slack-clone\front\node_modules\@pmmmwh\react-refresh-webpack-plugin\lib\utils\index.js
- C:\react-slack-clone\front\node_modules\@pmmmwh\react-refresh-webpack-plugin\lib\index.js
- C:\react-slack-clone\front\webpack.config.ts
- C:\react-slack-clone\front\node_modules\webpack-cli\lib\webpack-cli.js
- C:\react-slack-clone\front\node_modules\webpack-cli\lib\bootstrap.js
- C:\react-slack-clone\front\node_modules\webpack-cli\bin\cli.js
- C:\react-slack-clone\front\node_modules\webpack\bin\webpack.js
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:933:15)
at Function.Module._resolveFilename.sharedData.moduleResolveFilenameHook.installedValue [as _resolveFilename] (C:\react-slack-clone\front\node_modules\@cspotcode\source-map-support\source-map-support.js:811:30)
at Function.resolve (node:internal/modules/cjs/helpers:108:19)
at Object.<anonymous> (C:\react-slack-clone\front\node_modules\@pmmmwh\react-refresh-webpack-plugin\lib\utils\injectRefreshLoader.js:16:47)
at Module._compile (node:internal/modules/cjs/loader:1105:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1159:10)
at Object.require.extensions.<computed> [as .js] (C:\react-slack-clone\front\node_modules\ts-node\src\index.ts:1608:43)
at Module.load (node:internal/modules/cjs/loader:981:32)
at Function.Module._load (node:internal/modules/cjs/loader:822:12)
at Module.require (node:internal/modules/cjs/loader:1005:19) {
code: 'MODULE_NOT_FOUND',
requireStack: [
'C:\\react-slack-clone\\front\\node_modules\\@pmmmwh\\react-refresh-webpack-plugin\\lib\\utils\\injectRefreshLoader.js',
'C:\\react-slack-clone\\front\\node_modules\\@pmmmwh\\react-refresh-webpack-plugin\\lib\\utils\\index.js',
'C:\\react-slack-clone\\front\\node_modules\\@pmmmwh\\react-refresh-webpack-plugin\\lib\\index.js',.js', g.ts',
'C:\\react-slack-clone\\front\\webpack.confi\webpack-cli\\lib\\webpack-cli.js',g.ts', \webpack-cli\\lib\\bootstrap.js',
'C:\\react-slack-clone\\front\\node_modules\\webpack-cli\\bin\\cli.js',\webpack-cli\\lib\\webpack-cli.js', \webpack\\bin\\webpack.js'
'C:\\react-slack-clone\\front\\node_modules\\webpack-cli\\lib\\bootstrap.js',
'C:\\react-slack-clone\\front\\node_modules\\webpack-cli\\bin\\cli.js', or documentation about this command.
'C:\\react-slack-clone\\front\\node_modules\\webpack\\bin\\webpack.js' slack-clone/front (main)
]
}
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
- 원인을 보니 react-refresh를 못찾겠다는 거 같아서 관련 플러그인이 적용된 webpack.config.ts 설정을 살펴보게 됐다.
webpack.config.ts
import path from 'path';
import ReactRefreshWebpackPlugin from '@pmmmwh/react-refresh-webpack-plugin';
[원인]
: 아무 생각없이 라이브러리의 종속성을 생각하지 않고 from 뒤에 명시된 패키지명만 추가 설치한 것
바로 이 부분이 문제였는데 다른 라이브러리들은 대충 어떤 식으로 돌아가는 지 알고 설치 후 임포트하였는데 ReactRefreshWebpackPlugin 부분은 그냥 복사 붙여넣기하고 `@pmmmwh/react-refresh-webpack-plugin` 만 설치했기 때문이다.
공식 홈페이지에 가보면 react-refresh도 같이 설치해주라고 나와있다.
또한, 버전도 맞춰줘야 하는데 둘 다 최신 버전으로 설치했기 때문에 이후 빌드작업이 성공적으로 됐다.
🔺해결방법: react-refresh 라이브러리도 같이 설치해준다.
# if you prefer yarn
yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
웹팩 빌드 명령어가 성공적으로 수행된다면 이제 package.json 에 script의 build명령어로 위의 명령어(yarn cross-env NODE_ENV=production webpack)를 저장해주면 된다. 빌드 명령어를 수행할 때, 너무 긴 명령어라 shortcut으로 활용하기 위해 이 작업을 한다고 생각하면 된다.
package.json
"name": "my-front",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "cross-env NODE_ENV=production webpack",
"test": "echo \"Error: no test specified\" && exit"
},
- 이렇게 스크립트로 저장해두면 기존 명령어를 `yarn build`로 활용할 수 있다. (npm run build 와 동일)
모든 패키지가 최신버전으로 업데이트될 필요는 없지만 버전에 따라서 호환이 되지 않는 경우도 있다. 이럴 때는 구버전의 패키지를 최신 버전으로 업데이트해주면 에러가 해결되는 경우가 있다.
결과적으로 빌드를 하게되면 output의 내용처럼 ./dist/app.js 로 하나로 응집된(빌드된) 파일이 생겨난다. 이 app.js를
index.html의 script태그에 직접 추가하여 화면에서 확인해볼 수 있다. (index.html도 따로 만들고 script 태그에 경로를 설정해서 보여주는 것도 직접해줘야 함)
즉, 기본적으로 bundle한 css,js파일들은 html 파일에 직접 추가를 해야하는 번거로움이 있는데 `html-webpack-plugin`를 사용하면 이 과정을 자동화할 수 있다.
// webpack.config.js
var HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {
entry: './src/index.js',
output: {
path: __dirname + '/dist',
filename: 'app.bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
title: 'Project Demo',
minify: {
collapseWhitespace: true
},
hash: true,
template: './src/index.html'
})
]
}
- 내 경우는 webpackHTML 플러그인을 설치하지 않았기 때문에 자세한 내용에 대해서는 아래의 링크를 참고하는 게 좋다. (수동으로 index.html을 생성하고 script태그넣고 확인해봤음)
- webpackHTML 플러그인 관련 블로그 자료
+ SPA의 index.html
- 검색 엔진이나 사용자가 SPA에서 가장 먼저 보는것은 index.html이다. 그래서 어플리케이션 전반에 걸친 핵심 css는 index.html의 style태그에 직접 작성하고 덜 중요한 css들은 js로 처리(우리가 위에서 만든 번들된 app.js파일)한다.
yarn 으로 구버전의 종속 패키지 확인하기
참고로 package.json의 구버전 라이브러리가 무엇인지 궁금하다면 outdated 명령어를 통해서 확인해볼 수 있다.
yarn outdated # or npm outdated
해당 명령어를 입력하면 아래와 같은 내용이 출력되는데 모두가 최신인 경우에는 아무 리스트가 뜨지 않는다.
Package Current Wanted Latest Package Type URL
lodash 4.15.0 4.15.0 4.16.4 devDependencies https://github.com/lodash/lodash#readme
underscore 1.6.0 1.6.0 1.8.3 dependencies https://github.com/jashkenas/underscore#readme
✨ Done in 0.72s.
- 여기서 현재 내 버전과 최신버전을 확인하고 필요한 패키지만 최신버전으로 재설치해주면 된다.
- 최신 버전으로 재설치할 때는 @골뱅이 뒤에 버전을 명시해주면 된다.
- 출처: https://classic.yarnpkg.com/lang/en/docs/cli/outdated/
Yarn
Fast, reliable, and secure dependency management.
classic.yarnpkg.com
'Frontend > TypeScript' 카테고리의 다른 글
[Typescript | React] 커스텀 훅만들기 TS tips (0) | 2023.05.21 |
---|---|
[webpack] babel, TS, webpack 세팅하기 2(dev server) (0) | 2023.05.18 |
[TypeScript] 다형성(polymorphism)과 제네릭 (0) | 2023.05.02 |
[Typescript] React.FC를 사용하지 않아야 하는 이유(+ 리액트 컴포넌트 네임 스페이스 패턴) (0) | 2023.03.26 |
[TypeScript | hooks] useRef 사용법( ref 객체 타입) (0) | 2023.03.05 |
- Total
- Today
- Yesterday
- grid flex
- D 플래그
- reactAPI
- aspect-ratio
- float 레이아웃
- Prittier
- getServerSideProps
- text input pattern
- ~ ^
- 원티드 3월 프론트엔드 챌린지
- 항해99프론트
- 형제 요소 선택자
- is()
- 틸드와 캐럿
- 원티드 프리온보딩 프론트엔드 챌린지 3일차
- 항해99프론트후기
- tilde caret
- getStaticPaths
- 타입스크립트 DT
- 항해99추천비추천
- nvm 설치순서
- && 셸 명령어
- 프리온보딩 프론트엔드 챌린지 3월
- 원티드 프리온보딩 FE 챌린지
- 원티드 FE 프리온보딩 챌린지
- fs모듈 넥스트
- nvm경로 오류
- 타입스크립트 장점
- 프리렌더링확인법
- 부트캠프항해
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |