Skip to content

jaehyeonjung0613/learning-webpack

Repository files navigation

🎉 learning-webpack

웹팩 가이드라인 따라해보면서 학습해보기

💡 필요성 및 목적

개발 역량을 쌓기 위해 우아한테크코스 미션에 도전하기 시작하였다.

첫 미션으로 perf-basecamp 문제풀이를 하였는데, react와 webpack을 활용하여 웹 성능 최적화하는 문제로 보였다.

react에 대해 어느 정도 알고 있었지만 webpack은 생소하여 부족한 webpack 개념을 채우고 사용법을 알아가고자 프로젝트를 생성하게되었다.

학습 방법은 웹팩 가이드라인을 직접 따라해볼 것이며, 가이드라인 외에도 학습하면서 실험해보고 싶은 것이 있으면 할 예정이다.

🚀 학습 과정

Getting Started

  • Basic Setup
  • Creating a Bundle
  • Modules
  • Using a Configuration
  • NPM Scripts
  • Conclusion

Asset Management

  • Setup
  • Loading CSS
  • Loading Images
  • Loading Fonts
  • Loading Data
  • Global Assets

Output Management

  • Preparation
  • Setting up HtmlWebpackPlugin
  • Cleaning up the /dist folder
  • The Manifest
  • Conclusion

Development

  • Using source maps
  • Choosing a Development Tool
  • Adjusting Your Text Editor
  • Conclusion

Code Splitting

  • Entry Points
  • Prevent Duplication
  • Dynamic Imports
  • Prefetching/Preloading modules
  • Bundle Analysis

Caching

  • Output Filenames
  • Extracting Boilerplate
  • Module Identifiers

Autoring Libraries

  • Authoring a Library
  • Webpack Configuration
  • Expose the Library
  • Externalize Lodash
  • Final Steps

Environment Variables

Hot Module Replacement

  • Enabling HRM
  • Via the Node.js API
  • Gotchas
  • HWR with Stylesheets
  • Other Code and Frameworks

Tree Shaking

  • Add a Utility
  • Mark the file as side-effect-free
  • Clarifying tree shaking and sideEffects
  • Mark a function call as side-effect-tree
  • Minify the Output

Production

  • Setup
  • NPM Scripts
  • Specify the Mode
  • Minification
  • Source Mapping
  • CLI Alternatives

Lazy Loading

  • Example
  • Frameworks

Shimming

  • Shimming Globals
  • Loading Polyfills

Web Workers

  • Syntax
  • Example
  • Node.js

Progressive Web Application

  • We Don't Work Offline Now
  • Adding Workbox
  • Registering Our Service Worker

🧪 실험

특정 파일들 한 폴더에 bundle

에셋과 같은 파일과 소스 파일을 분리하여 bundle 할 수 있는지 확인해본다.

소스 구조와 bundle된 구조가 동일한지 확인

소스에 에셋 폴더를 생성하고 bundle한다면, 구조가 유지되는지 확인

    src
+   |- public
+     |- icon.png
-   |- icon.png

먼저 위와 같이 public 폴더 생성 후 에셋 파일들을 이동시킨다.

import _ from 'lodash';
import './style.css';
import Icon from './public/icon.png';
import Data from './data.xml';
import Notes from './data.csv';
import toml from './data.toml';
import yaml from './data.yaml';
import json from './data.json5';

변경된 파일 경로와 동일하게 import 경로를 수정한다.

> learning-webpack@0.0.0 build
> webpack

