WEB PACK 스터디 2주차

JongO·2022년 4월 4일
1

Webpack

목록 보기
1/4
post-thumbnail

📌 주제

직접 Webpack의 속성들을 이용하여 개발환경 세팅해보기

2주차에는 1주차에 접했던 웹팩의 주요속성들을 이용하여,직접 프론트엔드 개발환경을 설정해보는 것이 주제였다. 따라서, 세팅을 하기위해 필요한 웹 어플리케이션을 만들어야 했고, 보통 SPA 프레임워크나/라이브러리를 많이 쓰는 추세이기 때문에, 어떻게 SPA가 동작되는지 원리를 간단하게 알고자 유튜브에서 vanilla JS로 SPA를 구현하는 튜토리얼을 진행해서 직접 웹팩 설정을 해보았다.

유튜브 링크

해당 튜토리얼에서는 모듈을 불러오기 위해 ES6의 script 태그에 module 타입을 지정하는 방식을 사용하였지만, 나는 웹팩을 통해 모듈 번들링을 진행 할 것이기 때문에, 해당 방법은 사용하지 않았다.

 <script type="module" src="..."></script>

프로젝트 구조

src
│  index.html
│  index.js
│  
├─route
│      index.js
│      
├─static
│  ├─assets
│  │      favicon.ico
│  │      img.jpeg
│  │      
│  └─style
│          index.scss
│          test.css
│          
└─views
        AbstractView.js
        Dashboard.js
        Posts.js
        Settings.js

간단하게 구조에 대해서 간단히 설명해 보자면, 번들링한 파일들을 주입시킬 index.html, entry point로 사용할 index.js 파일이 있고, 그 아래로는 라우팅, 이미지, CSS, 컴포넌트 파일들이 존재한다.

📌 웹팩 설정하기

개발을 하다보면, 개발진행 상황과 디버깅을 하기위해 development 단계가 필요하고, 배포를 하기위해, 결과물을 최적화 시키기 위한 production 단계가 필요하다. 이것은 웹팩에서 mode로 설정을 나눌 수 있기 때문에, package.json에 scripts 명령어에 환경변수 옵션을 줘서 나눌 수 있다.

// package.json 
  "scripts": {
    "build": "NODE_ENV=production webpack ",
    "dev": "webpack serve"
  },

이런 방식 말고 webpack-merge 라이브러리를 이용하여, 공통 설정을 만들고 각기 다른 설정파일에 합쳐준 다음 --config 옵션을 사용하여 나누는 설정법도 있다.

// package.json 
 "scripts": {
    "dev": "webpack serve --open --config webpack.dev.js",
    "prod": "webpack --config webpack.prod.js",
  }

프로젝트가 복잡해지면 두번째 방식이 좀더 관리가 편해지겠지만, 이번에는 글에서 많이 소개되는 첫번째 방법으로 설명을 진행하려고 한다.

해당 항목에 맞지 않는 설정은 주석처리 하므로, 주석처리 안된 부분을 위주로 보면 된다.

📖 공통 설정

웹팩에서 development 모드와, production 모드에 공통으로 쓰이는 설정에 대해서 먼저 알아보자.

entry & output

entry는 진입점이 될 경로를 정해주는 속성이다. 웹팩은 진입점에 import되어있는 모듈들을 따라가며, 의존성 그래프를 그리면서, 모듈들을 번들링 해준다.
output은 번들링 된 결과물이 나오게 되는 경로와 이름을 설정할 수 있게 해주는 속성이다.
path 옵션을 통해 해당 프로젝트 경로에서 build라는 폴더에 결과물을 만들어주고 filename 옵션을 통해 원하는 파일 이름으로 생성할 수 있다.

resolve

extensions안에 확장자를 입력하면, 해당 확장자는 import 할때, 확장자 이름을 적어주지 않아도 된다.

ex) import ... from "abc.js" ==> import ... from "abc"

alias 옵션은 특정 경로를 별칭으로 정하는 것이다. 보통 프로젝트가 복잡해지면 상대경로를 통해 부모까지 올라가야하는 가독성이 매우 떨어 질 수 있다 생각하여, alias 옵션을 통해 경로를 정해주면 절대경로를 사용할 수 있다.

ex) import ... from "../../../../abc.js" ==> import ... from "@/abc.js"

