모듈 시스템과 Webpack(1)

terry yoon·2022년 2월 8일
1
post-thumbnail

글을 작성한 이유

리액트를 공부하고, 리액트 프로젝트를 진행하며 모듈 시스템을 이용해 컴포넌트 주도 개발을 할 때 Webpack과 같은 번들러의 역할이 중요하다는 것을 알게 되었다. 이 글을 통해서 리액트 개발 시 웹팩이 필요한 이유와 웹팩 설정 방법을 정리해본다.

🧐 웹팩 왜 필요해?

😷 모던 웹 환경의 한계

현재 모던 웹 환경은 모듈 시스템 및 컴포넌트 주도 개발에 한계를 가지고 있다. 그 이유는 다음 2가지로 정리할 수 있다.

1️⃣ 브라우저 지원

CanIUse 모듈 시스템
대부분의 모던 브라우저는 모듈 시스템을 지원하지만, IE와 같은 일부 구형 브라우저에서는 모듈 시스템을 지원하지 않고 있다. 물론 모듈을 사용하기 이전에는 즉시 실행 함수를 통해 모듈 패턴을 사용했지만, 파일 단위로 관심사의 분리를 할 수 있고 전역 스코프를 오염시키지 않는 모듈 시스템을 도입하는 것이 개발 경험(DX) 관점에서 훨씬 더 좋기 때문이다.

2️⃣ 앱 빌드 환경

최근 웹/앱의 기능이 복잡해지면서, 컴포넌트 및 페이지가 복잡하기 때문에 반드시 이를 처리할 수 있는 빌드 환경이 제공되어야 한다.

1) 모듈 번들링

번들링은 서로 관련 있는 모듈 파일들을 목적에 따라 하나의 번들링 파일로 묶어 관리하므로, 서버에 여러 개의 파일을 요청할 필요가 없기 때문에 서버 요청 횟수를 줄여주는 장점이 있다.

2) 트리쉐이킹

위의 번들링된 파일의 단점은 모듈의 개수가 늘어나거나, 용량이 커지면 하나로 합쳐진 번들링 파일 역시 크기가 커진다는 단점이 있어 서버 요청 시 부하가 커진다.

따라서 앱을 빌드할 때, 현재 코드 상에서 사용하지 않는 코드를 제거하는 트리쉐이킹을 통해 파일이 과도하게 커지는 것을 방지하여 성능 최적화를 이룰 수 있다

3) 코드 분할 (Code splitting)

사용자가 페이지를 로드할 경우, 모든 페이지를 다운 받을 필요는 없다. 혹은 여러 개의 chunk들이 하나의 모듈을 공유할 경우, 해당 모듈을 모든 chunk point에 번들링 될 필요는 없다.

따라서 코드를 적절히 분할할 경우 사용자가 처음 페이지에 접근할 경우 서버에 필요한 페이지만 요청하고, 이후 다른 페이지에 접근할 경우 그 때 추가 요청을 보내 성능적이 이득을 이룰 수 있다.

4) 코드 최적화, 소스맵

코드를 번들링하면, 번들링 된 파일은 사람이 읽고 이해하기 어렵다.
따라서 반드시, 해당 파일의 소스맵을 통해 해당 코드가 어느 파일에 있는지를 명시해야 한다. 이를 통해 디버깅 및 유지 보수가 유용하기 때문이다.

모듈 시스템을 이용하여 컴포넌트 주도 개발을 하기 위해서, 위의 4가지의 앱 환경이 필요하지만 현재 웹 브라우저 환경에서는 이런 시스템을 지원하지 않는다

그래서 웹팩이 필요하다

👀 웹팩 설정하기

📔 Webpack과 Babel의 관계

Babel의 역할은 트랜스파일링이다. 즉 ES6+ 문법을 하위 호환을 위해 ES5 문법으로 변환하거나, JSX 문법을 React.createElement로 변환시켜주는 역할을 한다.

Webpack은 이렇게 변환된 모듈 파일을 번들링 또는 코드 분할 및 최적화를 하여 build 하거나, devServer를 제공한다.

🙋🏻 Webpack 시작하기

웹팩 시작하기

npm install webpack webpack-cli --save-dev

웹팩을 사용하기 위한 webpack 패키지와 CLI 환경에서 사용하기 위한 webpack-cli를 설치한다.

⚙️ Configuration

웹팩 configuration

현재 프로젝트 폴더 루트에 webpack.config.js를 만들어 보자

const path = require('path');

