패키지 매니저를 정했으니 이제 필요한 패키지들을 설치해보자
yarn init-y
package.json 파일을 생성해준다.
직접 package.json 파일을 커스텀해 줄 것 이기 때문에 -y(--yes) 로 질문들을 스킵해준다.
yarn add react react-dom
yarn add -D typescript @types/react @types/react-dom
리액트와 리액트 돔, 타입스크립트를 설치해준다.
이때 타입스크립트는 개발할때만 필요하기 때문에 devDependency로 설치해준다.
yarn add -D webpack webpack-cli webpack-dev-server
웹팩과 같은 번들러는 개발단계에서만 필요하기 때문에 이 아래부터는 -D 로 설치해준다.
webpack-cli : 웹팩 커맨드를 터미널에서 실행할 수 있게 해줌.
webpack-dev-server : 개발서버를 열 수 있게 해줌.
yarn add -D ts-loader file-loader
yarn add -D html-webpack-plugin copy-webpack-plugin
로더와 플러그인을 설치해준다.
(아래에서 자세한 설명을 볼 수 있다.)
의존성이 있는 모듈들을 찾아 하나의 자스 파일로 번들링 해주는 정적
모듈
번들러
이다.
모듈
이란 재사용 가능한 코드 블럭 이다.
웹팩에서의 모듈이란 웹을 구성하는 모든 자원을 뜻한다.( .js 파일, html, css, font 등…)
만약 js 파일이 모듈이 아닌 일반 스크립트라면 변수의 유효 범위가 없어서
<script src="./app.js"></script>
<script src="./main.js"></script>
html 파일에서 이와 같이 두 자스 파일을 로딩하게 된다면 app.js 과 main.js 가 서로 영향을 미칠 수 있게 된다.
그렇기 때문에 파일 단위로 변수를 관리하고 싶어 필요한 모듈을 불러오거나 코드를 모듈 단위로 구성해주는 모듈시스템이라는 라이브러리가 등장했다. (CommonJS , AMD 등)
💡 모듈은 일반 스크립트와 다음과 같은 점에서 구분된다.
- 모듈은 항상 defer 속성을 붙인 것 처럼 지연 실행 된다.(body 끝에 script 를 넣어주지 않더라도!)
- 모듈은 엄격모드로 실행된다.
- 모듈은 자신만의 스코프를 가진다.
- 모듈은 한번만 실행되고 공유된다.
- 모듈 최상위 레벨 this 는 undefined 다.
- import.meta 객체로 정보를 얻을 수 있다.
브라우저 환경에서는 보통 모듈을 단독으로 사용하기보다 번들링 해서 배포서버에 올리는 방식을 사용한다.
번들러
란 js,css,이미지 등의 파일을 묶어주는 역할을 하는 것을 뜻한다. 번들링이 끝난 모듈은 일반 스크립트처럼 취급한다.(import,export 가 사라지며 type=”module”이 필요 없어짐)
모듈 번들링
이란 모듈을 브라우저가 인식할 수 있도록 함수 형식으로 변환시킨 다음 모듈 간에 참조할 수 있도록 하나의 파일로 합치는 것을 의미한다.
웹팩은 번들링 과정에서 의존성 그래프
를 그린다.
특정 지점(Entry)에서 출발해서 애플리케이션에 필요한 모든 모듈을 포함하는 그래프를 완성해 나간다. 그래프를 완성하면 이 모든 모듈을 번들링 후 브라우저에 로드 할 준비를 한다.
웹팩이 어떤 모듈을 사용하여 번들을 시작할지를 알려주는 역할.
entry: './src/index.tsx',
웹팩이 생성한 번들을 출력할 위치를 지정하는 속성.
번들링된 파일의 이름과 저장될 경로를 정할 수 있다.
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
// build 시작 전 dist 파일을 정리해준다.
// 이걸 설정해주면 clean-webpack-plugin 을 설치 할 필요가 없다!
},
웹팩은 기본적으로 js 와 JSON 파일만 이해할 수 있다.
사용하려는 포맷에 대응하는 로더를 설정해주면 js 가 아닌 다른 포맷의 리소스도 디펜던시 그래프에 추가할 수 있다.(파일 해석, 변환 과정에서 필요)
loader 설정에는 test, use
라는 필수 속성을 설정해줘야 한다.
test 는 어떤 파일을 변환할지 지정,
use 는 파일을 변화할 때 어떤 로더를 사용해야 하는지 명시하는 속성이다.
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
},
},
// {
// test: /\.css$/,
// use: ["style-loader", "css-loader"],
// },
// css-loader는 css 파일들을 읽어주고 style-loader는 읽은 css 파일들을 style 태그로 만들어 head 태그 안에 넣어준다.
// 우리는 panda css 를 사용하며 css 파일을 따로 두지 않을 것이기 때문에 설치해주지 않았다.
},
],
},
ts-loader, file-loader 를 설치했다.
❓ ts-loader vs babel (vs esbuild)
babel(babel-loader) : 트랜스파일러. tsconfig.json 의 옵션을 확인하지 않는다. (대신 @babel/preset-typescript 의 기본 옵션 사용)
타입체크를 진행하지 않기 때문에 ts-loader 보다 빠르다.
ts-loader(tsc) : 트랜스파일링을 진행할때 tsconfig.json 의 옵션을 따라 타입 체킹을 한다.
esbuild(esbuild-loader) : 타입체크를 하지 않지만 tsconfig 옵션을 참고한다.(일부만) 빌드 속도가 가장 빠르다고 한다.
속도 차이가 궁금하다면npx webpack
명령어를 통해 확인해보자!
우리는 빌드 속도 보다는 타입 체크를 하는게 더 중요하다고 생각해서 ts-loader 를 설치해주었다.
그리고 로고 이미지를 넣을 예정이라 file-loader 도 같이 설치,설정해주었다.
플러그인은 웹팩의 기본적인 동작에 추가적인 기능을 제공하는 속성이다.(결과물의 형태를 바꿈)
설치한 플러그인
html-webpack-plugin
dist 의 main.js를 스크립트 파일로 포함하는 html 문서를 dist 디렉토리 내에 자동으로 생성하는 역할을 한다. 이 플러그인을 설치해주지 않는다면 빌드 시 직접 html 파일을 작성해서 js,css 파일들을 넣어주어야 한다.(내용이 변경될때마다 html 파일도 수정해야됨!)copy-webpack-plugin
웹팩 빌드 시점에서 파일을 복사하는 플러그인. 웹팩으로 빌드한 결과물 외에 원하는 파일 추가로 복사할 수 있다. public 아래에 존재하는 이미지, 문서 등을 빌드 후에도 사용하기 위해 설치해주었다. 만약 이 플러그인이 없다면 public 폴더의 정적 파일들을 수동으로 dist 폴더에 복사해주어야 할 것이다.plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: './index.html',
}),
new CopyWebpackPlugin({
patterns: [{ from: 'src/assets', to: 'assets/' }],
}),
],
우리는 이정도의 플러그인만 설치하고 나머지는 프로젝트 중간에 직접 불편함을 겪은 후 추가로 설치해보기로 했다.
package.json 에서
"scripts": {
"start": "webpack serve",
"build": "webpack --mode=production"
// 아직 프로덕션과 개발 환경을 분리하지 않아서 임의로 작성했다.
},
로 설정해주어서 yarn start 를 통해 개발서버를 열 수 있다.
개발 서버를 설정해주자.
devServer: {
client: {
overlay: true, // 컴파일 오류나 경고가 발생하면 브라우저 화면에 표시
progress: true, // 브라우저 콘솔에 빌드 진행 상태를 표시
},
compress: true, // 모든 served 파일에 대해 gzip 압축을 활성화
hot: true,
open: true, // 서버 시작 시 브라우저를 자동으로 연다.
port: 3000, // 개발 서버가 실행될 포트
historyApiFallback: true,
},
ReferenceError: __dirname is not defined
CommonJS가 아닌 ES6의 type: "module" 를 사용할 경우 에러가 발생한다. 우리는 es6 로 통일하기 위해 __dirname 변수를 따로 선언해주었다.
https://velog.io/@suyeonpi/Node.js-dirname-is-not-defined-ES6
typescript emitted no output
webpack + ts-loader 환경에서는 noEmit 설정을 false 로 해주어야 한다.
https://github.com/TypeStrong/ts-loader/issues/1602
// webpack.config.js
import path from 'path';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import CopyWebpackPlugin from 'copy-webpack-plugin';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const config = {
mode: 'development',
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: ['ts-loader'],
},
{
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
filename: './index.html',
}),
new CopyWebpackPlugin({
patterns: [{ from: 'src/assets', to: 'assets/' }],
}),
],
resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
devServer: {
client: {
overlay: true,
progress: true,
},
compress: true,
hot: true,
open: true,
port: 3000,
historyApiFallback: true,
},
};
export default config;
참고한 레퍼런스
https://joshua1988.github.io/webpack-guide/motivation/why-webpack.html#웹팩의-등장-배경
https://engineering.linecorp.com/ko/blog/write-you-a-webpack-for-great-good
썸네일이 너무 깜찍하네요~💕