1011 - 1017

1011

49장 Babel과 Webpack을 이용한 ES6+/ES.NEXT 개발 환경 구축

  • ES6+와 ES.NEXT 사양을 사용하여 프로젝트를 진행하려면 최신 사양으로 작성된 코드를 경우에 따라 IE를 포함한 구형 브라우저에서 문제 없이 동작시키기 위한 개발 환경을 구축하는 것이 필요하다.
  • 또한 대부분 대부분의 프로젝트가 모듈을 사용하므로 모듈 로더도 필요하다.
  • ESM은 대부분의 모던 브라우저에서 사용할 수 있지만 다음과 같은 이유로 아직까지 ESM보다는 별도의 모듈 로더를 사용하는 것이 일반적이다.
    • IE를 포함한 구형 브라우저는 ESM을 지원하지 않는다.
    • ESM을 사용하더라도 트랜스파일링이나 번들링이 필요한 것은 변함이 없다.
    • ESM이 아직 지원하지 않는 기능(bare import 등)이 있고 아직 몇 가지 이슈가 존재한다.

Babel

Babel은 ES6+/ES.NEXT로 구현된 최신 사양의 소스코드를 IE 같은 구형 브라우저에서도 동작하는 ES5 사양의 소스코드로 변환(트랜스파일링)할 수 있다.

Babel 설치

# 프로젝트 폴더 생성
$ mkdir esnext-project && cd esnext-project

# package.json 생성
$ npm init -y

# babel-core, babel-cli 설치
$ npm install --save-dev @babel/core @babel/cli

Babel 프리셋 설치와 babel.config.json 설정 파일 작성

  • Babel을 사용하려면 @babel/preset-env를 설치해야 한다. 이는 함께 사용되어야 하는 Babel 플러그인을 모아 둔 것으로 Babel 프리셋이라고 부른다.
  • @babel/preset-env는 필요한 플러그인들을 프로젝트 지원 환경에 맞춰 동적으로 결정해 준다.
# @babel/preset-env 설치
$ npm install --save-dev @babel/preset-env
  • 프로젝트 루트 폴드에 babel.config.json 설정 파일을 생성하고 다음과 같이 작성한다. 설치한 프리셋을 사용하겠다는 의미다.
{
  "presets": ["@babel/preset-env"]
}

트랜스파일링

npm scripts에 Babel CLI 명령어를 등록한다.

{
  "scripts": {
    "build": "babel src/js -w -d dist/js"
  }
}
  • src/js 폴더에 있는 모든 JS 파일들을 트랜스파일링한 후, 그 결과물을 dist/js 폴더에 저장한다.
  • -w 옵션은 타깃 폴더에서 일어나는 변경을 감지하여 자동으로 트랜스파일한다.
  • -d 옵션은 트랜스파일링 결과물이 저장될 폴더를 지정한다.
  • @babel/preset-env는 현재 제안 단계에 있는 사양에 대한 플러그인을 지원하지 않기 때문에 이를 트랜스파일링하려면 별도의 플러그인을 설치해야 한다.

Babel 플러그인 설치

  • 설치가 필요한 Babel 플러그인은 Babel 홈페이지에서 검색할 수 있다.
  • 검색하여 플러그인을 설치하고 babel.config.json 설정 파일에 추가하면 된다.
