Webpack과 React, 그리고 Code Splitting

jhj46456·2020년 9월 20일
5
post-thumbnail

📚 생략된 Webpack 설정은 CRA 없이 JS + React 개발 환경 구성CRA 없이 TS + React 개발 환경 구성를 참조해주세요.

React 및 JS/TS 기초에 대해 설명하진 않습니다. 초보자를 위한 내용이 아닙니다.

webpack v4 기준입니다.

📌 여기선 Code Splitting을 중점으로 다룰 예정입니다.


Why

Code Splitting을 왜 할까요? 그냥 웹팩으로 번들만 해줘도 홈페이지는 동작하는데 말이죠.

Code Splitting을 하는 이유를 찾기 위해선 원초적인 것 부터 알아야 합니다.

먼저 Server Side RenderingClient Side Rendering을 알아야 합니다.

두 가지 모두 페이징 처리 기법입니다.

차이점이라면 SSR은 페이지를 이동할 때마다 서버로부터 새로운 페이지를 받아옵니다.
반대로 CSR은 최초 접속 시에 홈페이지의 모든 페이지(JS)를 불러오고 API와 통신하여 추가적인 네트워크 요청 없이 페이지를 전환시킵니다.

그렇다면 React는 어떤 페이징 기법을 사용할까요?
기본적으로는 CSR이긴 한데, 요즘은 Next.jsReactDOMServerSSR을 구현할 수 있습니다.

물론 CSR + SSR을 합칠 수도 있지만 일반적인 CSR을 기준으로 설명드리자면

이전 내용에서 webpack으로 JS/TS 파일들을 번들한 이유도 이런 이유 때문입니다.

하지만 번들이란게 하나로 합치는 장점만 있는 것이 아닙니다.

프로젝트 규모가 커져서 번들 하나의 용량이 10MB가 넘어간다면 초기 로딩이 매우 길어지게 됩니다.

그럴 때, Code Splitting이라는 것을 사용합니다.


Split Chunk

Code Splitting을 하기 위해서 번들된 파일 하나를 여러 개로 나눠야 합니다. 나눠진 파일을 chunk라고 부릅니다.

webpack code splitting guide를 보고 설정을 해봤습니다.

📌 splitChunk의 기본 값 목록

📚 webpack.config.js
          👇
