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;
}
- 받아온 문자열을 원하는 정보로 파싱하는 것이 첫 번째 관문이었다. 디스트럭처링 할당을 사용하여 좀 더 가독성있게 작성할 수 있었다.
- 정렬을 위해서 플레이 타임과, 시작 시간을 분으로 바꿔서 파싱하였다.
- 정렬을 할 때 항상 헷갈렸었는데, 양수를 반환하면 자리를 바꾸고 음수를 반환하면 자리를 바꾸지 않는다는 것을 확실히 인지하였다.
- 이 문제에서 막혔던 부분은 음 중에서
#이 들어가는 음을 생각하지 못했다는 것이다. 이러한 사소한 문제로 문제가 막힐 수 있음을 인지하였고 코딩테스트 문제를 풀 때 주의해야겠다고 생각했다.