언제부턴가 creat-react-app과 Next.js의 편안함에 익숙해져 프로젝트의 기본이 되는 번들링에 대해 제대로 알고있지 못한 것 같다는 생각이 들었어요.
여러분들은 creat-react-app, creat-next-app 없이 리액트 프로젝트를 설정할 수 있나요?
저는 혼자 하기는 버거울 것 같아요. 그러니 저랑 같이 해보실래요?ㅎㅎ
제가 작업을 한 레포는 여기
입니다! 커밋이력을 참고하시면 이해해 더 많은 도움이 될 것이라 생각합니다.
yarn add react react-router-dom
yarn add webpack webpack-cli -D
npx webpack init
https://docs.npmjs.com/cli/v9/configuring-npm/package-json
https://webpack.kr/guides/getting-started/
웹팩을 처음 접하는 사람들은 loader라는 것이 굉장히 낯설고, 설정 과정에서 애를 먹는 경우가 많습니다. 저도 그랬어요.
loader
는 공식문서에서 이렇게 정의하고 있습니다.
In addition to that webpack supports modules written in a variety of languages and preprocessors via loaders. Loaders describe to webpack how to process non-native modules and include these dependencies into your bundles. - 원문
loader
를 통해서 웹팩은 다양한 언어로 쓰인 모듈들, 전처리기구들을 처리할 수 있도록 지원해줍니다. 로더는 외부 모듈들을 처리하는 방법, 그리고 이러한 의존성 모듈들을 번들에 추가하기 위한 방법을 웹팩에 알려주는 역할을 한다고 하네요.
웹팩이 번들링을 하기 전에, 다양한 파일들을 번들링이 가능한 형태로 변환하는 전처리작업을 해주는 것이 loader라고 생각하면 되겠네요!
webpcak.config.js 파일에서 export 하는 설정객체(webpack configuration object이니 편의상 config객체 라 부르겠습니다)의
Module
객체는, 프로젝트 내부에 있는 수많은 파일(모듈들)이 어떻게 webpack에 의해 처리될지를 결정하는 옵션들을 담고 있습니다.
config 객체에서 module.rules
의 값으로 저장되는 객체는 Rule객체입니다.
Rule객체는 아래처럼 module.rules 내부에 대입됩니다.
//webpack.config.js
...
module:{
rules: Rule[]
}
...
Rule객체는 Conditions
,Results
,nested Rules
이렇게 세 부분으로 이루여져 있습니다.
...
module: {
rules: [
{
loader: 'babel-loader',
test: /\.(js,jsx)$/i, <-요부분이 Condition
exclude: /node_modules/,
options: {
presets: [
['@babel/preset-env'{targets:'defaults' }]
],
}
}//UseEntry 하나
]//module.rules
...
Rule
에는 test
,include
,exclude
,resource
가 resource(요청된 파일의 절대경로)와 매칭되고, issuer
프로퍼티가 issuer(파일을 요청한 파일의 절대경로)과 연결됩니다.
.js
, .jsx
에 해당하는 모듈들이 test 조건을 통과하게되고, webpack은 해당 Rule에 명시되어있는 loader의 도움을 받아 관련 작업을 진행하겠네요. loader는 Rule객체 내부의 loader
, options
,use
프로퍼티에 있는 값들에 의해 영향을 받습니다.Rule.loader
은 Rule.use: [ { loader } ]
의 단축형 표현이라 합니다. Rule.loader
와 Rule.use
는 같이 쓰이면 안되겠군요.
Rule.loader
를 사용한 UseEntry와 Rule.use
를 사용한 UseEntry는 각각 아래와 같겠네요.
module: {
rules: [
{
loader: 'babel-loader',
test: /\.(js,jsx)$/i,
exclude: /node_modules/,
options: {
presets: [
['@babel/preset-env'{targets:'defaults' }]
],
}
}//UseEntry 하나
]//module.rules
아래 코드에서 확인할 수 있다시피 Rule.use에는 UseEntry[] 가 대입됩니다.
...
module: {
rules: [
{
test: /\.(js,jsx)$/i,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { targets:'defaults' }]
],
},
},
],
}//UseEntry 하나
]//module.rules
webpack-UseEntry는 반드시 문자열타입의 loader
프로퍼티를 가져야 합니다.
UseEntry를 이해한 바를 간단히 설명하자면
loader
:어떤 loader를 사용할 것인지 - stringoptions
: 이 loader에 반영할 옵션들 - string,objecthttps://medium.com/age-of-awareness/setup-react-with-webpack-and-babel-5114a14a47e9#9b0c
npx webpack init
을 하면 기본적인 webpack config 파일을 생성해주지만, React를 번들링하는데 쓰려면 몇 가지 더 설정을 해 주어야 합니다.
@babel/preset-react
아래와같은 에러가 발생했습니다.
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| const root = createRoot(domNode); | root.render( <StrictMode> | <App /> | </StrictMode>
원인은 정규식 오타였습니다.
{
test: /\.(js,jsx)$/i, <-오타발생
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['env', 'react'],
},
},
],
},
{
test: /\.(js|jsx)$/i, <-수정
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['env', 'react'],
},
},
],
},
저는 babel-preset-env를 설치했는데...왜 못찾는다고 할까요?
프리셋명을 풀네임으로 명시해주었더니 해결되었습니다.
presets: ['@babel/preset-env', '@babel/preset-react'],
App.jsx
를 확장자가 없는 App
으로 읽으려니 웹팩이 탈이 난 것입니다.
그럴 때는 resolve.extentions에 ['.js', '.jsx']를 추가하여 웹팩이 js파일과 jsx파일의 확장자를 알아차리게 해줍시다.
우리는 드디어 첫 화면을 보게되었습니다.
지금까지 고생 많으셨습니다.
다음 시간에는 타입스크립트를 이 프로젝트에 적용해보도록 하죠!
끝내기 전에 마지막으로 이것 하나만 더 하죠.
create-react-app 사용하실 때 컴포넌트 작성할 때마다 React 관련 API를 호출하지도 않는데 import React
를 코드에 추가하신 기억이 있으신가요? 아마 없을겁니다.
그렇지만 우리가 이제껏 작성한 파일들에서는 무조건 import React from 'react
를 써야 제대로 프로젝트가 구동되고 있습니다. 만약 실수로라도 이 구문을 빼버린다면 에러가 나죠.
가령, index.js에서는 React를 사용할 일이 없어서 무심코 아래처럼 코드를 작성하고 프로젝트를 구동하면...
//index.js
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const domNode = document.getElementById('root');
const root = createRoot(domNode);
root.render(
<StrictMode>
<App />
</StrictMode>
);
"React is not defined" 에러를 콘솔창에서 발견하시게 될 겁니다.
왜 이런걸까요?
JSX는 내부적으로 React.createElement()
로 변환되기 때문에 위에서 언급한 import문이 필요했습니다. React 17버전 전까진 말이죠.
이 문제에 대한 해결법은 생각보다 간단합니다."React automatic runtime"이라는 것을 사용하면 됩니다.
이 기능은 React 17버전 에서 추가되었습니다.
https://so-so.dev/react/import-react-from-react/
바벨에서도 이런 변화가 있는데요
https://babeljs.io/blog/2020/03/16/7.9.0#a-new-jsx-transform-11154
Manual Babel Setup
Support for the new JSX transform is available in Babel v7.9.0 and above.
yarn upgrade @babel/core @babel/preset-react
바벨을 업데이트하고, @babel/preset-react 의 옵션 중 "runtime":"automatic"으로 설정해주시면됩니다.
//.bablerc
{
"plugins": ["@babel/syntax-dynamic-import"],
"presets": [
"@babel/preset-env",
["@babel/preset-react", { "runtime": "automatic" }]
]
}
그러면 이제 React를 import하는 구문들은 쓸모가 없어졌죠?
여기에 나와있는 커맨드를 실행해주시면 놀랍게도 React를 import 하는 부분만 쏙 빼준답니다!
그럼 다음 시간에 만나요!
코멘트와 댓글은 언제나 환영입니다! 😎
아주 잘 작성된 글이었습니다.