부제 : Webpack 과 Build 그리고 Serve
: scoop을 쓰면 된다. 그러나, 직접 다운받아도 상관없다.
npx create-react-app [project_name] --template typescript
위와 같이 타입스크립트를 사용해서 프로젝트를 만들어봤다.
npm run build
라는 명령어로 build(번들링이 포함된)를 해보면 cra로 만든 프로젝트는 기본적으로 웹팩을 쓰기 때문에 웹팩에 의해 번들링 작업이 이뤄진다. 이에 따라 결과적으로
위와 같은 build 폴더 하위에 모듈들이 번들링된 모듈 번들들이 생기게 된다(?). 이에 더하여 index.html도 본래 프로젝트 내부에 root/public/
아래에 있는 index.html과 별도의 index.html이 생기고, 여기에서는 새로 만든 번들을 참조하는 형태로 만들어진다. 즉, 번들링된 모듈들을 참조하는 새로운 방식의 index.html이 빌드 과정에서 생긴 것이다. 실제로 최적화 작업을 할 때 이미지 파일 혹은 css 파일 등이 너무 크면(앞서 말한 모듈들) chunk 혹은 main 이라는 이름이 섞인 파일들이 분량을 크게 차지하고 있음을 확인할 수 있다. 이는 결론적으로 번들링된 파일이 너무 크다는 말이고, SPA인 리액트 앱에서는 이게 초기 렌더링 대기 시간에 영향을 줄 수 있다. 어쨌든, cra로 만든 플젝에서 웹팩은 이 과정을 나름의 가장 합리적인 방향으로 진행하지만, 자기가 만들 프로젝트의 상태에 따라 웹팩 설정을 달리한다던지 혹은 cra로 만들지 않고 플젝을 만들어서 번들링을 좀더 효율적으로 하여 최적화를 도모할 수 있다.
어쨌든 여기서 기억해야할건 cra로 만들던 package.json을 직접 만들며 플젝 세팅을 직접하던, 또, 번들링 수단으로 웹팩을 쓰던, 결국엔 프로젝트 모듈들을(CSS, JS, HTML, image file, font file 등등) 번들링해야하는 과정은 필수적이라는 것이다. 그리고 여기서 말하는 번들링이란 쉽게 말해 필요한 파일 모듈들을 결합해 훨씬 단순한 형태의 모듈로 변환해주는 것을 말한다.
: 앞서 말한대로 어떤 형태로든 build한 디렉터리를, 즉, 만든 웹 애플리케이션을 유저들에게 서비스하려면(브라우저를 통해) 어떻게 해야할까?. 이를 배포라고 부르는데, 실제로는 아파치나, nginx 와 같은 서버를 이용해야 한다. 하지만, 지금은 local에서만 실행을 해보도록 한다.
npm install -g serve
serve -s build
이를 커맨드 창에 입력하면 로컬에서 현재 빌드한 웹앱을 볼 수 있다(서비스 할 수 있다. only 로컬에서만). url 창에(브라우저) localhost:3000
을 치고 들어가보면 다음과 같이 cra로 만든 리액트 프로젝트의 메인 화면을 볼 수 있다.
그러면 이 페이지는 어떤 소스를 바탕으로 렌더링되는지 network 탭을 보면 다음과 같다.
위의 파일들(아까 말한 번들링된 모듈)을 보면 어디서 본 것 같은 이름이다.. 뭐였지?
아까 프로젝트를 빌드했을 때 생긴 파일명과 동일한 파일들이 network 탭에 보인다. 이말은 빌드한 파일들을 특정 서버(serve 를 통해 임시로 로컬에 배포한)를 통해 브라우저가 받아왔고(network 탭에서 봤다), 이 파일들을 바탕으로 위에서 본 리액트 기본 화면을 렌더링할 수 있게 된거다(웹앱을 유저에게 서비스하게 된거다). 참고로 network 탭의 localhost라고 써있는 파일이 index.html과 동일하다.
다시 플로우를 정리해보면
: 앞서 해본 과정은 build -> serving(=deploy)였다. 그러면 이번엔 개발자가 유저에게 아직 serving하기 전에 단지 개발을 위해 로컬 브라우저에 아까 디폴트 리액트 화면을 띄우는 과정을 해보자.
npm run start
이 과정은 아까처럼 build 디렉터리를 생성하지 않는다(build 파일을 다시 지워보고 npm run start를 해보면 안다). 하지만 build -> serve를 했었을 때처럼 localhost:3000에서 해당 서비스가 제공되는 것처럼 보이는데, 이는 npm run start를 하게 되면 웹팩에서 서버 형태로 구동을 하기 떄문이다. 정리하면
npm run start
-> react-scripts start
-> 웹팩 웹 서버 가동 -> 웹 브라우저에서 호출 가능 상태(로컬만) -> 호출시 해당 서버에서 보낸 파일을 받아서 렌더링
이를 보면, 결국 build, serve 명령어를 쓰진 않았지만 내부적으로 플젝 디렉터리 파일들을 빌드하여 번들 파일로 변환하고, 이를 반영한 index.html 을 만들어서 해당 파일들을 통해 웹 페이지를 화면에 보여주는 것으로 볼 수 있다. 다시 정리해보면, 앞서 말한 build -> servce 순서를 webpack이 알아서 똑같이 실행한다는 것과 같은 의미이다.
: 이전에 npm run start
를 통해 개발모드로 진입했었고, 그 개발 모드에서는 웹팩 서버를 통해 localhost:3000(port넘버는 달라질 수 있다)에서 호스팅을 하도록 했고, 이 방식으로 개발을 하고 있었다. 그러면, 현재 프로젝트 디렉터리에서 일부 코드를 수정하면 어떻게 될까?. 그 코드는 즉각적으로
현재 브라우저의 화면에 반영된다. 이 때, 이러한 기능은 웹팩이 제공하는건데, 이 기능을 HMR 이라고 한다. 하지만, 이 핫 모듈 교체 기능은 완벽하진
않다. 따라서 만약 에러가 생기거나 한다면 새로고침을 해봐야한다. 내부 로직상 특정 코드 등이 변경되면 웹팩이 해당 부부을 재빌드하고, 이를 웹 브라우저의 자바스크립트 코드에 심어둔 웹팩 코드와 협업하여 변경된 내용을 실시간으로 반영한다고 한다.
: 위에서 말한 HMR은 프로덕션 모드에선 당연히 자동으로 비활성화 된다.
파일을 저장하면 Parcel 은 변한 부분을 다시 빌드하고 이에 영향을 받는 모든 실행중인 클라이언트에 새 코드를 보냅니다. 그 후 새 코드는 이전 버전과 교체되고 모든 부모가 함께 다시 평가 됩니다. 이 과정 중 module.hot API 를 사용해 훅(hook)을 걸수 있습니다. 이를 통해 모듈이 버려질 때, 또는 새 버전이 들어올 때 코드에 알려줄 수 있습니다. react-hot-loader같은 프로젝트는 이 과정에 도움이 되며, Parcel 에 바로 쓸 수 있습니다.
by https://ko.parceljs.org/hmr.html
좀 더 내부로직을 생각해보면, 특정 코드를 수정했을 때 그 부분이 완전히 독립적이긴 힘들다. 분명 그 코드의 수정으로 인해 디펜던시가 걸려있는 다른 코드가 있을 것이라는 것이다. 이에 따라 특정 부분이 수정되면 이에 따른 의존관계를 파악하고, 그 의존 관계에 얽혀있는 것들도 모두 갱신해줘야한다. 예를 들어, A 컴포넌트(부모)에서 B컴포넌트(자식)로 특정한 props를 내려주고 있을 때, 그 props의 로직이 변경됐다면(A에서) B에도 해당 부분이 연쇄적으로 영향을 미칠 것이다.