module


module 속성에는 rules를 통해 파일 확장자에 따른 처리 방법을 설정해 줄 수 있다.
보통 javascript는 es6의 문법을 지원하지 않는 브라우저를 위해 낮은 버전으로 변환해주기 위해 babel을 사용하여 처리하는데 babel을 불러와서 처리할 수 있도록 babel-loader를 설정하였다.
또한, 설치한 패키지들을 제외 시키는 것을 권장한다는 글을 보아서 node_modules폴더를 제외시켰다.

바벨을 통해 변환하는 과정은 제일 마지막에 소개하고자 한다.

이미지같은 정적 파일들을 webpack 4에서는 file-loader나 url-loader를 사용하여 처리하였지만, webpack 5에서 해당 loader를 사용하지 않아도 정적 자원들을 사용 할 수 있게 되었다.

css 파일은 style-loader, css-loader를 사용해야 하고, sass/scss는 sass-loader를 추가로 사용해야한다. 오른쪽에서 왼쪽으로 변환이 된다는 점 주의하자.
다만, 여기서 개발모드와 배포모드를 나눠 놨는데, 이는 나중에 설명을 하겠다.

plugins

플러그인들을 각 mode에 따라서 설정을 달리 할 수 있지만, html-webpack-plugin은 공통적으로 많이 쓰인다.

번들링을 진행하면 index.html 파일에 번들링한 결과물을 자동으로 주입해주고 부가적인 옵션을 통해 다른 것도 넣어 줄 수 있다. 본인은 favicon을 주입시켰다.

여기까지가 자주 쓰이는 공통설정이라고 생각한다. 필요한 것은 그 때마다 찾아서 써야할 듯?

📖 개발(development) 모드 설정

mode & devtool

mode 설정에는 development 모드와 production 모드를 설정할 수 있다. 각 mode 설정에 따라서 웹팩에서는 기본적으로 해당 mode에 맞는 설정들을 해준다. 예를 들어 development 모드에서는 디버깅을 하기 쉽게 컴파일 된 코드를 원래 코드로 보여주게 하기도 하고, production 모드에서는 코드를 알아 볼 수 없도록 난독화를 진행해주면서, 번들링 된 결과물을 압축시켜서 최적화 해준다. 또한 여러가지 devtool들을 사용할 수 있는데, 본인은 development 모드에서 기본값이 아닌 inline-source-map을 설정하였다.

css | scss | sass


스타일링에 필요한 css파일들을 불러올 때, development 모드와 production 모드를 나눠서 설정을 해주었다.

plugins

development 모드에서는 html-webpack-plugin만 사용하도록 설정하였다.

devServer


CRA를 통해 프로젝트를 진행하다보면 npm start 명령어를 통해 코드를 실행시켜 본 적이 있을 것이다. 그것이 webpack-dev-server이며 webpack 5에서는 devServer 속성을 통해 설정하고 webpack serve 명령어를 통해 실행 시킬 수 있다.

  • client 옵션에 logging은 터미널에 실행, 에러, 경고 문구들의 수준을 정하는 옵션이다. 본인은 vervose를 설정하였는데, 이 옵션은 엄청 세세하게 정보들을 출력해준다.
  • overlay 옵션은 코드 에러가 생겼을 때, 브라우저에도 해당 문구를 출력해주는 옵션이다. 이것을 설정 함으로써, 더 빠르게 문제를 확인할 수 있다.
  • static 은 정적파일을 제공할 경로를 설정해준다.
  • host는 domain이나 localhost를 정해줄 수 있다
  • port옵션에서 포트를 정해줄 수 있다.
  • open 옵션은 dev-server를 실행시키면 자동으로 기본 브라우저를 열어줘서 실행시키는 옵션이다.
  • proxy 옵션을 통해 개발 단계에서, api에 cors문제 없이 요청을 보낼 수 있다.
//index.js
fetch('/api/test')
  .then((res) => res.json())
  .then((data) => console.log(data));
//server.js
app.get('/api/test', (req, res) => {
  console.log('요청옴');
  res.status(200).json('proxy success!');
});

📖 배포(production) 모드 설정

mode & devtool


production 모드에서는 mode만 production으로 설정해주고 devtool은 따로 설정해주지 않았다.

plugins

