🎁 Module bundler/📖 Webpack

[Webpack] express 환경에서의 웹팩 사용법 (scss, es6 js 변환)

DarrenKwonDev 2020. 3. 19. 23:23

 

 

webpack

webpack is a module bundler. Its main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.

webpack.js.org

 

프론트엔드 개발환경의 이해: 웹팩(기본)

1. 배경

jeonghwan-kim.github.io

 

 

 

 

webpack은 모듈 번들러이다. SCSS나 babel을 적용한 js 등 dependencies가 있는 파일들을 일반 브라우저가 이해할 수 있는 일반 파일로 변환할 것이다.

 

웹팩이 터미널에 오류에 대한 내용을 상세히 알려주므로 웹팩이 하라는 대로 고분고분하게 해주면 작동할 것이다.

 

 

🚈 설치 및 사용

 

1. webpack과 webpack-cli 를 설치한다. 개발 단계에서만 사용할거니까 devdeps로.

npm i webpack webpack-cli -D

 

2. webpack.config.js를 구성합니다.

 

entry, output을 구성해주고, mode도 설정합니다.

 

주의할 점은 webpack.config.js는 ES6 모던 JS가 아니라 구식의 JS로 작성해야 한다는 점이다. babel로 변환 작업을 거치지 않기 때문이다. 

 

이후 프로덕션 상황에서 매번 수작업으로 고치는 것이 번거롭기 때문에 package.json에서 ENV를 변형해주는 것이 좋습니다.

const path = require("path");

const MODE = process.env.WEBPACK_ENV;
const ENTRY_FILE = path.resolve(__dirname, "assets", "js", "main.js");
const OUTPUT_DIR = path.join(__dirname, "static");

const config = {
  entry: ENTRY_FILE,
  mode: MODE,
  module: {
    rules: [
      {rule1},
      {rule2},
      {rule3},
    ],
  },
  output: {
    path: OUTPUT_DIR,
    filename: "[name].[format]",
  },
};

module.exports = config;

 

스크립트를 실행할 때 자동으로 WEBPACK_ENV를 바꾸도록 설정했습니다. 윈도우 환경에서는 cross-env가 있어야 합니다.

 

또, 개발시 편의성을 위해 대상 파일이 변경되었을 때 자동으로 웹팩을 설정하도록 옵션 w를 주었습니다.

(이거 안하면 굉장히 귀찮습니다 꼭 해줍시다)

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev:server": "nodemon --exec babel-node app.js",
    "dev:assets": "cross-env WEBPACK_ENV=development webpack -w",
    "build:assets": "cross-env WEBPACK_ENV=production webpack"
  },

 

 

3. loader 설치 및 rule 구성하기

 

텍스트 추출 : https://github.com/webpack-contrib/extract-text-webpack-plugin

 

 

scss를 변환해보자.

 

scss를 css로 변환하고 (scss loader), 웹 호환성을 위한 prefixes를 설정한 다음 (postcss-loader), css로 변환(css-loader)해야 한다.

 

중요한 건 이 순서인데, 이 순대를 차례대로 적는 것이 아니라 아래부터 위로 코드가 실행되기 때문에 역순으로 적어야 한다는 것이다. 이를 반영하자면 다음과 같이 작성될 수 있다.

 

* 아, 그리고 css 파일 분리를 위해 extract-text-webpack-plugin을 사용했는데 현재는

mini-css-extreact-plugin으로 대체되었습니다. 이는 다른 최신의 포스트에서 사용했으므로 참고하시면 될 것 같습니다

const path = require("path");
const ExtractCSS = require("extract-text-webpack-plugin");

const MODE = process.env.WEBPACK_ENV;
const ENTRY_FILE = path.resolve(__dirname, "assets", "js", "main.js");
const OUTPUT_DIR = path.join(__dirname, "static");