npm install --save-dev @babel/plugin-proposal-class-properties
{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

브라우저에서 모듈 로딩 테스트

Babel만 사용하게 되면 Node.js가 기본 지원하는 Common.js 방식의 모듈 로딩 시스템에 따라 트랜스파일링된다.
이를 브라우저에서 실행하면 에러가 발생한다.
이를 해결하기 위해서 Webpack을 사용해야 한다.

Webpack

  • Webpack은 의존 관계에 있는 JS, CSS, 이미지 등의 리소스들을 하나의 파일로 번들링하는 모듈 번들러다.
  • 이를 사용하면 의존 모듈이 하나의 파일로 번들링되므로 별도의 모듈 로더가 필요없다.
  • 여러 개의 JS 파일을 하나로 번들링하므로 HTML 파일에서 script 태그 여러 개의 JS 파일을 로드해야 하는 번거로움도 사라진다.
  • Webpack과 Babel을 이용하여 개발 환경을 구축하려면, Webpack이 파일을 번들링하기 전에 Babel을 로드하여 트랜스파일링하는 작업을 실행하도록 설정해야 한다.

Webpack 설치

npm install --save-dev webpack webpack-cli

babel-loader 설치

Webpack이 모듈을 번들링할 때 Babel을 사용하여 트랜스파일링하도록 babel-loader를 설치한다.

npm install --save-dev babel-loader
  • npm scripts를 변경하여 Babel 대신 Webpack을 실행하도록 수정하자.
{
  "scripts": {
    "build": "webpack -w"
  }
}

webpack.config.js 설정 파일 작성

  • Webpack이 실행될 때 참조하는 설정 파일이다.
const path = require("path");
module.exports = {
  // entry file
  entry: "./src/js/app.js",
  // 번들링된 js 파일의 이름(filename)과 저장될 경로(path)를 지정
  output: {
    path: path.resolve(__dirname, "dist/js"),
    filename: "bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        include: [path.resolve(__dirname, "src/js")],
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env"],
            plugins: ["@babel/plugin-proposal-class-properties"],
          },
        },
      },
    ],
  },
  devtool: "source-map",
  mode: "development",
};

babel-polyfill 설치

  • Babel을 사용해서 트랜스파일링해도 브라우저가 지원하지 않는 코드가 남아 있을 수 있다.
    • ex) Promise, Object.assign, Array.from 등은 대체할 기능이 없기 때문에 트랜스파일링되지 못한다.
  • 구형 브라우저에서 위와 같은 객체나 메서드를 사용하기 위해서는 @babel/polyfill을 설치해야 한다.
npm install @babel/polyfill
  • 개발 환경에서만 사용하는 것이 아니라 실제 운영 환경에서도 사용해야 하기 때문에 –save-dev 옵션을 지정하지 않는다.
  • ES6의 import를 사용하는 경우에는 진입점의 선두에서 먼저 폴리필을 로드한다.
import "@babel/polyfill";
  • Webpack을 사용하는 경우에는 위 방법 대신 webpack.config.js 파일의 entry 배열에 폴리필을 추가한다.
