외주 프로젝트 중 Next로 프로젝트를 시작 했었는데, 사실 Next로 기능을 많이 사용하지 않았다. 그래서 React와 Webpack을 사용해 앱을 다시 구성해본 여정을 기록한다.
Next와 React는 프레임워크와 라이브러리이다. 주된 차이점은 제어의 주도권이 누가 가지고 있느냐가 핵심인데 자세히 살펴보면 아래와 같다
프레임워크는 응용프로그램을 프레임워크가 flow를 관리하고, 개발자는 필요한 기능이 생긴다면 프레임워크가 가지는 Logic
, Rule
에 맞춰서 사용한다.
라이브러리는 응용프로그램이 라이브러리를 사용하는 개념이고, 개발자가 필요한 기능을 제공하는 라이브러리를 사용하면 된다.
먼저 React는 UI를 만들기 위한 도구일 뿐이므로 개발에 편리한 개발서버설정이나, JSX문법 지원하기 위해선 바벨설정 등 여러가지 설정을 해줘야된다. 그래서 보통 React만으로 앱을 구성한다 하면 Webpack설정이 무조건 들어간다고 생각하면 된다.
(물론 진짜 순수 createElement라는 함수로 앱을 만들 수 있지만 리액트공식문서에서도 JSX를 권장한다)
그래서 웹팩설정을 보통 보편적으로 해주기 때문에 CreateReactApp(CRA)이라는 도구를 통해 앱을 구성한다.
또한 CRA를 통해 앱을 구성하면, CSR(Client-Side-Rendering)방식으로 앱이 동작하는데 UI를 클라이언트에서 화면을 구성한다.
CSR의 동작에 대한 관점을 유저와 서버, 브라우저의 입장에서 생각을 해보자.
즉, JS가 다운로드가 마치고 실행이 완료될 때 까지 원하는 UI를 볼 수 없다.
따라서 장점으로는 처음에 JS파일을 모두 불러오기 때문에 초기에 로딩만 기다린다면 이후에 화면 전환시 서버에 요청하는 것이 AJAX를 통한 data 정도이기 때문에 부하도 줄고 랜더링 과정도 빠르다.
React기반의 오픈소스 프레임워크 입니다. 핵심은 SSR과 SSG를 제공해주고, 자동 코드 분할, API라우터 등 다양한 기능들을 제공하는 프레임 워크 입니다.
마찬가지로 유저, 서버, 브라우저 입장에서 설명을 하면 다음과 같다.
이미 랜더링된 HTML을 받으므로 초기 로딩속도가 빠르고, 검색엔진이 HTML코드를 바로 확인할 수 있어 SEO에 용이하다.
그러나 view가 바뀔 때마다 서버에 UI를 계속 요청하기 때문에 부하가 CSR보다 크다. 또한 사용자 관점에서 새로고침되는 느낌이 들어 사용자 경험이 저하된다.
위에서 설명한 SSR의 4번 과정에서 JavaScript가 로드되면, React는 서버에서 렌더링되어 클라이언트에 보내준 HTML에 생명을 불어넣어주는 작업을 한다.
이 과정을 hydrates라고 하는데, 이는 React가 서버에서 생성된 마크업을 인식하고, 해당 마크업에 React 컴포넌트의 이벤트 리스너와 상태 관리 기능을 연결하는 과정이다.
예를 들어, 블로그 포스트의 "좋아요" 버튼이나 "댓글 작성" 폼이 이제 클릭 가능하고, 사용자 입력을 받을 수 있게 된다. 즉 상호작용이 가능한 페이지로 변환이 되는 것이다.
우선 next와 관련된 package들을 yarn remove명령어를 통해 제거한뒤 React,Typescript를 설치하였다.
yarn add react react-dom
yarn add -D typescript @types/react @types/react-dom
yarn tsc --init
그런다음 Webpack에 관한 패키지를 설치한다
yarn add webpack —dev
yarn add webpack-cli —dev
yarn add html-webpack-plugin webpack-dev-server ts-loader --dev
웹 페이지를 만들 때 수 많은 파일들이 연결이 될 때 여러가지 문제가 발생하는데 그것들을 해결하기 위해 수 많은 파일을 하나로 묶어주는 도구이다. (이러한 도구를 번들러라고 한다 => webpack
말고 parcel
도 있다)
일단 여러 개의 파일을 하나로 묶어주기 때문에 클라이언트-서버의 통신부하가 줄어든다. 그리고 모듈끼리는 전역스코프를 공유하는 문제가 있는데, 이 문제를 해결하기 module타입이 나오기 전까진 즉시실행 함수로 감싸주었지만, ES6에서는 module을 통해 쉽게 해결할 수 있다.
그러나 모든 브라우저가 지원하는게 아니여서 js가 어떤버전에 맞춰서 작성됫냐에 따라 호환성 문제가 발생하는데 그것을 모듈별로 스코프를 만들어주는 역활로서 웹팩이 수행이 된다.
공식문서에서 제시한 6가지의 개념을 먼저 이해해야한다.
1) filename: 번들링 결과 파일명
2) path: 번들링 결과 파일 경로
3) clean: 번들링 시 이전에 생성되어있던 번들링 파일을 지우고 번들링
내가 했던 설정을 예시로 살펴보자.
//...
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/, // .css 확장자를 가진 모든 파일에 적용
use: ['style-loader', 'css-loader'], // css-loader를 적용한 후 style-loader 적용
},
{
test: /\.svg$/, // .svg 확장자를 가진 모든 파일에 적용
type: 'asset/resource', // 파일을 별도의 파일로 내보내고 URL을 제공
},
],
},
//...
rules에서 loader를 적용할 파일들을 정의해주고 각 파일에 대해 적용할 loader를 정의하는 개념이다.
1) test: loader를 적용할 파일의 확장자를 정규표현식으로 선언
2) exclude: 정규표현식에 일치하는 파일 중에서 제외할 파일
3) use: 사용할 loader들 선언
내가 사용한 플러그인을 보면 아래와 같은대 각각의 사용법과 설명은 플러그인마다 다르니 확인해야한다.
1) HtmlWebpackPlugin: html을 자동으로 생성해주는 역활. 해당 플러그인 없이 빌드하면 html파일이 없기 때문에 일일히 추가해줘야된다. 그 문제를 해결하기 위한 플러그인
2) CleanWebpackPlugin: 빌드시 이전에 남아있던 빌드 결과물을 지우고 새로 생성하는 플러그인
//...
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
//...
1) production 모드 : 로드 시간을 줄이기 위해 번들 최소화, 가벼운 소스맵 및 애셋 최적화에 초점을 맞춘다.
2) development 모드 : 개발 생산성을 높이기 위한 모드. 버그발생 위험이 있는 코드를 미리 경고해 주는 검증 코드도 포함되어 있다.
const config: Configuration = {
mode: 'development',
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
clean: true,
},
devtool: 'inline-source-map',
devServer: {
static: './dist',
hot: true,
port: 3001,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.(js|jsx|ts|tsx)$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/, // .css 확장자를 가진 모든 파일에 적용
use: ['style-loader', 'css-loader'], // css-loader를 적용한 후 style-loader 적용
},
{
test: /\.svg$/, // .svg 확장자를 가진 모든 파일에 적용
type: 'asset/resource', // 파일을 별도의 파일로 내보내고 URL을 제공
},
],
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src/'), // '@/': './src/' 경로에 대한 별칭 설정
},
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './public/index.html',
}),
new DefinePlugin({
'process.env.MAP_API_KEY': JSON.stringify(process.env.MAP_API_KEY),
'process.env.NEXT_PUBLIC_API_URL': JSON.stringify(
process.env.NEXT_PUBLIC_API_URL,
),
'process.env.WEATHER_API': JSON.stringify(process.env.WEATHER_API),
}),
],
};
export default config;
설정에서 잠깐 Devserver부분을 살펴보자.
//...
devtool: 'inline-source-map',
devServer: {
static: './dist',
hot: true,
port: 3001,
historyApiFallback: true,
},
//...
devServer는 먼저 webpack-dev-server
라이브러리를 설치해야 한다.