webpack 최적화

오민준·2023년 5월 8일
0

webpack의 기본 옵션

  • entry: 앱이 번들처리를 시작할 지점을 뜻하며 index.js부터 시작해 import문을 따라 빌드를 진행한다.
  • output: 번들링된 결과물의 위치 및 파일 이름을 지정해줍니다. path나 filename이라는 옵션으로 위치 및 파일이름을 지정할 수 있다.
    이미지나 파일 같은 외부 리소스를 로드할때는 publicPath가 필요한데 이는 prefix의 개념으로 사용자가 webpack.config.js에 설정한 주소가 실제 배포되는 주소 앞에 설정이 된다.
// putblicPath: /assets/

src="picture.jpg" -> /assets/picture.jpg
  • resolve: 모듈을 해석하는 방식을 변경할 수 있다. 예를 들어 alias 옵션을 이용할때 특정 모듈의 별칭을 만들 수 있으며 일반적으로는 사용되는 src 폴더의 별칭을 지정할 수 있다.

  • loader: 기본적으로 webpack은 javascript 및 json파일만 해석 가능한데 로더를 사용하면 webpack이 다른 포맷의 파일을 처리하고 앱에서 사용 가능한 모듈로 변환이 가능하다.

  • plugin: 로더가 파일 단위로 처리한다면 플러그인은 주로 번들된 결과물을 처리한다.

  • externals: 우리도 코드가 의존하고 있지만 빌드 과정에서 포함하지 않아도 되는 axios, jQuery같은 써드파티 라이브러리들을 여기에 명시하면 빌드 프로세스에 포함되지 않는다.

  • performance: 성능과 관련된 옵션으로 파일 크기가 250kb를 넘어갈 경우 경고를 띄워주는 옵션 등이 있다.

webpack의 최적화

  • 번들 사이즈 줄이기
    • webpack mode
    • source-map
    • 코드 스플리팅
    • 트리 쉐이킹
  • 빌드 속도 올리기
    • caching

webpack mode

webpack 모드의 종류

  • Development: 강력한 소스 매핑, localhost 서버에서는 라이브러리 로딩이나 hot module replacement 기능을 목적으로 사용한다.
  • production: 로드 시간을 줄이기 위해 번들 최소화 가벼운 소스맵 및 에셋 최적화에 초점을 맞춘다.
    기본적으로 tersurPlugin을 이용하여 코드를 압축, 난독화를 한다.

Source-map

  • production으로 빌드한 파일과 원본 파일을 서로 연결시켜주는 기능이다.
  • 소스맵은 원본 코드를 특정 알고리즘으로 인코딩하여 특정 키워드로 매핑을 시켜 놓으면 나중에 브라우저에서
    난독화된 코드를 그대로 디코딩하여 복원시킬 수 있다.
  • {
    	version : 3,
        file: "bundle.js"
        sourceRoot: "",
        sources: ["a.js", "b.js"]
    	names: ["src", "maps", "are", "fun"],
        mapping: "AAgBC, SAAQ, CAAEA"
    }
  • 소스맵의 구성
    • file : 연결된 파일의 이름
    • sources : bundle.js를 만드는데 활용되는 소스 코드 파일 목록
    • names : 소스코드의 모든 변수와 함수 이름이 기록된다.
    • mappings : 실제 코드와 매핑할 수 있도록 하는 데이터이며 Base64 VLQ로 인코딩되어 기록된다.

소스 레벨 종류

  • None
    • 소스맵을 생성하지 않는다.
    • 최대 성능의 Production build시 추천하는 방식
  • Source-map:
    • 고품질 소스맵을 포함한 Production build시 추천하는 옵션
  • 기본적으로 none과 source-map이 있고 대부분의 소스맵은 (option)- source-map 형식으로 진행된다.

option의 종류

  • eval

    • eval이 붙는 소스맵 옵션은 자바스크립트 함수인 eval을 사용해서 소스맵을 만듭니다
    • eval은 mode: development에서devtool 옵션을 주지 않으면 기본적으로 들어가는 소스맵이므로 실제로 소스맵을 생성하지 않으려면 devtool: false를 준 상태로 빌드를 진행해야한다.
    • eval 함수는 각 모듈을 따로 실행시켜 수정된 모듈만 재빌드하여 빠르지만 정확한 소스 코드 위치를 맵핑 하지 못한 경우가 종종 있습니다
  • inline

    • inline 옵션은 map 파일을 만들지 않고 주석에 파일을 data URL로 작성한다.
    • 따라서, source map이 독립된 파일로 존재하지 않고 bundle.js 파일 내에 포함되게 됩니다

  • hidden
    • 소스맵 옵션과 거의 동일하지만 소스맵에 대한 참조가 추가 되지 않는다.
    • 주로 사용자에게 개발자 도구에서 소스맵을 보여주고 싶지 않을 때 사용하는 옵션이다.

  • nosource
    • 주로 사용자에게 내부 소스 코드를 보여 주고 싶지 않을 때 사용하는 옵션이다.


  • cheap
    • 라인 넘버만 매핑하고 라인에서 몇 번째 글자인지는 매핑하지 않는 옵션입니다
    • 그래서 에러가 발생하면 몇 번째 라인에서 발생했는지 알 수 있지만 실제로 몇 번째 글자에서 발생했는지 알 수 없습니다.
    • 빌드 속도와 리빌드 속도가 약간 향상된다.

요약

모드별로 추천하는 소스맵

  • Development
    기본적으로 리빌드가 빠른 eval을 추천한다.

    • eval : eval은 매우 빠른 리빌드와 빠른 빌드를 제공하지만 개발자가 작성한 코드가 아니기 때문에 babel이나 webpack으로 변환된 코드를 보게 된다.
    • eval-cheap-module-source-map : 빠른 리빌드와 함께 소스맵을 제공하지만 정확한 글자에 대한 매핑은 불가능
    • eval-source-map : 앞의 두 소스맵에 비해 리빌드가 약간 느려지지만 더 성능 좋은 소스맵을 제공한다.
  • Production

    • none : 매우 빠른 리빌드와 매우 빠른 빌드를 제공하고 소스맵을 제공하지 않으므로 디버깅이 불가능하다.
    • source-map : 성능 좋은 소스맵을 Production 환경에서 사용하려면 이용한다.
    • inline으로 생성되는 eval과 inline같은 소스맵은 번들 사이즈가 커져서 프로덕션 빌드에는 적합하지 않다.

Code Splitting

  • 자바스크립트로 애플리케이션을 개발하게 되면 기본적으로 하나의 파일에 모든 로직이 들어가게 된다.
  • 따라서, 프로젝트의 규모가 커질수록 자바스크립트 파일 용량도 커지는데 용량이 커지면 로딩 속도가 저하된다.
  • 코드 스플리팅은 지금 당장 필요한 코드가 아니라면 따로 분리시켜 필요할 때 불러와 사용한다.

SPA로 구현된 react project에서 코드 스플리팅 하는법

  • React에서는 컴포넌트들을 동적으로 불러오기 위해서
    React.lazy를 사용하는데 이를 위해선 React.Suspense를 함께 이용해야 한다.
  • 예시 코드를 보면 Suspense가 Routes를 감싼 걸 볼 수 있는데 이렇게 감싼 상태이면 컴포넌트를 동적으로 불러올 때 로딩 상태를 React.Suspense가 처리하게 된다.
const Home = lazy(() => import("@/pages/Home"));

//router Code
<Suspense fallback={<div>로딩중..</div>}/>
	<Routes>
		<Routes path="|" element={<Home />} />
  		...
	</Routes>
</Suspense>
  • 다음은 React.lazy를 사용해서 코드 스플리팅한 번들 결과물로 큰 하나의 파일을 여러 개의 작은 모듈로 나눠진 걸 볼수 있다.

  • 여기서 추가적으로 최적화할 수 있는 옵션으로 중복된 모듈을 처리하는 optimization.splitChunks 옵션이다.
  • splitChunks옵션을 주면 중복된 모듈을 공통 모듈로 분리한다.
  • default 설정으로 chunks가 node_modules에 존재하고 20kb보다 크고 로드할 때 최대 병렬의 요청 수가 30개 이하여야지만 splitChunks를 진행한다.

Tree Shaking

  • 일반적으로 사용하지 않는 코드를 번들링 결과에 포함하지 않는 일이다.
  • 함수 단위가 아닌 모듈 단위에서 필요하지 않거나 사용하지 않는 코드를 번들링 결과에 포함하지 않는 것을 의미한다.
  • 이런 경우 tree shaking 이루어지면 c는 번들 결과에 포함되지 않게 된다.

💡 안 쓰는 코드는 그냥 지워버리면 되는거 아닌가요?

우리가 작성한 코드라면 폴더에서 파일을 삭제하면 되지만

  • node처럼 서로간의 의존성이 높고
  • 대규모 프로젝트라 코드를 지웠을 때 서로에게 어떤 영향이 있을지 모를 때는 함부로 코드를 지우는 것은 지양해야 한다.
  1. Library
    라이브러리를 만드는 경우에는 tree shaking 개념을 알고 있어야 소비자들의 자원을 낭비하지 않게 만들 수 있습니다.
    또 소비자들은 webpack을 쓸지 rollup을 쓸지, 또 사용하더라도 webpack의 최신 버전을 안쓸지도 모르는 일이기 때문입니다
  2. CommonJS
    두 번째로는 CommonJs 방식으로 개발된 lodash같은 라이브러리를 사용할 때 더 조심할 수 있습니다
    tree shaking 개념을 모르고 이런 옛날에 만들어진 라이브러리를 사용한다면 사용하지 않는 코드들도 다 같이 불러와질 수 있기 때문입니다

그래서 tree shaking은 어떻게 하는데..?

  • tree shaking은 그냥 production mode를 켜면 알아서 잘 이루어진다.

persistent cache

  • webpack 5에서 persistent cache라는 기능이 추가 되었는데 말 그대로 하드디스크에 빌드된 결과물을 저장해 놓는다는 것입니다
  • 그리고 다시 re-build가 일어났을 때 파일이 변경되지 않았다면 하드디스크에 저장돼 있는 캐시 데이터를 재활용하고 만약에 변경이 되었다면 하드디스크에 있는 캐시 데이터를 무효화하는 일을 한다.

persistent cache 적용법

moduel.exports = merge(common, {
  mode: "production"
  devtools: "source-map",
  plugins: [],
  optimization: {},
  cache: {
    type: "filesystem",
      buildDependencies: {
        config: [__filename],
    },
  },
}),
  • cache 옵션에 type을 "filesystem"으로 지정해주면 하드디스크를 사용한다는 의미이다.

💡 근데 이 좋은걸 왜 default로 안 씀?

캐시의 무효화 정책이 상황에 따라 다르게 적용돼야 하기 때문이다.

profile
ChatGPT-Driven Development를 지양합니다.

0개의 댓글