assets by status 39 KiB [cached] 1 asset
asset bundle.js 75.8 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 2.28 KiB 8 modules
javascript modules 543 KiB
  modules by path ./node_modules/ 539 KiB
    modules by path ./node_modules/style-loader/dist/runtime/*.js 5.84 KiB 6 modules
    modules by path ./node_modules/css-loader/dist/runtime/*.js 2.31 KiB 2 modules
    + 1 module
  modules by path ./src/ 3.38 KiB
    modules by path ./src/*.css 1.78 KiB 2 modules
    + 3 modules
json modules 565 bytes
  ./src/data.toml 188 bytes [built] [code generated]
  ./src/data.yaml 188 bytes [built] [code generated]
  ./src/data.json5 189 bytes [built] [code generated]
./src/public/icon.png 42 bytes (javascript) 39 KiB (asset) [built] [code generated]
...

빌드 수행 후 결과를 확인하였지만, 여전히 에셋과 분리가 안되어있는걸 볼 수 있다.

webpack config 옵션 확인

웹팩 설정 옵션을 찾아보는 중 이와 같은 상황을 위해 Rule.generator 있다고 한다.

실제로 작동하는지 확인보도록 한다.

// webpack.config.js
{
  ...
  module: {
    rules: [
      {
        ...
        generator: {
          filename: '',
          publicPath: '',
          outputPath: ''
        },
      }
    ]
  }
}

Rule.generator에 다양한 옵션이 있지만, 그 중 현재 실험과 관련된 옵션만 다룬다면 위와 같다.

옵션 설명
filename bundle 시 파일 경로
publicPath bundle 소스 앞 경로(prefix 기능과 유사) 추가
outputPath bundle 파일 경로(bundle 소스 path 적용 x)

얼핏 보면 filename과 outputPath의 기능이 동일하다고 생각할 수 있는데, 약간의 차이점이 있다.

filename 설정은 bundle 에셋 이동과 bundle 소스 path 적용까지 하지만, outputPath는 bundle 에셋 이동만 적용한다는 점이 다르다.

// webpack.config.js
{
  ...
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'public/[hash][ext]',
        },
      },
    ]
  }
}

filename 옵션을 통해 public 폴더 밑에 에셋 파일을 생성하려면 위와 같이 설정하면된다.

[hash]는 bundle 후 생성된 파일명을 의미하고 [ext](. 포함)은 확장자를 의미한다.


위와 같이 에셋 이동과 bundle 소스 path가 잘 적용되어 나온 것을 볼 수 있다.

// webpack.config.js
{
  ...
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
        generator: {
          outputPath: 'public/',
        },
      },
    ]
  }
}

outputPath 옵션 설정은 위와 같이 설정하면된다.


반면 outputPath는 에셋 이동이 되었음에도 불구하구 실제 실행하였을때 리소스를 못가져오고있다.

해당 리소스 경로를 보았을때, 에셋 경로인 public/이 아닌 bundle 위치로 조회하는 것을 볼 수 있다.

// webpack.config.js
{
  ...
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'public/[hash][ext]',
        },
      },
    ]
  }
},
{
  ...
  module: {
    rules: [
      {
        test: /\.(png|svg|jpg|jpeg|gif)$/i,
        type: 'asset/resource',
        generator: {
          publicPath: 'public/',
          outputPath: 'public/',
        },
      },
    ]
  }
}

따라서 위 옵션들을 사용하였을때 솔루션을 적용할 수 있는 방법은 위와 두 가지가 있다.

filename은 에셋 이동에 대한 모든 기능이 있으니 그대로 사용하면 되고, outputPath의 경우 에셋 이동 기능만 있으니 bundle 소스 앞 경로에 설정된 url를 적용하는 publicPath를 같이 사용하면 된다.

Typescript Path Alias 설정

typescript에는 import 경로를 줄일 수 있도록 path alias 기능이 존재한다.

보통 @{folder}로 쓰는 경우가 많은데, webpack module loader 설정에서 정규식 대상(test:)으로 path alias를 인식하는지 확인해본다.

// tsconfig.json
{
  ...
  "baseUrl": "./"                                  /* Specify the base directory to resolve non-relative module names. */,
  "paths": {
    "@public/*": ["src/public/*"]
  }                                      /* Specify a set of entries that re-map imports to additional lookup locations. */,
  ...
}
import _ from 'lodash';
import './style.css';
import Icon from '@public/icon.png';
import Data from './data.xml';
import Notes from './data.csv';
import toml from './data.toml';
import yaml from './data.yaml';
import json from './data.json5';