const path = require('path');
module.exports = {
  // entry file
  entry: ['@babel/polyfill', './src/js/main.js'],
...

출처

모던 자바스크립트 Deep Dive

1012

프로그래머스 카카오 Level2 - 파일명 정렬

function solution(files) {
  const arr = [];
  for (const x of files) {
    const [, head, num] = x.match(/([a-zA-z-. ]+)([0-9]{1,5})/);
    arr.push([x, head, num]);
  }

  arr.sort((a, b) =>
    a[1].toLowerCase() > b[1].toLowerCase()
      ? 1
      : b[1].toLowerCase() > a[1].toLowerCase()
      ? -1
      : +a[2] > +b[2]
      ? 1
      : +b[2] > +a[2]
      ? -1
      : 0
  );
  return arr.map((v) => v[0]);
}
  • 이번에 공부한 정규 표현식을 사용하여 받은 데이터를 파싱하였다.
    • 영어 대소문자와 . - ‘ ‘ 를 계속 그룹화하고, 숫자를 만나면 최대 5개까지 그룹화하였다.
    • 여기서 띄워쓰기를 어떻게 표현하는지 제대로 몰라서 문제를 틀렸었다.
  • 정렬은 항상 할때마다 헷갈린다.
    • 반환값이 1보다 크면 바꾸고, -1 이하이면 안바꾼다.

1013

프로그래머스 카카오 Level2 - 튜플

function solution(s) {
  const answer = [];

  const arr = s
    .slice(1, s.length - 1)
    .split("}")
    .join("")
    .split("{");

  arr.shift();

  for (let i = 0; i < arr.length; i++) {
    const tmp = arr[i].replace(/,$/, (match) =>
      match.substring(0, match.length - 1)
    );

    arr[i] = tmp.split(",");
  }

  arr.sort((a, b) => a.length - b.length);
  const nh = new Set(arr.flat(1));
  for (const x of nh) {
    answer.push(+x);
  }

  return answer;
}
  • 최근에 학습한 정규 표현식과 set을 적극적으로 활용했다.
  • 정규 표현식에는 콜백함수를 넘길 수 있는데, 이를 활용하면 매칭된 정규 표현식을 조작해서 반환할 수 있다.

프로그래머스 카카오 Level2 - 오픈 채팅방

function solution(record) {
  const answer = [];
  const sh = new Map();

  for (const info of record) {
    const [behave, id, nick] = info.split(" ");
    if (behave === "Leave") {
      answer.push([id, behave]);
    } else {
      sh.set(id, nick);
      if (behave === "Enter") {
        answer.push([id, behave]);
      }
    }
  }

  for (let i = 0; i < answer.length; i++) {
    const [id, behave] = answer[i];

    answer[i] =
      behave === "Enter"
        ? `${sh.get(id)}님이 들어왔습니다.`
        : `${sh.get(id)}님이 나갔습니다.`;
  }

  return answer;
}

console.log(
  solution([
    "Enter uid1234 Muzi",
    "Enter uid4567 Prodo",
    "Leave uid1234",
    "Enter uid1234 Prodo",
    "Change uid4567 Ryan",
  ])
);
  • 간단한 문자열 파싱 문제였다.
  • 점점 자바스크립트 실력이 향상하는 것을 체감한다.

1014

프로그래머스 카카오 Level2 - 후보키

function solution(relation) {
  let answer = 0;

  function isAnswer(arr) {
    const newArr = [];
    for (const x of relation) {
      let tmp = "";
      for (const index of arr) {
        tmp += x[index];
      }
      if (newArr.includes(tmp)) return false;
      newArr.push(tmp);
    }
    return true;
  }

  let tmp = [];
  const nh = new Map();

  function DFS(L, s, c) {
    if (L === c) {
      const arr = [...tmp];
      if (isAnswer(arr)) {
        if (![...nh].some(([key]) => key.every((v) => tmp.includes(v)))) {
          nh.set(arr, 1);
          answer++;
        }
      }
    } else {
      for (let i = s; i < relation[0].length; i++) {
        tmp.push(i);
        DFS(L + 1, i + 1, c);
        tmp.pop();
      }
    }
  }

  for (let i = 0; i < relation[i].length; i++) {
    tmp = [];
    DFS(0, 0, i + 1);
  }

  return answer;
}
  • DFS로 조합을 구하고, 해당 키로 모든 릴레이션을 식별할 수 있는지 구할 수 있는 isAnswer 함수를 작성했다.
  • 맵을 생성해서 맵에 조합에 속하는 키가 하나라도 있다면 최소성을 만족하지 못하기 때문에 제외하였고, 그렇지 않다면 최소성을 만족하기에 맵에 넣고 답의 개수를 1 증가시켜서 문제를 해결하였다.

1015

프로그래머스 카카오 Level2 - 프렌즈 4블록

function solution(m, n, board) {
  let answer = 0;
  const dx = [0, 1, 1];
  const dy = [1, 1, 0];
  board = board.map((v) => v.split(""));

  while (true) {
    let tmp = 0;
    let ch = Array.from(Array(m), () => Array(n).fill(0));
    for (let x = 0; x < m - 1; x++) {
      for (let y = 0; y < n - 1; y++) {
        let flag = true;
        if (board[x][y] === 0) flag = false;
        for (let k = 0; k < 3; k++) {
          const nx = x + dx[k];
          const ny = y + dy[k];

          if (board[nx][ny] !== board[x][y]) flag = false;
        }
        if (flag) {
          if (ch[x][y] === 0) {
            tmp++;
            ch[x][y] = 1;
          }
          for (let k = 0; k < 3; k++) {
            const nx = x + dx[k];
            const ny = y + dy[k];

            if (ch[nx][ny] === 0) {
              tmp++;
              ch[nx][ny] = 1;
            }
          }
        }
      }
    }

    for (let x = 0; x < m; x++) {
      for (let y = 0; y < n; y++) {
        if (ch[x][y] === 1) {
          for (let k = x; k > 0; k--) {
            board[k][y] = board[k - 1][y];
            board[k - 1][y] = 0;
          }
        }
      }
    }

    if (!tmp) break;
    answer += tmp;
  }

  return answer;
}
  • 한 문제 내부에서 이렇게 많은 로직을 작성해야한다는 사실이 생소하여 처음에는 약간 거부감이 느껴졌다.
  • 반복문만 돌리면 해결하는 문제였는데 처음부터 어렵게 생각해서 BFS로 접근한 것이 실수였다.
  • 요소를 반복하여 돌면서 우, 우하, 하 위치에 있는 요소를 확인하여 모두 현재 요소와 같은지 비교하고 같다면 체크, 같지 않다면 패스하였다.
  • 해당 요소가 부서지면 위에 요소를 내려줘야했기에 이를 해결해주는 반복문도 작성하였다.
  • 위 로직을 부서지는 요소가 있는 동안 계속해서 반복하였다. 이렇게 하나하나 해결해나가는 문제도 익숙해지도록 연습해야겠다.

1016

프로그래머스 카카오 Level3 - 추석 트래픽

function solution(lines) {
  let answer = 0;
  lines = lines.map((line) => {
    let [, S, T] = line.split(" ");
    const [hour, minute, millisecond] = S.split(":");

    S = +hour * 60 * 60 * 1000 + +minute * 60 * 1000 + +millisecond * 1000;
    T = +T.substring(0, T.length - 1) * 1000 - 1;
    return [S - T, S];
  });

  for (let i = 0; i < lines.length; i++) {
    const standardTime = lines[i][1] + 999;
    let cnt = 1;
    for (let j = i + 1; j < lines.length; j++) {
      if (standardTime >= lines[j][0]) cnt++;
    }
    answer = Math.max(answer, cnt);
  }

  return answer;
}
  • 간만에 3단계 문제에 도전했는데 잘 해결해서 기분이 좋았다.
  • 노트에 어떻게 문제를 해결할 지 작성하고, 그대로 코딩을 했다.
  • 우선 받아온 배열을 내 생각대로 사용하기 위해 요청 시간과, 응답 시간으로 파싱해서 저장했다.
  • 밀리세컨드 단위로 포문을 돌리면 어떻게 해도 시간초과가 나기 때문에 요청 시간을 기준으로 코드를 작성하면 될 것이라고 생각해서 문제를 해결했다.

1017

일 - 알고리즘

프로그래머스 카카오 Level2 - 방금그곡

function solution(m, musicinfos) {
  let answer = "";

  musicinfos = musicinfos.map((musicinfo) => {
    let [start, end, title, info] = musicinfo.split(",");
    let [startHour, startMinute] = start.split(":");
    start = +startHour * 60 + +startMinute;

    let [endHour, endMinute] = end.split(":");
    end = +endHour * 60 + +endMinute;

    const playtime = end - start;

    info = info.match(/.#{0,1}/g);

    while (info.length < playtime) {
      info = [...info, ...info];
    }

    info = info.slice(0, playtime);

    return [playtime, start, title, info];
  });

  musicinfos.sort((a, b) =>
    a[0] > b[0] ? -1 : a[0] < b[0] ? 1 : a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0
  );

  outer: for (let i = 0; i < musicinfos.length; i++) {
    const arr = m.match(/.#{0,1}/g);
    for (let j = arr.length; j <= musicinfos[i][3].length; j++) {
      if (musicinfos[i][3].slice(j - arr.length, j).join("") === m) {
        answer = musicinfos[i][2];
        break outer;
      }
    }
  }
  if (!answer) answer = "(None)";
  return answer;
}
  • 받아온 문자열을 원하는 정보로 파싱하는 것이 첫 번째 관문이었다. 디스트럭처링 할당을 사용하여 좀 더 가독성있게 작성할 수 있었다.
  • 정렬을 위해서 플레이 타임과, 시작 시간을 분으로 바꿔서 파싱하였다.
  • 정렬을 할 때 항상 헷갈렸었는데, 양수를 반환하면 자리를 바꾸고 음수를 반환하면 자리를 바꾸지 않는다는 것을 확실히 인지하였다.
  • 이 문제에서 막혔던 부분은 음 중에서 #이 들어가는 음을 생각하지 못했다는 것이다. 이러한 사소한 문제로 문제가 막힐 수 있음을 인지하였고 코딩테스트 문제를 풀 때 주의해야겠다고 생각했다.

태그:

카테고리:

업데이트: