프론트엔드 개발환경 설정 (webpack)

Moen·2022년 2월 27일
0
post-thumbnail

Webpack을 시작으로 babel, typescript, eslint, prettier, nvm 등의 개발 환경을 순서대로 설정할 계획입니다.

📌 Webpack 개념 정리

Webpack5 를 기준으로 모든 기능을 소개하고 있습니다.

현재 자바스크립트는 광범위하게 사용되고 있는 반면에 파일 단위로 관리할 수 있는 모듈을 지원하지 않았습니다. (ES6 이후에 ESM을 지원을 하지만 현재 지원하지 않는 브라우저도 아직 존재한다.) 그렇다고 여러 개의 파일을 브라우저에 로딩하는 것은 그만큼 네트워크에 무리를 주는 행동이였습니다. 또한 자바스크립트는 파일별로 분리하여 식별자 이름을 작성해도 파일(module) 단위로 스코프를 가지고 있지 않고 공통의 스코프를 가지고 있어서 식별자 이름 충돌이 발생하는 문제가 존재했습니다.(여러가지 문제를 발생시키는 전역 스코프에 식별자가 등록되는 문제가 발생할 수 있습니다.)

이러한 문제를 해결하기 위해서 즉시실행함수(IIFE)를 사용해서 모듈 기능을 대처하려는 노력을 하였지만 하나의 파일에서 모든 작업을 해야하 다른 문제가 존재했습니다. 이러한 문제를 해결하기 위해서 모듈 번들러 관리 라이브러리가 등장하게 되었고 현재 가장 많이 사용하고 있는 모듈 번들러로는 Webpack입니다.

Wepbkac 등장 전에 여러가지 모듈 번들러가 존재했습니다. 현재 트랜드로는 Webpack을 사용하여 많은 프로젝트가 진행되고 있으며, ESM(ES2015)가 새롭게 등장했지만 모든 브라우저에서 지원하지 않으며, Webpack configuration에서만 지원하는 다양한 option들을 사용하여 프로젝트를 진행할 수 있습니다.

🚪 Entry

📚 Entry Point 용어 정의

Entry 속성 객체의 property key를 지칭합니다. ex) entry: {keyNmae: 'filePath/index.js'} 여기서 Entry Point는 keyName입니다. 축약표현으로 작성하게 될 경우 main이 Entry Point의 기본값입니다. ex) entry: 'filePath/index.js'

Entry 속성은 webpack이 내부의 디펜던시 그래프를 생성하기 위해 사용해야 하는 모듈이며, webpack은 Entry Point가 의존하는 다른 모듈과 라이브러리를 찾습니다.

Entry 속성은 설정 방법에 따라서 단일 엔트리 구문과 다중 엔트리 구문을 설정할 수 있습니다. SPA(single page application)에서는 대부분 단일 엔트리 구문으로 Entry 속성을 설정하여 사용할 수 있습니다.

🔑 Tip

디펜던시 그래프는 webpack 공식문서에서 사용하는 개념으로 의존하고 있는 모든 모듈에 대해 재귀적으로 빌드한 다음 설정한 엔트리 모듈로 번들합니다.(여러가지 파일을 재귀적으로 찾아서 엔트리 파일로 통일한다고 생각하면 이해하기 쉽습니다.)

📖 Single Entry Syntax

// webpack.config.js

// 축약된 single entry syntax 표현
module.exports = {
  entry:'./src/index.js', 
  
  // 축약된 표현을 사용하면 chunk name과 asset file name이
  // 기본값 main으로 자동 생성됩니다.
}
// webpack.config.js

// 기본 single entry syntax 표현
module.exports = {
  entry: {
    app: './src/app.js',
  }
  
  // chunk name과 asset file name을 변경하고 싶은 경우 축약된
  // 표현이 아닌 기본 문법을 사용해서 변경할 수 있습니다.
}

프로젝트를 단위가 작은 경우 단일 엔트리 구문로 Webpack을 설정할 수 있습니다. 하지만 단일 엔트리 구문을 사용하면 설정을 확장할 수 있는 유연성이 떨어질 수 있습니다.

📖 Multi-Page Application

// webpack.config.js

module.exports = {
	entry: {
		app1: './src/app1.js',
		app2: './src/app2.js',
    }
  	// entry.app1, entry.app2가 Entry Point로 
  	// asset file(컴파일 후 출력된 file)에서 2개의 file이 생성됩니다.
}

다중 Entry Point를 설정하면 chunk name(Entry point name을 사용)으로 개별 디펜던시 그래프를 생성하여 asset을 사용할 수 있습니다.

// webpack.config.js

module.exports = {
	entry: ['./src/app1.js', './src/app2.js'],
  	output: {
    	filename: 'bundle.js',
  	},
}

위 예제는 Multi-Page Application처럼 동작한다고 예상할 수 있겠지만 다중-메인 엔트리 방식으로 app1.js, app2.js을 컴파일 해서 하나의 bundle로 만드는 방식입니다. 이 방식은 여러 의존성 파일을 한 번에 주입하고 해당 의존성을 하나의 chunk에 그래프를 표시하려는 경우에 유용합니다. (하나의 파일에 여러가지 module 의존성을 주입하기에 유용하다는 의미입니다.)