먼저 typescript path alias 사용하기 위해 위와 같이 tsconfig.json과 index.ts 파일을 수정한다.

...
ERROR in ./src/index.ts 3:0-36
Module not found: Error: Can't resolve '@public/icon.png' in 'D:\jaehyeonjung\learning-webpack\src'
resolve '@public/icon.png' in 'D:\jaehyeonjung\learning-webpack\src'
  Parsed request is a module
  using description file: D:\jaehyeonjung\learning-webpack\package.json (relative path: ./src)
    Field 'browser' doesn't contain a valid alias configuration
    resolve as module
      D:\jaehyeonjung\learning-webpack\src\node_modules doesn't exist or is not a directory
      looking for modules in D:\jaehyeonjung\learning-webpack\node_modules
        single file module
          using description file: D:\jaehyeonjung\learning-webpack\package.json (relative path: ./node_modules/@public/icon.png)
            no extension
              Field 'browser' doesn't contain a valid alias configuration
              D:\jaehyeonjung\learning-webpack\node_modules\@public\icon.png doesn't exist
            .js
              Field 'browser' doesn't contain a valid alias configuration
              D:\jaehyeonjung\learning-webpack\node_modules\@public\icon.png.js doesn't exist
            .ts
              Field 'browser' doesn't contain a valid alias configuration
              D:\jaehyeonjung\learning-webpack\node_modules\@public\icon.png.ts doesn't exist
        D:\jaehyeonjung\learning-webpack\node_modules\@public\icon.png doesn't exist
      D:\jaehyeonjung\node_modules doesn't exist or is not a directory
      D:\node_modules doesn't exist or is not a directory

webpack 5.95.0 compiled with 1 error and 1 warning in 9424 ms

이후 빌드를 시도하였지만 실패하는 것을 볼 수 있다.

ts-loader에서 path alias을 인식하지 못해 모듈로 불러왔으나 존재하지않은 모듈로 인해 build가 실패했다는 내용이다.

여러 해결할 수 있는 방법이 있지만 그 중 ts-loader에서 소개하고있는 plugin 설정을 이용할 것이다.

방법은 간단하다. 위에 처럼 tsconfig-paths-webpack-plugin을 설치한 후에 plugins에 추가 설정만 하면된다.

npm i -D tsconfig-paths-webpack-plugin
// webpack.config.js
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

module.exports = {
  ...
  resolve: {
    extensions: ['.js', '.ts'],
    plugins: [new TsconfigPathsPlugin({})],
  },
  ...
};
> learning-webpack@0.0.0 build
> webpack