module.exports = (webpackEnv) => {
  const isEnvDevelopment = webpackEnv === "development";
  const isEnvProduction = webpackEnv === "production";

  return {
    output: {
      ...
      chunkFilename: isEnvProduction
        ? "static/js/[name].[contenthash:8].chunk.js"
        : isEnvDevelopment && "static/js/[name].chunk.js",
    },
    optimization: {
      splitChunks: {
        chunks: "all",
      },
      runtimeChunk: { 👈 브라우저 캐싱에 관심있다면 추가합니다.
        name: (entrypoint) => `runtime-${entrypoint.name}`,
      },
    },
};

build를 진행하면 다음과 유사한 결과가 보입니다.

필자의 사이드 프로젝트를 build한 이미지

chunk라는 것으로 분리가 되긴 했는데...

WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
  static/js/2.a7a17758.chunk.js (1.83 MiB)

webpack이 권장하는 asset size는 244KB 이하라고 합니다.


Compressor

필자가 생각해도 이 사이드 프로젝트 규모로 1.83MiB는 너무 용량이 많이 나왔습니다.

이럴 때, JavaScript parser and mangler/compressor toolkit을 사용합니다. uglify-js, babel-minify, terser 정도가 있습니다.
이 툴의 용도는 코드 난독화 및 공백 제거입니다. 공백도 1byte를 차지하기 때문에 최대한 다이어트를 해야 합니다.

코드 난독화의 이해를 돕기위해 필자의 실제 빌드 코드를 가져왔습니다.

const onSubmit = (event: React.FormEvent) => {
  event.preventDefault();
  const term = inputRef.current!.value;
  if (term === "") return null;
  inputRef.current!.value = "";
  inputRef.current!.blur();
  push(`?term=${term}`);
};

👆 코드가 👇 처럼 변경됩니다.

{onSubmit:function(n){n.preventDefault();var e=r.current.value;if(""===e)return null;r.current.value="",r.current.blur(),a("?term="+e)}}

필자는 셋 중 terser를 사용할 예정입니다.

webpack terser-webpack-plugin docs를 보면서 설정을 진행하겠습니다.

패키지 설치

yarn add terser-webpack-plugin -D

webpack.config.js 수정

📚 webpack.config.js
          👇
const TerserPlugin = require('terser-webpack-plugin');

module.exports = (webpackEnv) => {
  const isEnvProduction = webpackEnv === "production";
  optimization: {
    minimize: isEnvProduction,
    minimizer: [new TerserPlugin()],
  },
};

다시 build를 해봅니다.

main chunk는 206KB에서 125KB로 줄었습니다.
2번 chunk는 1.83MB에서 847KB로 50% 넘게 줄었습니다.

terser에 대한 설정 목록은 여기에서 확인이 가능하긴 한데,
필자는 기본 설정과 커스터마이징 둘 다 차이점은 못느껴 기본 설정으로 진행하였습니다.

커스터마이징이 필요하다면 문서를 보고 작업하면 됩니다.


Bundle Analyzer

어떤 부분을 Code Splitting을 해야 하는지 분석기가 필요합니다.

그 역할을 webpack-bundle-analyzer가 해줍니다.

패키지 설치

yarn add webpack-bundle-analyzer -D

webpack.config.js 수정

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = (webpackEnv) => {
  const isEnvProduction = webpackEnv === "production";
  plugins: [
    ...,
    isEnvProduction && new BundleAnalyzerPlugin(),
  ].filter(Boolean),
}

build를 입력해보세요.


이제 각 chunk 분석을 할 시간입니다.

보이는 것처럼 847.22KB chunknode_modules에서 발생하였습니다.

node_modules에서 발생한 경우에는 패키지 별로 문서를 찾아서 최적화를 하거나 동일한 기능의 더 가벼운 패키지를 찾아야 합니다.
혹은 해당 모듈의 기능을 직접 구현하거나요.

하지만 warning이기 때문에 꼭 244KiB 밑으로 만들어야 하는 건 아닙니다.


이번에는 src에 대한 chunk입니다.
콘솔에서도 244KiB를 넘지 않아 warning이 없었으니 React 상에서 Code Splitting해줄 건 없습니다.


Code Splitting

현재로서는 Code Splitting을 해줄 필요는 없지만 방법은 알아보겠습니다.

https://ko.reactjs.org/docs/code-splitting.html 를 방문해보면 import(), React.lazy, Route-based code splitting, Named Exports 총 네 가지 방법이 있습니다.

필자는 넷 중 Route-based code splitting를 사용해보겠습니다.

현재 작업 중인 사이드 프로젝트는 다음의 모듈 페이지를 가집니다.

import Home from "../screens/Home";
import Room from "../screens/Room";
import AddRoom from "../screens/AddRoom";
import Profile from "../screens/Profile";
import Auth from "./Auth";
import RoomEdit from "../screens/RoomEdit";
import Question from "../screens/Question";

이 중 HomeRoomCode Splitting해보겠습니다.

import React, { lazy } from "react";

const Home = lazy(() => import("../screens/Home"));
const Room = lazy(() => import("../screens/Room"));

react에서 lazy를 꺼내서 문서에 적힌 방법대로 컴포넌트를 불러옵니다.

import React, { Suspense } from "react";

<BrowserRouter>
  <Suspense fallback={<div>Loading...</div>}>
    <Header />
    <Container>
      <Routes />
    </Container>
  </Suspense>
</BrowserRouter>

Router 안을 Suspense로 감싸줍니다.

build:analyze를 하여 결과를 확인해봅니다.

chunk가 여러개 생겼습니다.

node_modules와 관련된 chunk는 용량이 그대로인 것을 볼 수 있습니다.

node_modules 관련은 제외한 분석기 화면입니다.

src chunk에서 HomeRoom만 분리되었네요.

이렇게 코드 스플리팅이 가능합니다.

Package manager

만약, npm을 사용 중이라면 yarn으로 대체하면 node_modules에 대한 chunk 용량을 줄일 수 있습니다.

필자가 동일한 프로젝트를 두 개의 package managerbuild를 해보았는데,

npm : 2.34 MB 👉 Compressor 👉 1.04 MB
yarn : 1.84 MB 👉 Compressor 👉 847 KB

npm은 패키지가 많아짐에 따라 build performance가 떨어지는 단점이 있는데

yarnnpm의 그 단점을 보완해냅니다.

요즘 npmyarn의 성능이 차이가 거의 없다고 하지만

Node.js Back-end는 가벼워서 뭐 그렇다 쳐도 Front-end Library를 이용할 때는 yarn이 필수이지 않을까 싶습니다.

@types/ 패키지 7개 + 일반 패키지 10개로도 거의 200KB가 차이나는데, 규모가 커지면 1MB 이상 차이가 나지 않을까요?

0개의 댓글