const webpackConfig = ({ development, production }) => {
  return {
    target: ['web', 'es5'],
    mode: development ? 'development' : 'production',
    cache: true,
    devtool: development ? 'eval-cheap-source-map' : false,
    resolve: {
      extensions: ['.jsx', '.js', '.json', '.wasm'],
    }, 
		{
			...
		}
			...    
};
  • target : 번들링 된 파일이 실행되어야 하는 환경을 명시한다
  • mode : production이 기본값이며, development와 달리 production 모드에서 code-splitting, tree-shaking 을 지원한다
  • devtool : 디버깅이 용이한 개발 도구를 명시함 (웹팩 devtool )
  • resolve - extensions : 모듈을 import 할 경우, 해당 모듈의 확장자를 명시하지 않아도 인식하도록 지원 (웹팩 resolveExtensions)
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ROOT_DIR = process.cwd();

const webpackConfig = ({ development, production }) => {
  return {
    ...
	entry: {
      main: {
        import: path.resolve(ROOT_DIR, 'src/index.jsx'),
        dependOn: 'vender',
      },
      vender: ['react', 'react-dom'],
    },
    output: {
      path: path.resolve(ROOT_DIR, development ? 'dist' : 'public'),
      filename: 'js/[name].js',
      clean : true,
    },
    module: {
      rules: [
        {
          test: /\.jsx?$/i,
          exclude: /node_modules|public/,
          use: 'babel-loader',
        },
      ],
    },
    plugins: [
      new HtmlWebpackPlugin({
        template: path.resolve(ROOT_DIR, 'public/index.html'),
        title: '랜덤 카운트 업 - React Count Up',
      }),
    ],
  };  
};
  • entry : 번들링이 시작될 포인트를 명시함
    • string | [string] | object가 올 수 있음

    • 모듈이 여러 개일 경우, entry 객체 안에 여러 개의 chunk를 명시할 수 있음 (Code splitting)

      Code Splitting | webpack

      💡 주의 - 여러 개의 chunk에서 공통적으로 사용되는 모듈의 경우 chunk마다 번들링 되어 중복이 발생

    • dependOn (중복 제거 방법)

      → 기본적으로 모든 chunk는 번들링 시, 자신이 사용하는 모듈을 포함하여 번들링 되므로 용량이 커짐

      → 하지만 해당 옵션을 사용하면, 하나의 chunk entry를 이용해 다른 chunk에 모듈을 공유할 수 있음

      → 위에서는 번들링된 main.js 가 vender.js의 모듈을 의존하는 상황이므로, 반드시 두 파일을 동시에 html 파일에서 로드해야 함

  • output : 웹팩을 통해서 번들링 된 파일이 위치할 장소를 명시한다
    • 이 때 반드시 절대 경로를 사용해야 하므로, process.cwd 를 사용해야 current working directory를 기준으로 path를 설정한다
    • filename 에 명시된 형태로 번들링 파일 이름이 설정된다.
    • clean : true 옵션을 줄 경우, 번들 시 이전 버전을 지우고 다시 생성
  • module : 웹팩에서 사용할 여러 loader 를 명시한다. 여기서는 babel-loader를 사용한다
  • plugins : 웹팩에서 사용할 plugins를 명시한다. 여기서는 HtmlWebpackPlugin을 사용한다.
    HTMLWebpackPlugin
    - 번들링 된 파일을 html에 자동으로 삽입시켜주는 플러그인

🛠 번들링 및 빌드 하기

// package.json 
"scripts": {
    "start": "npm run dev -- --open", // devServer 실행 (open 옵션)
    "dev": "webpack serve --env development",// devServer 실행
    "bundle": "webpack --env development", // bundling
    "build": "webpack build --env production" // build 
},

💊 babel loader 설치하기

npm install -D babel-loader @babel/core @babel/preset-en
  • 위에서 설명한 것처럼 바벨은 트랜스파일링 작업을 위해서 사용하며, webpack에서 사용할 경우 babel-loader를 설치한다
  • 일반적으로 plugins의 option을 설정할 수 있는데, 루트 폴더 .babelrc 파일을 설정할 수 있다
    {
      "presets": ["@babel/preset-env", "@babel/preset-react"]
    }
    • @babel/preset-env : babel-loader가 변환할 때 사용할 여러 플러그인 모음
    • @babel/preset-react : JSX 문법을 변환하기 위한 플러그인

🖥 dev 서버 설치 및 세팅하기

npm install webpack-dev-server --save-dev
// webpack.config.js 

const webpackConfig = ({ development, production }) => {
  return {
    ...
    devServer: {
      static: ['public'],
      port: 3000,
      compress: true,
      client: {
        logging: 'info',
        overlay: true,
      },
    },
		...
	}
}
  • compress : gzip 압축을 지원하는 옵션이다. 브라우저가 서버에게 자원을 요청할 경우, 서버에서 브라우저에게 압축된 파일을 전달함으로써 빠른 브라우저 로딩이 가능하도록 한다 (How To Optimize Your Site With GZIP Compression)

🔑 devServer 구동하기

웹팩 devServer

// package.json alias 설정하기 
Usage: webpack serve|server|s [entries...] [options]

// options

--env : configuration(webpack.config.js 내 webpackConfig)가 함수일 경우, 인수로 값을 전달할 수 있음 (: --env production => 인수 : { production : true }
--open : devServer 가 구동하면서 브라우저 창을 자동으로 실행 
profile
배운 것을 기록하는 FrontEnd Junior 입니다

0개의 댓글