asset bundle.js 75.8 KiB [emitted] [minimized] (name: main) 1 related asset
asset public/a1af828b4e65d37668e1.png 39 KiB [emitted] [immutable] [from: src/public/icon.png] (auxiliary name: main)
runtime modules 2.28 KiB 8 modules
javascript modules 543 KiB
  modules by path ./node_modules/ 539 KiB
    modules by path ./node_modules/style-loader/dist/runtime/*.js 5.84 KiB 6 modules
    modules by path ./node_modules/css-loader/dist/runtime/*.js 2.31 KiB 2 modules
    + 1 module
  modules by path ./src/ 3.38 KiB
    modules by path ./src/*.css 1.78 KiB 2 modules
    + 3 modules
json modules 565 bytes
  ./src/data.toml 188 bytes [built] [code generated]
  ./src/data.yaml 188 bytes [built] [code generated]
  ./src/data.json5 189 bytes [built] [code generated]
./src/public/icon.png 42 bytes (javascript) 39 KiB (asset) [built] [code generated]

빌드가 성공적으로 완료된 것을 볼 수 있다.

Bundle Analysis analyse

webpack 공식 분석 도구인 analyse 사용법을 확인해본다.

// package.json
{
  "scripts": {
    ...
    "analysis": "webpack --profile --json > stats.json",
    ...
  },
}

먼저 analyse 분석 도구를 사용하기 전, webpack 통계 데이터를 추출한다.

그 다음 analyse 링크에 들어가서 통계 데이터를 업로드하면된다.

메뉴는 Home, Modules, Chunks, Assets, Warnings, Erros, Hints로 구성되어있고, 메뉴마다 모듈 크기 의존도 등을 확인할 수 있다.

Minimize CSS

Production 학습 과정에서 소개하는 CSS Minify 방법을 숙지한다.

pre {
  color: red;
}
import './style.css';
import { cube } from './math';
// webpack.common.js
{
  ...
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

css 파일을 번들링 과정에 추가하기 위해 style.css, index.ts, webpack.common.js 파일을 수정한다.

> learning-webpack@0.0.0 build
> webpack --config webpack.prod.js

asset app.bundle.js 4.67 KiB [emitted] [minimized] (name: app) 1 related asset
asset index.html 219 bytes [compared for emit]
runtime modules 698 bytes 4 modules
orphan modules 1.2 KiB [orphan] 2 modules
cacheable modules 10.8 KiB
  modules by path ./node_modules/ 8.58 KiB
    modules by path ./node_modules/style-loader/dist/runtime/*.js 5.84 KiB
      ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 2.42 KiB [built] [code generated]
      ./node_modules/style-loader/dist/runtime/styleDomAPI.js 1.5 KiB [built] [code generated]
      ./node_modules/style-loader/dist/runtime/insertBySelector.js 1000 bytes [built] [code generated]
      + 3 modules
    modules by path ./node_modules/css-loader/dist/runtime/*.js 2.74 KiB
      ./node_modules/css-loader/dist/runtime/sourceMaps.js 505 bytes [built] [code generated]
      ./node_modules/css-loader/dist/runtime/api.js 2.25 KiB [built] [code generated]
  modules by path ./src/ 2.17 KiB
    ./src/index.ts + 2 modules 1.57 KiB [built] [code generated]
    ./node_modules/css-loader/dist/cjs.js!./src/style.css 610 bytes [built] [code generated]
webpack 5.95.0 compiled successfully in 3368 ms

빌드 결과 css 번들링된 것을 확인할 수 있다.

npm install --save-dev mini-css-extract-plugin css-minimizer-webpack-plugin
// webpack.common.js
...
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  ...
  optimization: {
    minimizer: [`...`, new CssMinimizerPlugin()]
  },
}
// webpack.prod.js
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  ...
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css'
    })
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      }
    ]
  }
}

Css 최적화 관련 패키지를 설치한 다음 webpack common, prod에 플러그인을 추가한다.

MiniCssExtractPlugin은 스타일 코드를 청크에서 파일로 추출하기 때문에 인라인보다 작동이 느리다.

따라서 prod 환경일때만 적용되도록 따로 환경설정을 분리하였다.

> learning-webpack@0.0.0 build
> webpack --config webpack.prod.js

asset index.html 257 bytes [emitted]
asset app.bundle.js 221 bytes [emitted] [minimized] (name: app) 1 related asset
asset app.css 49 bytes [emitted] [minimized] (name: app) 1 related asset
Entrypoint app 270 bytes (1.09 KiB) = app.css 49 bytes app.bundle.js 221 bytes 2 auxiliary assets
orphan modules 3.48 KiB (javascript) 937 bytes (runtime) [orphan] 9 modules
cacheable modules 484 bytes (javascript) 22 bytes (css/mini-extract)
  ./src/index.ts + 1 modules 484 bytes [built] [code generated]
  css ./node_modules/css-loader/dist/cjs.js!./src/style.css 22 bytes [built] [code generated]
webpack 5.95.0 compiled successfully in 5866 ms

빌드 결과 css 파일이 추출되고 전보다 청크 크기가 작아진 것을 볼 수 있다.

📚 기술 스택

🔧 환경

🌏 언어

🧰 개발도구

🎸 기타

About

웹팩 가이드라인 따라해보면서 학습해보기

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published