📖 Each Entry Point Description

Entry Point에 Description 객체 option을 통해서 여러가지 기능을 구현할 수 있습니다.

  • dependOn: 현재 Entry Point를 의존하는 Entry Point. dependOn에 지정한 Entry Point가 실행된 후에 실행됩니다.
  • import: 현재 Entry Point가 참조하고 로드하는 모듈(file path)
// webpack.config.js

module.exports = {
  entry: {
    app1: './src/js/app1.js',
    app2: {
      dependOn: 'app1',
      // app1.js의 entry point인 app1이 먼저 로드하고 
      // app2.js의 entry point app2를 로드하겠다고 명시적으로 알려주는 방법
      import: './src/js/app2.js',
      // app2 Entry Point에 명시적으로 로드할 모듈(file path)을 지정
    },
  },
  output: {
    filename: '[name].js',
    // entry 속성에서 2개의 디펜던시 그래프를 로드하므로, 
    // output 속성에도 따로 이름을 지어줘야합니다.
    // 여러개의 output이 필요한 경우 동적으로 asset file을 가져오기 위해서는 
    // webpack에 미리 지정된 Placeholders를 사용할 수 있습니다. ex) [name], [hash]
    clean: true,
  },
};

위의 사진을 보면 app2가 의존(depondOn 속성)하는 app1이 컴파일되고 난 후에 app2에서 설정한 import 속성에 맞게 file path를 정확히 찾아서 컴파일 됩니다.

  • filename: Entry Point에서 output asset(컴파일 결과 file)를 명시적으로 지정할 수 있습니다.
// webpack.config.js

module.exports = {
  entry: {
    app1: './src/js/app1.js',
    app2: {
      dependOn: 'app1',
      import: './src/js/app2.js',
      filename: 'utile.js', 
      // output 속성에서 filename을 지정하지 않고 entry 속성에서
      // 바로 작성할 수 있습니다.
    },
  },
  output: {
    filename: '[name].js',
    // 만약 위의 app2처럼 명시적으로 entry.app2.filename을
    // 작성한 경우 Placeholderss는 무시됩니다.
    clean: true,
  },
};
  • runtime: 런타임 청크의 이름입니다. 설정되면 runtime에 지정한 이름의 런타임 청크가 생성되고 그렇지 않을 경우에는 기존의 Entry Point 이름을 사용합니다.

위의 예시 사진에서 보면 Error가 발생하는데 They will use the runtime(s) from referenced entrypoints instead. Remove the 'runtime' option from the entrypoint.라는 문구가 보이며 현재는 사용하지 않는 option처럼 보입니다. 만약 제가 잘못 사용했다면 댓글로 알려주세요.

// webpack.config.js

// runtime 속성은 기존의 Entry Point를 가리키지 않아야 합니다.

module.exports = {
  entry: {
    app1: './src/js/app1.js',
    app2: {
      runtime: 'app1', // <- 기존의 entry point 사용 
      dependOn: 'app1',
      import: './src/js/app2.js',
      filename: 'utile.js',
    },
  },
}
  • publicPath: host된 브라우저를 참조할 때 Entry의 출력 파일(asset)에 대한 공용 URL 주소를 지정합니다. publicPath을 잘못 지정하면 외부 리소스를 가져올 때 404 Error가 발생할 수 있습니다. output.publicPath에 대해서 공부하면 더욱 명확하게 사용법을 확인할 수 있습니다.

  • library: 현재 엔트리에서 라이브러리를 번들링하려면 라이브러리 option을 지정합니다. (사용 방법 미숙지이며 라이브러리 프로젝트 제작에서 webpack을 사용하게 되는 경우 사용하는 option입니다.)

📇 Output

output 속성은 생성된 번들을 내보내는 위치와 파일 이름을 지정하는 방법을 webpack에게 알려주는 역할입니다. 가장 보편적으로 사용하는 폴더 이름을 ./dist입니다.

// webpack.config.js

const path = require('path') // node.js에서 지원하는 path 모듈을 사용할 수 있습니다.

module.exports = {
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
}

화면을 보여줄 html 파일에 번들한 결과인 bundle.js를 script tag에 삽입합니다.

<!-- index.html -->

<body>
	<script src="./dist/bundle.js"></script>			
</body>

webpack은 커맨드 라인을 통해 빌드가 가능하며 `webpack.config.js 파일을 만들면 간단한게 webpack 을 커맨들 라인에 작성하면 됩니다.

// webpack.config.js 파일을 작성하지 않은 경우 
$ webpack ./src/index.js ./dist/bundle.js

// webpack.config.js 파일을 작성한 경우
$ webpack 

🎞 Loader and Asset Modules

webpack은 모든 파일은 모듈로 관리합니다.(js, json 파일뿐만 아니라 이미지, 폰트, 스타일시드도 모듈로 관리하는게 목적) 그러나 webpack은 js 파일만 이해합니다. 로더를 사용하면 webpack에서 다른 유형의 파일을 사용할 수 있습니다.

로더는 testuse 키로 구성된 객체로 설정합니다.

  • 변환이 필요한 파일(들)을 식별하는 test 속성
  • 변환을 수행하는데 사용하는 로더를 가리키는 use 속성

css-loader, style-loader

자주 사용하는 로더인 css-loader 와 style-loader로 예시를 들게습니다.

$ npm i --save-dev css-loader style-loader
module: {
  rules: [
    {
      test: /\.css$/,  // 정규 표현식을 사용하여 확장자 css가 보이는 모든 파일을 로더한다.
      use: ['style-loader' ,'css-loader'],
    },
  ],
},
  • css-loader : css 파일을 자바스크립트로 변환하는 로더
  • style-loader : css-loader를 통해 자바스크립트로 변환된 스타일시트를 동적으로 돔에 추가하는 로더

위 설정에서는 testuse 라는 두 가지 필수 속성을 가진 하나의 모듈을 위해 rules 속성을 정의했습니다. webpack의 컴파일러가 이해하는 방식을 예로 들어 보겠습니다.

"webpack 컴파일러야 require() / import 문 내에서 '.css' 파일로 확인되는 경로를 발견하면 번들러에 추가하기 전에 style-loader, css-loader 를 사용하여 변환해 주렴"

handlebars-loader

js 파일, css 파일처럼 html 파일 또한 js 코드에 따라서 랜더링 시키고 싶은 경우가 있습니다. 이런 경우 템플릿 엔진인 handlebars를 사용할 수 있습니다. (다른 템플릿 엔진을 사용해도 괜찮습니다.)

$ npm i --save-dev handlebars-loader handlebars

handlebars를 install하지 않고 loader만 install 하는 경우 경고가 발생하기 때문에 handlebars도 npm에서 같이 다운 받아야 합니다.

module: {
  rules: [
    {
      test: /\.hbs$/,  // 확장자 이름은 hbs를 사용하면 파일을 만들때 간편합니다.(취향 존중)
      use: ['handlebars-loader'],
    },
  ],
},
<!-- template.hbs -->

<main>
	<ul>
    <li>사과</li>
    <li>바나나</li>
    <li>딸기</li>
  </ul>
</main>
// index.js

const tempHtml = require('./pages/template.hbs'); 

console.log(tempHtml()); // 모든 htmp tag를 문자열로 파싱하는 특징이 있습니다.

📚 플러그인

로더는 파일 단위로 특정 유형의 모듈을 변환하는 데 사용되자만, 플러그인은 번들된 결과를 처리합니다. 번들된 결과물을 최적화, 환경 변수 주입 등과 같은 광범위한 작업을 수행합니다.

플러그인을 사용하려면 require ()를 통해 플러그인을 요청하고 plugins 배열에 추가해야 합니다. 대부분의 플러그인은 옵션을 통해 사용자가 지정할 수 있습니다. 다른 목적으로 플러그인을 여러 번 사용하도록 설정할 수 있으므로 new 연산자로 호출하여 플러그인의 인스턴스를 만들어야 합니다.

HtmlWebpackPlugin

HtmlWebpackPlugin은 html 파일을 후처리하는데 사용합니다. HtmlWebpackPlugin 플러그인을 사용하여 빌드하면 html 파일로 아웃풋에 생성됩니다.

CleanWebpackPlugin

CleanWebpackPlugin은 빌드 이전에 결과물을 제거하는 플러그인입니다. 과거 파일이 남아 있을 수 있는 문제를 해결합니다.

MiniCssExtractPlugin

스타일시트가 점점 많아지면 하나의 자바스크립트 결과물로 만드는 것이 부담일 수 있습니다. 번들 결과에서 스트일시트 코드만 뽑아서 별도의 CSS 파일로 만들어 역할에 따라 파일을 분리하는 것이 좋습니다..

$ npm install -D html-webpack-plugin clean-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');


module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      filename: 'index.html', // output file name
      template: 'index.html', // template file name
      collapseWhitespace: true, // 빈칸 제거
    	removeComments: true, // 주석 제거
    }),
    new MiniCssExtractPlugin({ filename: '[name].css' }),
    new CleanWebpackPlugin({
      cleanAfterEveryBuildPatterns: ['dist'],
    }),
  ],

🧲 Mode

mode 파라미터를 development, production 또는 none으로 설정하면 webpack에 내장된 환경별 최적화를 활성화 할 수 있습니다. 기본값은 production 입니다. 환경 변수를 사용하여 production 모드와 development 모드를 변경하는 방식을 사용 할 수 있습니다.

module.exports = {
  mode: 'production', 
};

참고 자료

Webpack 공식 문서

profile
게시글에 잘못된 부분이 있으면 댓글로 알려주시면 빠르게 수정 및 수용도 하겠습니다. 🥲

0개의 댓글