const config = {
  entry: ENTRY_FILE,
  mode: MODE,
  plugins: [new ExtractCSS("styles.css")],
  module: {
    rules: [
      {
        test: /\.(scss)$/,
        use: ExtractCSS.extract([
          {
            loader: "css-loader",
          },
          {
            loader: "postcss-loader",
          },
          {
            loader: "sass-loader",
          },
        ]),
      },
    ],
  },
  output: {
    path: OUTPUT_DIR,
    filename: "[name].js",
  },
};

module.exports = config;

 

웹팩을 통해 build해보면 node-sass가 없다는 등 뭐가 없다고 오류 메세지를 보내오는데 시키는 대로 고분고분 설치해주자.

 

설치 후 build를 진행합시다. output의 path에 지정한 곳을 확인해보면 변환된 파일을 확인할 수 있습니다.

 

 

scss만 변환해보았는데 ES6 이상의 js를 비롯하여 많은 내용들을 변환할 수 있습니다. 필요에 따라 공부하고 활용합시다.

 

 

 


 

번외) babel 적용한 js 변환하기

 

babel-loader와 babel-polyfill 설치합시다

(https://babeljs.io/docs/en/babel-polyfill)

 

const path = require("path");
const autoprefixer = require("autoprefixer");
const ExtractCSS = require("extract-text-webpack-plugin");

const MODE = process.env.WEBPACK_ENV;
const ENTRY_FILE = path.resolve(__dirname, "assets", "js", "main.js");
const OUTPUT_DIR = path.join(__dirname, "static");

const config = {
  entry: ["@babel/polyfill", ENTRY_FILE],
  mode: MODE,
  plugins: [new ExtractCSS("styles.css")],
  module: {
    rules: [
      {
        test: /\.(js)$/,
        use: [
          {
            loader: "babel-loader",
          },
        ],
      },
      {
        test: /\.(scss)$/,
        use: ExtractCSS.extract([
          {
            loader: "css-loader",
          },
          {
            loader: "postcss-loader",
            options: {
              plugin() {
                return [autoprefixer({ browsers: "cover 99.5%" })];
              },
            },
          },
          {
            loader: "sass-loader",
          },
        ]),
      },
    ],
  },
  output: {
    path: OUTPUT_DIR,
    filename: "[name].js",
  },
};

module.exports = config;

 

 

 


 

🚗 path.join과 path.resolve의 차이는 무엇인가?

 

아무 인자도 없으면 join은 현재 경로를 상대 경로로 반환하고 resolve는 현재 경로를 절대 경로로 반환한다.

뭘 쓸지는 코더의 선택이다.

path.join(); // .
path.resolve(); // C:\Users\USERNAME\FULL_PATH_TO_THE_FOLDER

 

때문에 resolve에 /를 입력하면 최상위 루트부터 시작하게 된다. 대개의 경우 불상사다.

path.join('hello'); // hello
path.resolve('hello'); // C:\Users\USERNAME\FULL_PATH_TO_THE_FOLDER\hello
path.join('/hello'); // \hello
path.resolve('/hello'); // C:\hello

 

resolve의 경우 인자에 /를 넣는 것을 피하자. 두번째 인자부터 앞에 /이 붙으면 첫번째 인자의 경로를 먹어버린다.

대신 앞에 .을 찍어서 상대 경로임을 알려주면 정상적으로 작동한다.

path.resolve('hello', '/path'); // C:\path

path.resolve('wwwroot', 'static_files/png/', '../gif/image.gif');
// If the current working directory is /home/myself/node,
// this returns '/home/myself/node/wwwroot/static_files/gif/image.gif'

 

 

🚗 __dirname은 어디인가?

당신이 작성하고 있는 프로그램의 경로이다. 명령창을 켜보면 확인할 수 있다. 당연히 이후 서비스를 위해 가상 컴퓨터 위에 올리면 그 가상 컴퓨터에서 사용하는 경로가 뜰 것이다.