
최상의 성능을 위해 어플리케이션을 최적화하는 개발도구
Webpack 사용
$ npm i -D webpack webpack-cli webpack-dev-server
$ npm i -D terser-webpack-plugin
//webpack.config.js
const path = require("path");
const TerserWebpackPlugin = require("terser-webpack-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./dist"),
clean: true,
},
devtool: "source-map",
mode: "development",
optimization: {
minimizer: [new TerserWebpackPlugin()],
},
};
TerserWebpackPlugin 을 생성하여 minimizer 에 입력 후 $ npx webpack 을 실행한다.dist 경로에 bundle.js , bundle.js.map 파일이 생성된다.HTML과 CSS파일을 설정해줄 모듈을 설치한다.
$ npm i -D html-webpack-plugin
$ npm i -D mini-css-extract-plugin css-loader css-minimizer-webpack-plugin
Lodash, 객체, 배열 등의 데이터 구조를 쉽게 변환해 사용하게 도와주는
자바스크립트 라이브러리
$ npm i -g npm
$ npm i --save lodash
//webpack.config.js
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); /
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "./dist"),
clean: true,
},
devtool: 'source-map',
mode: "development",
devServer: {
host: "localhost",
port: 4200,
open: true,
watchFiles: 'index.html',
},
plugins: [
new HtmlWebpackPlugin({
title: "keyboard",
template: "./index.html",
inject: "body",
favicon: "./favicon.ico"
}),
new MiniCssExtractPlugin({ filename: "style.css" }),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
],
},
optimization: {
minimizer: [
new TerserPlugin(),
new CssMinimizerPlugin()
]
},
};
build를 production 모드에서 하기//package.json
"scripts": {
"build": "webpack --mode=production", //npm run build
"dev": "webpack-dev-server" //npm run dev
},
$ npx webpack-dev-server 입력해서 잘 동작하는지 확인//webpack.config.js
devServer: {
host: "localhost",
port: 4200,
open: true,
watchFiles: 'index.html',
},
Webpack Dev Server로 인해 개발환경이 설정이 제대로 되었다면
브라우저를 새로고침하지 않아도 자동으로 새로고침되어 개발하기 편한 환경 탄생!
$ npm run dev를 통해 브라우저를 열 수 있다.
ESLint, 코드 퀄리티를 보장하도록 도와준다.
Prettier, 코드 스타일을 깔끔하게 도와준다.
$ npm i -D eslint
$ npm install --save-dev --save-exact prettier
$ npm i -D eslint-config-prettier eslint-plugin-prettier
$ npx eslint --init
//.eslintrc.json
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
"parserOptions": {
"ecmaVersion": 13,
"sourceType": "module"
},
"rules": {
"prettier/prettier": "error"
}
}
//.prettierrc.json
{
"arrowParens": "always",
"bracketSameLine": false,
"bracketSpacing": true,
"embeddedLanguageFormatting": "auto",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"jsxSingleQuote": false,
"printWidth": 80,
"proseWrap": "preserve",
"quoteProps": "as-needed",
"requirePragma": false,
"semi": true,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"useTabs": false,
"vueIndentScriptAndStyle": false
}
//settings.json
{
//저장할 때 prettier가 포맷한다.(js.css.html 모두)
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
//저장될 때 eslint 룰대로 fix 한다.
"source.fixAll.eslint": true
}
다크테마 기능을 사용하기 위한 css 설정
/* style.css */
html[theme="dark-mode"] {
filter: invert(100%) hue-rotate(180deg);
}
<!-- index.html -->
<!DOCTYPE html>
<html theme=""></html>
class Keyboar 안에서 DOM을 가져와서 DOM에 이벤트를 붙이는 형식으로 구성
#를 붙이면 private변수가 되서 class 외부에서 값을 오해하거나 덮어씌울 수 없다.
[ES2019부터 javascript에서 지원되는 속성]
인스턴스가 생성되었기 때문에 keyboard.js 안의 consteuctor()가 실행되고
#assignElement(), #addEvent()가 실행되면서 console에 true / false 값이 찍힌다.
true 일 때 다크테마모드 적용, false면 해제.
//keyboard.js
export class Keyboard {
#swichEl;
constructor() {
this.#assignElement();
this.#addEvent();
}
//엘리먼트 가져오기
#assignElement() {
this.#swichEl = document.getElementById("switch");
}
//이벤트를 가져오는 메소드
#addEvent() {
this.#swichEl.addEventListener("change", (event) => {
document.documentElement.setAttribute(
"theme",
event.target.checked ? "dark-mode" : ""
);
console.log(event.target.checked);
});
}
}
<div class="select-box"> 에 <select id="font"> 를 줘서
변경될 때 마다 변화를 감지하고 옵션태그 안의 값을 이용해서 폰트를 변경하는 것을 구현
//keyboard.js
export class Keyboard {
#swichEl; //다크모드 스위치 엘레멘트
#fontSelectEl; //폰트 변경 엘레멘트
constructor() {
this.#assignElement();
this.#addEvent();
}
#assignElement() {
this.#swichEl = document.getElementById("switch");
this.#fontSelectEl = document.getElementById("font");
}
#addEvent() {
this.#swichEl.addEventListener("change", (event) => {
document.documentElement.setAttribute(
"theme",
event.target.checked ? "dark-mode" : ""
);
console.log(event.target.checked);
});
this.#fontSelectEl.addEventListener("change", (event) => {
//가져온 폰트값 body에 적용하기
//body에 있는 모든 폰트들이 선택한 폰트로 바뀌게 된다.
document.body.style.fontFamily = event.target.value;
//select-box이기때문에 value를 가져올 수 있다.
});
}
}
코드 리팩토링 [비용절감 / 이벤트핸들러 분리]
//keyboard.js
export class Keyboard {
#swichEl; //다크모드 스위치 엘레멘트
#fontSelectEl; //폰트 변경 엘레멘트
#containerEl;
constructor() {
this.#assignElement();
this.#addEvent();
}
#assignElement() {
this.#containerEl = document.getElementById("container");
this.#swichEl = this.#containerEl.querySelector("#switch");
this.#fontSelectEl = this.#containerEl.querySelector("#font");
}
#addEvent() {
this.#swichEl.addEventListener("change", this.#onChangeTheme);
this.#fontSelectEl.addEventListener("change", this.#onChangeFont);
}
//다크테마모드 변경
#onChangeTheme(event) {
document.documentElement.setAttribute(
"theme",
event.target.checked ? "dark-mode" : ""
);
console.log(event.target.checked);
}
//폰트 변경
#onChangeFont(event) {
document.body.style.fontFamily = event.target.value;
}
}
키보드 이벤트 추가하기
KeyboardEvent { isTrusted: true, key: ‘a’, code: ’KeyA’, …}
//keyboard.js
#addEvent() {
this.#swichEl.addEventListener("change", this.#onChangeTheme);
this.#fontSelectEl.addEventListener("change", this.#onChangeFont);
document.addEventListener("keydown", (event) => {
console.log(event.code);
if (this.#keyboardEl.querySelector(`[data-code=${event.code}]`)) {
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
.classList.add("active");
}
});
document.addEventListener("keyup", (event) => {
if (this.#keyboardEl.querySelector(`[data-code=${event.code}]`)) {
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
.classList.remove("active");
}
});
}
//keyboard.js
#addEvent() {
this.#swichEl.addEventListener("change", this.#onChangeTheme);
this.#fontSelectEl.addEventListener("change", this.#onChangeFont);
document.addEventListener("keydown", (event) => {
console.log(event.code);
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
?.classList.add("active");
});
document.addEventListener("keyup", (event) => {
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
?.classList.remove("active");
});
}
한글 입력 불가 메세지 표시 및 한글 입력 막기
keydown 즉, 입력된 키가 한글인지 캐치해서 인풋그룹에 에러 클래스를 추가해input value 중에 한글을 찾아내서 한글을 공백으로 치환해준다. replace()의 첫번째 파라미터에는 정규식을 넣어주고 두번째는 ""으로 바꿔준다. ""으로 바꾼 값을 다시 inputEl.value에 넣어준다.export class Keyboard {
#swichEl;
#fontSelectEl;
#containerEl;
#keyboardEl;
#inputGroupEl;
#inputEl;
constructor() {
this.#assignElement();
this.#addEvent();
}
.
.
.
#addEvent() {
this.#swichEl.addEventListener("change", this.#onChangeTheme);
this.#fontSelectEl.addEventListener("change", this.#onChangeFont);
document.addEventListener("keydown", this.#onKeyDown.bind(this));
document.addEventListener("keyup", this.#onKeyUp.bind(this));
this.#inputEl.addEventListener("input", this.#onInput);
}
#onInput(event) {
event.target.value = event.target.value.replace(/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/, "");
}
#onKeyDown(event) {
this.#inputGroupEl.classList.toggle(
"error",
/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(event.key)
);
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
?.classList.add("active");
}
#onKeyUp(event) {
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
?.classList.remove("active");
}
#onChangeTheme(event) {
document.documentElement.setAttribute(
"theme",
event.target.checked ? "dark-mode" : ""
);
console.log(event.target.checked);
}
#onChangeFont(event) {
document.body.style.fontFamily = event.target.value;
}
}
마우스 이벤트 추가하기
export class Keyboard {
#swichEl;
#fontSelectEl;
#containerEl;
#keyboardEl;
#inputGroupEl;
#inputEl;
//예외처리
#keyPress = false;
#MouseDown = false;
constructor() {
this.#assignElement();
this.#addEvent();
}
.
.
.
#onMouseUp(event) {
//키보드를 누를 때 마우스가 동작하지 않도록 해주고
//mouseUp상태니까 this.#MouseDown = false;로 .
if (this.#keyPress) return;
this.#MouseDown = false;
const KeyEl = event.target.closest("div.key");
const isActive = !!KeyEl?.classList.contains("active");
const val = KeyEl?.dataset.val;
if (isActive && !!val && val !== "Space" && val !== "Backspace") {
this.#inputEl.value += val;
}
if (isActive && val === "Space") {
this.#inputEl.value += " ";
}
if (isActive && val === "Backspace") {
this.#inputEl.value = this.#inputEl.value.slice(0, -1);
}
this.#keyboardEl.querySelector(".active")?.classList.remove("active");
}
#onMouseDown(event) {
//키보드를 입력하고 있을 때 마우스핸들러는 작동하지 않게하기
//메소드 자체가 실행이 안되고 밑에 있는 코드들에 접근하지 못하게 막기
if (this.#keyPress) return;
this.#MouseDown = true;
event.target.closest("div.key")?.classList.add("active");
}
#onInput(event) {
event.target.value = event.target.value.replace(/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/, "");
}
#onKeyDown(event) {
//#MouseDown 상태면 키보드 이벤트핸들러는 동작할 수 없게하기
if (this.#MouseDown) return;
//onKeyDown한 상태니까 keyPress가 true인 상태.
this.#keyPress = true;
this.#inputGroupEl.classList.toggle(
"error",
/[ㄱ-ㅎ|ㅏ-ㅣ|가-힣]/.test(event.key)
);
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
?.classList.add("active");
}
#onKeyUp(event) {
//#MouseDown 상태면 키보드 이벤트핸들러는 동작할 수 없게하기
if (this.#MouseDown) return;
//onKeyUp을 하면 키보드가 눌려진 상태가 아니니까 false.
this.#keyPress = false;
this.#keyboardEl
.querySelector(`[data-code=${event.code}]`)
?.classList.remove("active");
}
.
.
.
}