production 모드에는 css-minimizer-webpack-plugin, clean-webpack-plugin, mini-css-extract-plugin 들이 추가되었다.

  • css-minimizer-webpack-plugin은 번들링 된 css의 용량을 압축시켜주는 플러그인이다. 하지만, 해당 프로젝트에서는 매우 적은 스타일링이 필요하므로, css의 용량이 줄어들지는 않았다.

  • mini-css-extract-plugin은 번들링을 할 때, css파일을 따로 분리해주는 플러그인이다. 프로젝트 규모가 매우 커진다면, 번들링되는 js파일은 매우 용량이 커질 수 밖에 없고, 성능의 저하가 우려 될 수 있다. 따라서, css를 분리해주게 되면 더욱 어플리케이션이 가벼워지는 장점이 있으므로 분리 해준다. css를 분리해주는 것 또한 코드 스플리팅이라 볼 수 있다.

  • clean-webpack-plugin은 매번 번들링 할 때마다 기존의 번들링 된 파일들을 없애주는 역할을 한다. 이를 통해 결과물을 더 잘 관리 할 수 있게 된다.

css | scss | sass


production 모드에서 css를 따로 분리 했다면, mini-css-extract-plugin의 loader를 사용해주어야 한다고 한다. 특히, style-loader와 mini-css-extract-plugin의 loader는 같이 쓰이면 안된다고 하니 주의하자.

optimization

build를 할 때, 이 속성을 통해 최적화를 할 수 있는데, 여러가지 속성이 있지만, 이번 과정에서는 minimizer를 통해 이미지 용량을 줄여보고 , 코드를 난독화하는 플러그인에 대해서만 설정해보았다.

image-minimizer-webpack-plugin

  • image-minimizer-webpack-plugin을 통해 이미지들의 요량을 줄일 수 있는데, 본인이 설정한 설정은 손실방지 설정이다. 공식 홈페이지에는 손실 설정까지 소개되어 있다.

참고로 jpeg를 쓴다면,
ImageMinimizerPlugin.imageminMinify -> ImageMinimizerPlugin.imageminGenerate 로 변경해주어야 한다.

플러그인 적용 전

플러그인 적용 후

terser-webpack-plugin

  • terser-webpack-plugin은 자바스크립트 코드를 난독화하고 debugger 구문을 없애준다고 한다.

📌 Babel 설정

webpack을 설정 해줄 때, js파일들은 babel-loader를 통하여 최신 문법을 예전 문법으로 변환하도록 설정하였다. 하지만, babel-loader만 설정해줘서는 변환이 이루어지지 않는다. 그에 따른 부가적인 babel 설정들이 필요하다.
최신 자바스크립트 문법을 만나게 되면, 해당 문법을 예전 것으로 변환해주어야 하는데 이때 필요한 것이 babel의 plugin이다. 여러가지 플러그인들이 존재하지만, babel 공식 홈페이지에서는 @babel/preset-env 라는 preset을 통해 여러 플러그인들을 한번에 사용할 수 있도록 사용 권장한다.

//babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage', // 폴리필 사용 방식 지정 기본값은 false 이다
        targets: '> 0.25%, not dead', //브라우저 점유율을 나타냄, 해당 비율을 넘는 사용률을 가진 브라우저에서 작용
        corejs: {
          // 폴리필 버전 지정 기본값은 2
          version: 3,
        },
      },
    ],
  ],

이렇게 설정하면 internet explorer11 같은 옛날 브라우저에서도 제대로 자바스크립트가 동작이 될까?

📖 Babel 설정시 마주친 문제들

@babel/preset-env

제대로 동작이 되지 않는 걸 알 수 있으며, 해당 번들링 파일 안에는 아래와 같이 화살표 함수가 그대로 있는 것을 알 수 있다.

왜 이같은 현상이 나타나며 어떻게 해결할 수 있을까?

이유와 해결방안

이유: babel-loader는 번들링이 되기 전에 각 파일단위로 es6 -> es5 변환 시키고, 그 후에, 완료가 되면 웹팩이 번들링을 시작한다고 한다. 하지만 webpack 5부터는 번들링이 될 때, 기본적으로 es6로 번들링이 되기 때문에, 다시 es6 문법들로 변환 되는 것이다.

1. 초기상태
file1 (es6)
file2 (es6)
file3 (es6)
2. babel-loader 변환
file1' (es5)
file2' (es5)
file3' (es5)
3. 빌드 후
bundled file (es6)

따라서 이를 해결하려면, webpack 설정 파일에 target을 설정해줘야 한다.

target: ['web', 'es5'],

해당 설정을 하고 나서 실행 internet-explorer 11을 실행시키면

제대로 화살표 함수가 변환이 된 것을 알 수 있다.

지금은 웹팩설정에 대한 내용이므로 CSS 스타일링이 깨진거는 넘어가자...

@babel/polyfill

위에처럼 설정을 해도, 아직 완전히 다 해결 된 것이 아니다. Promise는 preset으로 변환 하지 못한다.

바벨은 ECMAScript2015+를 ECMAScript5 버전으로 변환할 수 있는 것만 빌드한다. 그렇지 못한 것들은 "폴리필"이라고 부르는 코드조각을 추가해서 해결한다.

따라서, polyfill을 추가하려 하였으나, @babel/polyfill은 전역공간의 오염이라는 단점 때문에 deprecated 되었다고 한다.

@babel/plugin-transform-runtime

@babel/polyfill의 전역공간 오염을 막기 위해 해당 플러그인을 사용해야 한다고 공식문서에 적혀있다.

//babel.config.js
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        useBuiltIns: 'usage', // 폴리필 사용 방식 지정 기본값은 false 이다
        targets: '> 0.25%, not dead', //브라우저 점유율을 나타냄, 해당 비율을 넘는 사용률을 가진 브라우저에서 작용
        corejs: {
          // 폴리필 버전 지정 기본값은 2
          version: 3,
        },
      },
    ],
  ],

  plugins: [['@babel/plugin-transform-runtime', { corejs: 3 }]], // 플러그인들을 따로 추가할 수 있음, preset은 플러그인들을 모아 놓은 것
};

그 외 발견한 internet explorer 11 호환성 문제

  • event.target.matches
  • fetch API

event.target.matches와 fetch API는 babel로 타겟 브라우저에 맞게 변환해주지 않는다. 그 이유는 babel은 ECMA SPEC에 정해진 것들만 변환을 해주며, 위의 것들은 ECMA SPEC에 정의 된 것이 아니다. 따라서 변환해 주지 않기 때문에, 따로 처리를 해주어야 한다.

  • event.target.matches는 아래와 같이 msMatchesSeletor를 이용해서 조건에 따라 처리하는 방식으로 해결할 수 있다.
let matches = e.target.matches;
    matches ? e.target.matches('[data-link]') : e.target.msMatchesSelector('[data-link]')
  • fetch API는 @babel/polyfill을 통해 whatwg-fetch 를 사용하면 해결할 수 있다는 글이 많지만 @babel/polyfill은 deprecated 되었기 때문에 어떻게 해결해야 하는지 열심히 조사해보았지만 찾을 수 없었다. 앞으로도 궁금점으로 남을 듯...

📌 결론

스스로 웹팩 설정을 해보고 크로스 브라우징 까지 고려해보니까, 알면 알수록 더 배울게 많아지는 것 같다. 개인적으로 NEXT JS 없이 웹팩만으로 SSR 설정도 해보는게 목표이기 때문에, 배울것이 더 많지만, 계속 해나간다면 좋은 자산이 될 것이라 생각한다. 또한, 이번 계기로 크로스 브라우징에 대해서 어느정도 알게 된 것 같다. 3주차에는 React환경에 개발환경 세팅을 해보면서 caching과 code-splitting을 위주로 웹팩 설정을 공부하려고 한다.

References

https://jeonghwan-kim.github.io/series/2019/12/22/frontend-dev-env-babel.html#5-env-%ED%94%84%EB%A6%AC%EC%85%8B-%EC%84%A4%EC%A0%95%EA%B3%BC-%ED%8F%B4%EB%A6%AC%ED%95%84
https://babeljs.io/docs/en/babel-plugin-transform-runtime
https://webpack.js.org/configuration/target/#string-1
https://stackoverflow.com/questions/54039337/how-to-remove-arrow-functions-from-webpack-output
https://www.youtube.com/watch?v=6BozpmSjk-Y

0개의 댓글

관련 채용 정보