개발 환경 설정 - pozafly/surviving-developer GitHub Wiki

개발 환경 설정은 크게 2가지로 나뉩니다. Webpack 설정과 Babel 설정입니다.
이는 이슈 #5에 따른 설정입니다. feature/2-webpack-babel-setting브랜치에서 작업 되었습니다.

요약

  • Entry, Output 경로 설정

  • 모듈 Loader 설정

    • style-sheet
    • assets
    • babel
  • Plugin 설정

    • HTML 파일 template 설정
    • CSS 파일 외부 생성 설정
    • 배포를 위한 난독화 및 압축 플러그인 설정
  • Source Map 설정

  • Optimization 설정

  • Webpack 설정 파일 모드 별 분리

    • .env 파일 모드 별 분리
  • Babel IE 11 기준 설정

  • postcss의 autoprefixer 설정


webpack 설정

Mode

  • webpack.dev.js : 개발 환경에서 사용할 config 파일
  • webpack.prod.js : 배포 환경에서 사용할 config 파일
  • webpack.common.js : 개발, 배포 환경에서 공통으로 사용할 config 파일
  • webpack.config.js : common 파일과 환경별 파일을 조합해 최종 설정 파일 추출
// webpack.config.js
const { merge } = require('webpack-merge');

const commonConfig = require('./webpack.common.js');
const productionConfig = require('./webpack.prod.js');
const developmentConfig = require('./webpack.dev.js');

module.exports = (env, args) => {
  switch (args.mode) {
    case 'development':
      return merge(commonConfig, developmentConfig);
    case 'production':
      return merge(commonConfig, productionConfig);
    default:
      throw new Error('No matching configuration was found!');
  }
};

webpack-merge 패키지를 사용해 환경 별로 파일을 나누어 진행했습니다.

// package.json
"scripts": {
  "build": "cross-env webpack --progress --mode production",
  "dev": "cross-env webpack serve --open --progress --mode development",
}
  • cross-env를 사용해 OS에 관계없이 명령어를 실행할 수 있습니다.
  • package.json에서 --mode 옵션을 통해 배포 모드와 개발 모드를 구분할 수 있습니다. 이는 process.env.NODE_ENV 를 사용하기 위함입니다.

Entry, Output

// webpack.config.js
module.exports = {
  (...)
  entry: {
    main: './src/index.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name]_[chunkhash:8].js',
    clean: true,
  },
}

input

  • entry : JavaScript 파일 진입점입니다. 이곳의 main은 ouput의 [name]과 매칭됩니다. 여러 개의 entry가 올 수 있습니다.

output

  • path : build될 결과물이 나올 디렉토리 경로와 이름을 지정해주었습니다.
  • filename : JavaScript 청크 파일의 이름입니다.
    • [name] : entry 파일 이름입니다.
    • [chunkhash:8] : entry 파일 기준으로 연관된 JavaScript 파일이 변경되었을 경우만 hash를 변경합니다. 8자로 제한하였습니다.
  • clean : build시 생성될 폴더를 제거하고 새로운 빌드 폴더를 생성해줍니다. clean-webpack-plugin을 의존하고 있습니다.

Loader

loader는 모듈(images, fonts, css, ...)을 JavaScript 파일 내부로 가져올 수 있도록 합니다. 이는 JavaScript를 통해 모듈을 가공하거나 필요한 처리를 하기 위해서입니다.

module: {
  (...)
  rules: [
    {
      test: /\.(sa|sc|c)ss$/,
      use: [
        {
          loader: 'style-loader',
          options: { injectType: 'singletonStyleTag' },
        }
        'css-loader',
        'postcss-loader',
        'sass-loader',
      ],
    },
    {
      test: /\.(png|svg|jpe?g|gif|webp)$/i,
      // 기본적으로 8kb 이하라면 url-loader로, 이상이면 file-loader로 동작
      type: 'asset',
      generator: {
        filename: 'assets/images/[name]_[contenthash:8][ext]',
      },
    },
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          cacheDirectory: true,
        },
      },
    },
  ],
},

스타일 시트 관련 로더

  • style-loader : html의 head 태그 내부에 <style> 태그로 css 파일을 주입합니다. (빠르게 로드하므로 개발모드에서만 사용)
    • 파일 별 여러 개의 style 태그를 넣기보다 하나의 style 태그에 넣도록 options를 조정해주었습니다.
  • MiniCssExtractPlugin : css 파일을 외부 시트 파일로 만들어줍니다. 여러 entry가 생겼을 경우를 대비하였습니다. (배포 모드에서만 사용)
  • css-loader : JavaScript 파일 내부의 import문으로 css 파일을 불러옵니다.
  • postcss-loader : css파일을 후처리합니다. autoprefixer를 사용하기 위해 load 했습니다.
  • sass-loader : sass 또는 scss 파일을 JavaScript 파일로 불러오며, css 파일로 전처리 합니다.

※ loader의 use 배열 뒤에 정의된 loader부터 적용 됩니다.

assets 관련 로더

  • webpack5 부터는 url-loader와 file-loader를 사용하지 않습니다.
  • webpack 자체에서 지원하기 때문입니다.
  • type: 'asset' 을 명시해주면, 기본적으로 8kb 이하라면 url-loader로, 이상이면 file-loader로 동작합니다. 참고1, 참고2
    • url-loader : 이미지를 base64로 인코딩하여 img 태그의 src 속성에 넣어줌.
    • file-loader : 파일 자체의 형태로 나타나며 output의 assetModuleFilename 경로에 hash와 함께 저장됨.

babel 관련 로더

  • babel-loader : babel을 설정하기 위한 로더입니다. JavaScript 파일을 브라우저 상황에 맞게 트랜스파일링 합니다.
  • options-cacheDirectory를 통해 node_modules/.cache/babel-loader 디렉토리에 캐시되도록 설정했습니다.

Plugin

module: {
  (...)
  plugins: [
    new HtmlWebpackPlugin({
      template: 'index.html',
    }),
    new MiniCssExtractPlugin({
      filename: '[name]_[contenthash:8].css',
    }),
    new Dotenv({ path: './env/.env.production' }),
  ]
},
  • HtmlWebpackPlugin : output에 번들링 된 모듈이 링크된 HTML 파일을 생성해줍니다. template에 지정된 html 기준으로 생성해주도록 설정했습니다.
  • MiniCssExtractPlugin : 배포 모드에서 사용됩니다. MiniCssExtractPlugin Loader를 사용하기 위해 plugin도 적어주었습니다.
    • [name]_[contenthash:8].css : contenthash는 변경된 파일의 hash 만 변경시켜준다는 의미입니다.

Source Map

module: {
  (...)
  devtool: devMode ? 'inline-source-map' : 'source-map', // 디버깅을 위한 소스맵
},
  • inline-source-map : .map 파일을 생성하지 않고, 소스맵을 DataUrl로 번들에 추가합니다. 웹팩추천 소스맵입니다. 개발 모드에만 사용합니다.
  • source-map : .map 파일이 생성되며 생략없는 모든 소스맵이 포함됩니다. 배포 모드에만 사용합니다.
  • 참고

Optimization

module: {
  (...)
  optimization: {
    minimizer: [new CssMinimizerPlugin(), new TerserPlugin()],
    runtimeChunk: 'single',
  },
},
  • CssMinimizerPlugin : css를 한줄로 압축합니다. MiniCssExtractPlugin와 함께 사용해야 합니다.
  • TerserPlugin : 난독화(mangle) 및 소스코드 압축(compressor) 플러그인 입니다.
  • runtimeChunk : 모듈 변들링 할 때 생성되는 코드를 실행하는데 필요한 코드입니다. single은 현재 JavaScript 파일의 chunk가 1개이므로 runtime을 공유할 일이 없으므로 single로 주었습니다.


Babel 설정

Babel-loader 설정

기존에는 babel polyfill(@babel/polyfill)은 deprecated 되었습니다. 2가지 문제점이 있습니다.

  • core-js/es6를 전역에서 import하기 때문에 사용하지 않는 polyfill도 번들 포함
  • @babel/polyfill은 딱 한번만 import 해야 한다. 두 개 이상이 import 되면 오류 발생

전역 스코프를 오염시키는 문제가 있기 때문에 아래 두개의 패키지를 사용합니다.

  • core-js -> ES6+ 코드를 ES5 코드로 변환하는 역할
  • regenerator-runtime/runtime -> async/await를 포함한 제너레이터 함수를 폴리필

preset-env는 ECMAScript2015+를 변환할 때 사용합니다.

.babelrc 파일 설정

바벨 설정 파일입니다. 참고 - 카카오 babel 설정

// .babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": "ie 11",
        "useBuiltIns": "usage", // 폴리필 사용 방식 지정
        "corejs": {
          "version": 3 // 폴리필 버전 지정
        }
      }
    ]
  ]
}

※ useBuiltIns : 어떤 방식으로 폴리필을 사용할지 설정하는 옵션. [참고](https://so-so.dev/web/you-dont-know-polyfill/)

  • false : 폴리필을 생성하지 않는다.
  • entry : transpile 하는 시작점에 import된 core-js, regenerator-funtime 모듈을 babelrc에서 지정한 target에 맞게 변경한다.
    • target이 구형 브라우저일 경우 과도한 polyfill이 추가되어 최신 브라우저에서도 큰 bundle이 생성된다.
  • usage : 실제 코드에서 사용하는 polyfill만 import 하는 설정이다.
    • 사용하는 code만 polyfill 대상으로 보기 때문에 node_modules의 dependency에서 polyfill이 적용되지 않은 코드가 있다면 에러가 발생할 수 있다.

※ 프로젝트에 사용한 방법은 usage, core-js 3

  • 이번 프로젝트에서 종속성 설치가 지양되므로 usage로 설정했습니다.
  • core-js는 ECMAScript 새로운 feature가 더 많이 지원되므로 3으로 설정했습니다.

만약 node_modules 안에 있는 패키지에서 usage를 사용할 때 오류가 난다면, babel-loader 설정에서 exclude 해주었던 설정을 아래와 같이 조금만 바꿔주면 됩니다.

//webpack.config.js
module.exports = {
  ...,
  module: {
    rules: [{
        test: /\.(js)$/,
        exclude: /node_modules\/(?!(||))/, // here
        use: {
          loader: 'babel-loader'          
        },
      }]}}

.babelrc 생성이유

babel.config.js 는 공식 문서에 따르면 구성하는 api가 노출됩니다. 이런 방식은 캐싱과 관련 복잡성을 증가시키므로, .babelrc, 보다는 좋지않다고 합니다. (.babelrc, babel.config.json은 같음) 또한 .babelrc를 루트 디렉토리에 두면 더 확장성 있는 설정이 가능합니다. Storybook은 빌드시 루트 디렉토리의 babelrc를 찾아 적용할 수 있기 때문입니다. 링크1, 링크2

단, 웹팩 모드 별로 다르게 주고 싶다면 webpack의 babel-loader에 .babelrc 파일 내용을 직접 설정해주면 됩니다.


추가 설정

postcss 설정

postcss는 autoprefixer를 사용하기 위해 설정했습니다. postcss.config.js 파일을 추가하여 설정해줄 수 있지만, 내용이 많지 않아 webpack.config.js의 postcss-loader에 설정하였습니다. postcssPresetEnv는 autoprefixer를 사용하기 위한 플러그인.

{
  loader: 'postcss-loader',
  options: {
    postcssOptions: {
      // autoprefix를 사용하기 위한 플러그인
      plugins: [postcssPresetEnv()],
    },
  },
},

autoprefixer를 설정하기 위해서는 browserslist 설정을 추가로 해주어야 합니다. 이는 브라우저의 어느 수준까지 설정이 될 것인지를 설정하기 위함입니다. package.json 파일에 추가로 설정해주었습니다.

// package.json
"browserslist": {
  "production": [
    "IE 10"
  ],
  "development": [
    "last 1 chrome version",
    "last 1 firefox version"
  ]
}

※ babel 설정 또한, browserslist를 참고 하는데요, .babelrc 파일에서 @babel/preset-env이 있다면 .babelrc 파일이 우선권을 가져갑니다. 링크

⚠️ **GitHub.com Fallback** ⚠️