yjryu / KERIS star
윤주 2023-10-30
231030 류윤주 사용자 등록 커밋
@3bb27b4eacf654b94e13f067dd5080c9ed44bb62
client/resources/js/commonUtil.ts (Renamed from client/resources/js/commonUtil.js)
--- client/resources/js/commonUtil.js
+++ client/resources/js/commonUtil.ts
@@ -6,30 +6,244 @@
  * 공통 자바스크립트 Util입니다.
  */
 
-
 const COMMON_UTIL = (function () {
 
 	var _utils = {
 
+
+		engNum: function(value : any) {
+			let engNum =  /^[a-zA-Z0-9]*$/;
+			if(engNum.test(value)) {
+				return true
+			} else {
+				return false
+			}
+		},
+
+		/**
+		 * 오늘 년-월-일 구하기
+		 */
+		today: function() {
+			let date = new Date();
+			let today = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10);
+			return today;
+		},
+
+		/**
+		 * 오늘 년-월-일 구하기
+		 */
+		dateTime: function() {
+			let date = new Date();
+			let today = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)).toISOString().substring(0, 16);
+			return today;
+		},
+
+		/**
+		 * 한달전 년-월-일 구하기
+		 */
+		oneMonthAgo: function() {
+			let date = new Date();
+			let oneMonthAgo = new Date(date.setMonth(date.getMonth() - 1) - (date.getTimezoneOffset() * 60000)).toISOString().substring(0, 10);
+			return oneMonthAgo;
+		},
+
+		/**
+		 * null값 '-' 으로 치환
+		 */
+		nullHyphen: function(data : any) {
+			if(data === null || data === "") {
+				return "-";
+			} else {
+				return data;
+			}
+		},
+
+		/**
+		 * 일자에 '-' 넣기
+		 */
+		dateHyphen: function(data : any) {
+			if(data === null || data === "") return "-";
+			let formatDate = '';
+			// 공백제거
+			data = data.replace(/\s/gi, "");
+			if(data.length == 8) {
+				formatDate = data.replace(/(\d{4})(\d{2})(\d{2})/, '$1-$2-$3');
+			} else {
+				formatDate = data;
+			}
+			 
+			return formatDate;
+		},
+
+		/**
+		 * 일시에서 시,분,초 자르기
+		 */
+		yyyymmdd: function(data : any) {
+			if(data === null || data === "") {
+				return "-";
+			} else {
+				let date = data.substr(0,10);
+				return date;
+			}
+		},
+
+		/**
+		 * 전화번호 출력 시 '-'을 추가하여 출력
+		 */
+		HyphenMinus: function( phoneNumber : any ) {
+			if(!phoneNumber) return phoneNumber
+
+			phoneNumber = phoneNumber.replace(/[^0-9]/g, '')
+		
+			let tmp = ''
+			if( phoneNumber.length < 4){
+				return phoneNumber;
+			}
+			else if(phoneNumber.length < 7)
+			{
+				tmp += phoneNumber.substr(0, 3);
+				tmp += '-';
+				tmp += phoneNumber.substr(3);
+				return tmp;
+			}
+			else if(phoneNumber.length == 8)
+			{
+				tmp += phoneNumber.substr(0, 4);
+				tmp += '-';
+				tmp += phoneNumber.substr(4);
+				return tmp;
+			}
+			else if(phoneNumber.length < 10)
+			{
+				if(phoneNumber.substr(0, 2) =='02') { //02-123-5678            
+					tmp += phoneNumber.substr(0, 2);
+					tmp += '-';
+					tmp += phoneNumber.substr(2, 3);
+					tmp += '-';
+					tmp += phoneNumber.substr(5);
+					return tmp;
+				}
+			}
+			else if(phoneNumber.length < 11)
+			{
+				if(phoneNumber.substr(0, 2) =='02') { //02-1234-5678            
+					tmp += phoneNumber.substr(0, 2);
+					tmp += '-';
+					tmp += phoneNumber.substr(2, 4);
+					tmp += '-';
+					tmp += phoneNumber.substr(6);
+					return tmp;
+				} else {                        //010-123-4567            
+					tmp += phoneNumber.substr(0, 3);
+					tmp += '-';
+					tmp += phoneNumber.substr(3, 3);
+					tmp += '-';
+					tmp += phoneNumber.substr(6);
+					return tmp;
+				}
+			}
+			else { //010-1234-5678        
+				tmp += phoneNumber.substr(0, 3);
+				tmp += '-';
+				tmp += phoneNumber.substr(3, 4);
+				tmp += '-';
+				tmp += phoneNumber.substr(7);
+				return tmp;
+			}
+		},
+
+		/**
+		 * 전화번호 자동 '-' 삽입
+		 */
+		//전화번호 입력 시 자동 '-' 삽입  
+		getMask: function( phoneNumber : any ) {
+			if(!phoneNumber) return phoneNumber
+				phoneNumber = phoneNumber.replace(/[^0-9]/g, '')
+		
+			let res = ''
+			if(phoneNumber.length < 3) {
+				res = phoneNumber
+			}
+			else {
+				if(phoneNumber.substr(0, 2) =='02') {
+					if(phoneNumber.length <= 5) {//02-123-5678
+						res = phoneNumber.substr(0, 2) + '-' + phoneNumber.substr(2, 3)
+					}
+					else if(phoneNumber.length > 5 && phoneNumber.length <= 9) {//02-123-5678
+						res = phoneNumber.substr(0, 2) + '-' + phoneNumber.substr(2, 3) + '-' + phoneNumber.substr(5)
+					}
+					else if(phoneNumber.length > 9) {//02-1234-5678
+						res = phoneNumber.substr(0, 2) + '-' + phoneNumber.substr(2, 4) + '-' + phoneNumber.substr(6)
+					}
+					} else {
+					if(phoneNumber.length < 8) {
+						res = phoneNumber
+					}
+					else if(phoneNumber.length == 8)
+					{
+						res = phoneNumber.substr(0, 4) + '-' + phoneNumber.substr(4)
+					}
+					else if(phoneNumber.length == 9)
+					{
+						res = phoneNumber.substr(0, 3) + '-' + phoneNumber.substr(3, 3) + '-' + phoneNumber.substr(6)
+					}
+					else if(phoneNumber.length == 10)
+					{
+						res = phoneNumber.substr(0, 3) + '-' + phoneNumber.substr(3, 3) + '-' + phoneNumber.substr(6)
+					}
+					else if(phoneNumber.length > 10) { //010-1234-5678
+						res = phoneNumber.substr(0, 3) + '-' + phoneNumber.substr(3, 4) + '-' + phoneNumber.substr(7)
+					}
+				}
+			}
+		return res
+		},
+
+		/**
+		 * 비밀번호 일치 체크
+		 */
+		checkPassword: function(pw : any, pwC : any) {
+			if (pw != pwC) return false;
+			return true;
+		},
+		/**
+		 * 휴대폰 번호 정규식
+		 */
+		checkPhone: function(data : any) {
+			let regExp = /^01([0|1|6|7|8|9]?)-?([0-9]{3,4})-?([0-9]{4})$/;
+			if (regExp.test(data) === true) return true;
+			return false;
+		},
+
+		/**
+		 * 이메일 정규식
+		 */
+		checkEmail: function (data : any) {
+		  	// 이메일 형식 검사
+			let validateEmail = /^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\.[A-Za-z0-9\\-\\.]+$/;
+			if (validateEmail.test(data) === true) return true;
+			return false;
+		},
+
 		/**
 		 * 빈 객체 여부
 		 */
-		isEmpty: function (data) {
-			if (data === undefined || data === null || data === "" || data.length === 0 || (data.constructor == Object && Object.keys(data).length === 0)) {
+		isEmpty: function (data : any) {
+			if (data === undefined || data === null || data === "null" || data === "" || data.length === 0 || (data.constructor == Object && Object.keys(data).length === 0)) {
 				if ((typeof data) === "number") {
-					return false
+					return true
 				} else {
-					return true;
+					return false;
 				}
 			} else {
-				return false;
+				return true;
 			}
 		},
 
 		/**
 		 * empty to null
 		 */
-		toNull: function (data) {
+		toNull: function ( data : any) {
 			if(data === undefined || data === "") {
 				try {
 					data = null;
@@ -46,23 +260,7 @@
 		/**
 		 * string to JSON
 		 */
-		toJson: function (data) {
-			if ("string" === (typeof data)) {
-				try {
-					return JSON.parse(data);
-				} catch (e) {
-					console.log("commonUtil.js - string to json convert error : ", e);
-					return data;
-				}
-			} else {
-				return data;
-			}
-		},
-
-		/**
-		 * string to JSON
-		 */
-		toJson: function (data) {
+		toJson: function (data : any) {
 			if ("string" === (typeof data)) {
 				try {
 					return JSON.parse(data);
@@ -78,7 +276,7 @@
 		/**
 		 * JSON to string
 		 */
-		toString: function (data) {
+		toString: function (data : any) {
 			try {
 				return JSON.parse(data);
 			} catch (e) {
@@ -90,12 +288,12 @@
 		/**
 		 * 다중 separator split
 		 */
-		split: function (text, separator) {
+		split: function (text : any, separator : any) {
 			var words = [];
 			if (this.isEmpty(text) == false && this.isEmpty(separator) == false && separator.length > 0) {
 				words.push(text);
 				for (var i = 0; i < separator.length; i++) {
-					var subWords = [];
+					var subWords : any = [];
 					for (var j = 0; j < words.length; j++) {
 						if (this.isEmpty(words[j]) == false && this.isEmpty(separator[i]) == false) {
 							subWords = subWords.concat(words[j].split(separator[i]));
@@ -121,7 +319,7 @@
 		/**
 		 * 객체 깊은 복사
 		 */
-		copyObject: function (obj) {
+		copyObject: function (obj : any) {
 			if (obj === null || typeof(obj) !== 'object') return obj;
 
 			try {
@@ -138,7 +336,7 @@
 
 		 */
 		getDateTime : function () {
-			return this.getDate()+ " " + this.getFullTime();
+			return  this.getDate(null) as String + " " + this.getFullTime(null);
 		},
 
 		/**
@@ -153,7 +351,7 @@
 		 *   separator(String)
 		 * }
 		 */
-		getDate: function (options) {
+		getDate: function (options : any) {
 
 			if (this.isEmpty(options) == true) {
 				options = {
@@ -257,7 +455,7 @@
 		 *   separator(String)
 		 * }
 		 */
-		getFullTime: function (options) {
+		getFullTime: function (options : any) {
 			if (this.isEmpty(options) == true) {
 				options = {
 					addHour: 0,
@@ -295,7 +493,7 @@
 		 *   separator(String)
 		 * }
 		 */
-		getTime: function (options) {
+		getTime: function (options : any) {
 			if (this.isEmpty(options) == true) {
 				options = {
 					addHour: 0,
@@ -328,7 +526,7 @@
 		 * ex) this.prefixZero(2, 5) => 00002, this.prefixZero(20, 5) => 00020
 		 *
 		 */
-		prefixZero: function (text, length) {
+		prefixZero: function (text : any, length : any) {
 			var zero = '';
 			var suffix = text;
 
@@ -348,7 +546,7 @@
 		/**
 		 * Date => text
 		 */
-		dateToText: function (date) {
+		dateToText: function (date : any) {
 			var d = new Date(date);
 			var yyyy = d.getFullYear();
 			var mm = d.getMonth() + 1;
@@ -366,7 +564,7 @@
 		 *
 		 * ex) getRandomInt(2, 5) => 2~5사이의 정수 난수 값 리턴
 		 */
-		getRandomInt: function (min, max) {
+		getRandomInt: function (min : any, max : any) {
 			min = Math.ceil(min);
 			max = Math.floor(max);
 			return Math.floor(Math.random() * (max - min)) + min;
@@ -398,7 +596,7 @@
 		 *
 		 * ex) 10000 => 10,000
 		 */
-		comma: function (text) {
+		comma: function (text : any) {
 			try {
 				return text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
 			} catch (e) {
@@ -415,7 +613,7 @@
 		 *
 		 * ex) 10,000 => 10000
 		 */
-		removeComma: function (text) {
+		removeComma: function (text : any) {
 			try {
 				return text.toString().replace(/,/g, "");;
 			} catch (e) {
@@ -430,7 +628,7 @@
 		/**
 		 * json 데이터 가지고 오기 (외부 JSON 파일 PATH or URL) (동기 요청)
 		 */
-		getJsonByPromise: function (url, isAsync) {
+		getJsonByPromise: function (url : any, isAsync : any) {
 			if (this.isEmpty(url) == true) {
 				new Error('COMMON_UTIL - getJson(url, isAsync) Error : url(parameter) is empty')
 			}
@@ -461,7 +659,7 @@
 		/**
 		 * json 데이터 가지고 오기 (동기 요청) (외부 JSON 파일 PATH or URL)
 		 */
-		getJsonBySync: function (url) {
+		getJsonBySync: function (url : any) {
 			var result = {};
 			if (this.isEmpty(url) == true) {
 				new Error('COMMON_UTIL - getJson(url, isAsync) Error : url(parameter) is empty')
@@ -486,16 +684,6 @@
 
 			return result;
 		},
-
-				/**
-		 * 이메일 정규식
-		 */
-				checkEmail: function (data) {
-					// 이메일 형식 검사
-				  let validateEmail = /^[A-Za-z0-9_\\.\\-]+@[A-Za-z0-9\\-]+\.[A-Za-z0-9\\-\\.]+$/;
-				  if (validateEmail.test(data) === true) return true;
-				  return false;
-			  },
 
 	}
 
 
client/views/AppFilters.ts (added)
+++ client/views/AppFilters.ts
@@ -0,0 +1,157 @@
+/***
+ *
+ * @param 기존 CMS
+ * @returns
+ */
+
+ const AppFilters = (function () {
+
+	var _filters = {
+		
+		/*전화번호*/
+		phone: function (phone : any) {
+			return phone.replace(/[^0-9]/g, '')
+						.replace(/(\d{3})(\d{4})(\d{4})/, '($1) $2-$3');
+		},
+
+		/* 반올림  */
+		math: function (param : any) {
+			if(param == 0){
+				return "-";
+			}
+			return Math.round(param);
+		},
+
+		/* 특수문자 변형 */
+		specialCharacter: function (arr : any) {
+			arr = arr.replace(/&lt;/g, '<');
+			arr = arr.replace(/&gt;/g, '>');
+			arr = arr.replace(/&quot;/g, '"');
+			arr = arr.replace(/&amp;/g, '&');
+			arr = arr.replace(/&#10;/g, '\n');
+			arr = arr.replace(/&#9;/g, '\t');
+			return arr;
+		},
+
+		/* input text 제한 */
+		comma: function (text : any) {
+			try {
+				return text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+			} catch (e) {
+				if (text === undefined || text === null || text === "" || text.length === 0) {
+					return "-";
+				} else {
+					return text;
+				}
+			}
+		},
+
+		/* html text 제한 */
+		textLimit: function (text : any, limit : any) {
+			if (text === undefined || text === null || text === "" || text.length === 0) {
+				return text;
+			} else {
+				return text.substr(0, limit);
+			}
+		},
+
+		/* 파일 사이즈  */
+		fileSize: function (size : any) {
+			var result = size + " bytes";
+			// optional code for multiples approximation
+			var type = ["KB", "MB", "GB"];
+			for (var i = 0, reSize = (size / 1024); reSize > 1 && i < type.length; reSize /= 1024, i++) {
+				result = reSize.toFixed(3) + " " + type[i];
+			}
+			return result;
+		},
+
+		/* 숫자 한글 표현  */
+		numberSize: function (number : any) {
+			var result = number;
+			// optional code for multiples approximation
+			var type = ["만", "억", "조", "경", "해"];
+			for (var i = 0, reSize = (number / 10000); reSize > 1 && i < type.length; reSize /= 10000, i++) {
+				result = reSize.toFixed(1) + " " + type[i];
+			}
+			if (result == null) {
+				return '0';
+			} else {
+				return result.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+			}
+			//return result;
+		},
+
+		isEmpty: function (data : any) {
+			if (data === undefined || data === null || data === "" || data.length === 0 || (data.constructor == Object && Object.keys(data).length === 0)) {
+				if ((typeof data) === "number") {
+					return false
+				} else {
+					return true;
+				}
+			} else {
+				return false;
+			}
+		},
+
+		/* 빈 데이터 표현 => '-'  */
+		emptyText: function (text : any) {
+			if (text === undefined || text === null || text === "" || text.length === 0) {
+				return '-';
+			} else {
+				return text;
+			}
+		},
+
+		//클라이언트의 현재 일짜와 비교해서 더 작은 지에 대한 여부 확인
+		//param - dateText : yyyy-mm-dd (String)
+		isSmallerClientDate: function (dateText : any) {
+			const date = new Date(dateText);
+			let clientDate = new Date();
+
+			let yyyy = clientDate.getFullYear();
+			let mm : any = clientDate.getMonth() + 1;
+			if (mm < 10) {
+				let MM : String;
+				MM = "0" +  mm;
+				mm=String(mm);
+			}
+			
+			let dd : any = clientDate.getDate();
+			if (dd < 10) {
+				dd = "0" + dd;
+			}
+			clientDate = new Date(`${yyyy}-${mm}-${dd}`);
+
+			if (date <= clientDate) {
+				return true;
+			} else {
+				return false;
+			}
+		},
+
+		/* 인하 수치 5mm */
+		DEFAULT_REPAIR_SIZE: -5,
+
+		/* 인하 횟수 => 인하 수치 */
+		repairMillimeter: function(repairCount : any) {
+			const newRepairCount = !repairCount ? 0 : Number(repairCount);
+			const repairSize = this.DEFAULT_REPAIR_SIZE * newRepairCount;
+			return repairSize;
+		}
+		
+	}
+
+
+	//초기화
+	function init() {
+        return _filters;
+    }
+
+
+	return init();
+
+})();
+
+export default AppFilters;
+
client/views/index.js
--- client/views/index.js
+++ client/views/index.js
@@ -1,17 +1,17 @@
-/**
- * @author : 최정우
- * @since : 2022.10.19
- * @dscription : Vue를 활용한 Client단 구현의 시작점(Index) Component 입니다.
- */
-import { createApp } from 'vue';
+// /**
+//  * @author : 최정우
+//  * @since : 2022.10.19
+//  * @dscription : Vue를 활용한 Client단 구현의 시작점(Index) Component 입니다.
+//  */
+// import { createApp } from 'vue';
 
-import AppRouter from './pages/AppRouter.js';
-import App from './pages/App.vue';
-import AppStore from './pages/AppStore.js';
+// import AppRouter from './pages/AppRouter.js';
+// import App from './pages/App.vue';
+// import AppStore from './pages/AppStore.js';
 
-const vue = createApp(App).use(AppRouter).use(AppStore).mount('#root');
+// const vue = createApp(App).use(AppRouter).use(AppStore).mount('#root');
 
-if (!APP_USER_HTTP_REQUEST_URL && APP_USER_HTTP_REQUEST_URL != '/') {
-    console.log('index.js APP_USER_HTTP_REQUEST_URL : ', APP_USER_HTTP_REQUEST_URL);
-    AppRouter.push({ path: APP_USER_HTTP_REQUEST_URL, query: {}})
-}
+// if (!APP_USER_HTTP_REQUEST_URL && APP_USER_HTTP_REQUEST_URL != '/') {
+//     console.log('index.js APP_USER_HTTP_REQUEST_URL : ', APP_USER_HTTP_REQUEST_URL);
+//     AppRouter.push({ path: APP_USER_HTTP_REQUEST_URL, query: {}})
+// }
 
client/views/index.ts (added)
+++ client/views/index.ts
@@ -0,0 +1,22 @@
+/**
+ * @author : 최정우
+ * @since : 2022.10.19
+ * @dscription : Vue를 활용한 Client단 구현의 시작점(Index) Component 입니다.
+ */
+import { createApp } from 'vue';
+
+import AppRouter from './pages/AppRouter';
+import AppStore from './pages/AppStore';
+import AppFilters from './AppFilters';
+import App from './pages/App.vue';
+
+
+// const vue = createApp(App).use(AppRouter).mount('#root');
+const vue = createApp(App)
+vue.use(AppStore);
+vue.use(AppRouter);
+vue.config.globalProperties.$filters = AppFilters
+
+
+vue.mount('#root');
+
client/views/pages/admin/login/Login.vue
--- client/views/pages/admin/login/Login.vue
+++ client/views/pages/admin/login/Login.vue
@@ -4,7 +4,7 @@
   
 <script>
 import axios from 'axios';
-import common from '../../../../resources/js/commonUtil.js';
+
 
 export default {
     data() {
client/views/pages/admin/main/Main.vue
--- client/views/pages/admin/main/Main.vue
+++ client/views/pages/admin/main/Main.vue
@@ -4,7 +4,7 @@
 
 <script>
 import axios from "axios";
-import common from '../../../../resources/js/commonUtil.js';
+
 
 export default {
     data() {
client/views/pages/admin/user/UserSelectList.vue
--- client/views/pages/admin/user/UserSelectList.vue
+++ client/views/pages/admin/user/UserSelectList.vue
@@ -66,7 +66,7 @@
                                 <tr v-for="(mngr, index) in mngrList" :key="index">
                                     <td>{{ mngr.mngr_id }}</td>
                                     <td>{{ mngr.mngr_nm }}</td>
-                                    <td>{{ mngr.mngr_eml}}</td>
+                                    <td>{{ mngr.mngr_eml }}</td>
                                     <td>{{ mngr.rgtr_id }}</td>
                                     <td>{{ mngr.reg_dt }}</td>
                                 </tr>
@@ -126,7 +126,7 @@
 import { useStore } from "vuex";
 import axios from "axios";
 import crypto from "crypto-js";
-import COMMON_UTIL from '../../../../resources/js/commonUtil.js';
+import COMMON_UTIL from '../../../../resources/js/commonUtil.ts';
 
 
 export default {
@@ -231,7 +231,7 @@
         },
         // 등록버튼 클릭 시 빈칸 검사
         managerInsertCheck: function () {
-            if (COMMON_UTIL.isEmpty(this.mngr.mngr_id)) {
+            if (COMMON_UTIL.isEmpty(this.mngr.mngr_id) === false) {
                 alert('ID를 입력해주세요.');
                 return false;
             }
@@ -241,27 +241,28 @@
                 return false;
             }
 
-            if (COMMON_UTIL.isEmpty(this.mngr.mngr_pw)) {
+            if (COMMON_UTIL.isEmpty(this.mngr.mngr_pw) === false) {
                 alert('비밀번호를 입력해주세요.');
                 return false;
             }
 
-            if (COMMON_UTIL.isEmpty(this.mngr.mngr_nm)) {
+            if (COMMON_UTIL.isEmpty(this.mngr.mngr_nm) === false) {
                 alert('이름을 입력해주세요.');
                 return false;
             }
 
 
-            if (COMMON_UTIL.isEmpty(this.email_id)) {
+            if (COMMON_UTIL.isEmpty(this.email_id) === false) {
                 alert('이메일 ID를 입력해주세요.');
                 return false;
             }
 
-            if (COMMON_UTIL.isEmpty(this.email_domain)) {
+            if (COMMON_UTIL.isEmpty(this.email_domain) === false) {
                 alert('이메일을 선택 혹은 입력해주세요.');
                 return false;
             }
-            return true;
+
+            return true
         },
 
         //사용자 등록
 
client/views/vue-shim.d.ts (added)
+++ client/views/vue-shim.d.ts
@@ -0,0 +1,5 @@
+declare module '*.vue' {
+    import type { DefineComponent } from 'vue'
+    const component: DefineComponent<{}, {}, any>
+    export default component
+}(No newline at end of file)
package-lock.json
--- package-lock.json
+++ package-lock.json
@@ -1,5 +1,5 @@
 {
-  "name": "KERIS",
+  "name": "KERIS-1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
@@ -41,7 +41,10 @@
       },
       "devDependencies": {
         "less": "^4.2.0",
-        "less-loader": "^11.1.3"
+        "less-loader": "^11.1.3",
+        "ts-loader": "^9.5.0",
+        "ts-node": "^10.9.1",
+        "typescript": "^5.2.2"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -351,6 +354,28 @@
         "node": ">=6.9.0"
       }
     },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.8.1",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+      "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "0.3.9"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+      "version": "0.3.9",
+      "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+      "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/resolve-uri": "^3.0.3",
+        "@jridgewell/sourcemap-codec": "^1.4.10"
+      }
+    },
     "node_modules/@discoveryjs/json-ext": {
       "version": "0.5.7",
       "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
@@ -482,6 +507,30 @@
       "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
       "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==",
       "optional": true
+    },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
+      "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+      "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+      "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+      "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+      "dev": true
     },
     "node_modules/@types/eslint": {
       "version": "8.44.4",
@@ -831,6 +880,15 @@
         "acorn": "^8"
       }
     },
+    "node_modules/acorn-walk": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz",
+      "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
     "node_modules/ajv": {
       "version": "6.12.6",
       "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -877,6 +935,12 @@
       "engines": {
         "node": ">= 8"
       }
+    },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true
     },
     "node_modules/array-flatten": {
       "version": "1.1.1",
@@ -1001,7 +1065,7 @@
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
       "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
-      "optional": true,
+      "devOptional": true,
       "dependencies": {
         "fill-range": "^7.0.1"
       },
@@ -1249,6 +1313,12 @@
         "url": "https://github.com/sponsors/mesqueeb"
       }
     },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true
+    },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -1391,6 +1461,15 @@
       "engines": {
         "node": ">= 0.8",
         "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.3.1"
       }
     },
     "node_modules/dom-walk": {
@@ -1684,7 +1763,7 @@
       "version": "7.0.1",
       "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
       "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
-      "optional": true,
+      "devOptional": true,
       "dependencies": {
         "to-regex-range": "^5.0.1"
       },
@@ -2141,7 +2220,7 @@
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
       "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
-      "optional": true,
+      "devOptional": true,
       "engines": {
         "node": ">=0.12.0"
       }
@@ -2373,6 +2452,12 @@
         "semver": "bin/semver"
       }
     },
+    "node_modules/make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true
+    },
     "node_modules/media-typer": {
       "version": "0.3.0",
       "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@@ -2397,6 +2482,19 @@
       "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
       "engines": {
         "node": ">= 0.6"
+      }
+    },
+    "node_modules/micromatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+      "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+      "dev": true,
+      "dependencies": {
+        "braces": "^3.0.2",
+        "picomatch": "^2.3.1"
+      },
+      "engines": {
+        "node": ">=8.6"
       }
     },
     "node_modules/mime": {
@@ -2750,7 +2848,7 @@
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
       "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
-      "optional": true,
+      "devOptional": true,
       "engines": {
         "node": ">=8.6"
       },
@@ -3420,7 +3518,7 @@
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
       "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
-      "optional": true,
+      "devOptional": true,
       "dependencies": {
         "is-number": "^7.0.0"
       },
@@ -3434,6 +3532,181 @@
       "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
       "engines": {
         "node": ">=0.6"
+      }
+    },
+    "node_modules/ts-loader": {
+      "version": "9.5.0",
+      "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz",
+      "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==",
+      "dev": true,
+      "dependencies": {
+        "chalk": "^4.1.0",
+        "enhanced-resolve": "^5.0.0",
+        "micromatch": "^4.0.0",
+        "semver": "^7.3.4",
+        "source-map": "^0.7.4"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "typescript": "*",
+        "webpack": "^5.0.0"
+      }
+    },
+    "node_modules/ts-loader/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/ts-loader/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/ts-loader/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/ts-loader/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/ts-loader/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ts-loader/node_modules/lru-cache": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+      "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+      "dev": true,
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/ts-loader/node_modules/semver": {
+      "version": "7.5.4",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
+      "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
+      "dev": true,
+      "dependencies": {
+        "lru-cache": "^6.0.0"
+      },
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/ts-loader/node_modules/source-map": {
+      "version": "0.7.4",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
+      "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/ts-loader/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ts-loader/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+      "dev": true
+    },
+    "node_modules/ts-node": {
+      "version": "10.9.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
+      "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
+      "dev": true,
+      "dependencies": {
+        "@cspotcode/source-map-support": "^0.8.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.1",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-esm": "dist/bin-esm.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
       }
     },
     "node_modules/tslib": {
@@ -3463,6 +3736,19 @@
       },
       "engines": {
         "node": ">= 0.6"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.2.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
+      "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
+      "dev": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
       }
     },
     "node_modules/undici-types": {
@@ -3570,6 +3856,12 @@
       "engines": {
         "node": ">= 0.4.0"
       }
+    },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+      "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+      "dev": true
     },
     "node_modules/vary": {
       "version": "1.1.2",
@@ -3997,6 +4289,15 @@
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+    },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
     }
   }
 }
package.json
--- package.json
+++ package.json
@@ -46,6 +46,9 @@
   },
   "devDependencies": {
     "less": "^4.2.0",
-    "less-loader": "^11.1.3"
+    "less-loader": "^11.1.3",
+    "ts-loader": "^9.5.0",
+    "ts-node": "^10.9.1",
+    "typescript": "^5.2.2"
   }
 }
 
tsconfig.json (added)
+++ tsconfig.json
@@ -0,0 +1,22 @@
+{
+    "compilerOptions": {
+        /* "composite": true,
+        "target": "esnext",
+        "module": "esnext",
+        "strict": true,
+        "jsx": "preserve",
+        "moduleResolution": "node",
+        "allowImportingTsExtensions": true,
+        "emitDeclarationOnly": true */
+        /* "outDir": "./client/build/", */
+        "outDir": "./built/",
+        "strict": true,
+        "noImplicitAny": true,
+        "module": "es6",
+        "target": "es5",
+        "jsx": "preserve",
+        "allowJs": true,
+        "moduleResolution": "node",
+        
+    }
+}(No newline at end of file)
webpack.config.js
--- webpack.config.js
+++ webpack.config.js
@@ -1,6 +1,6 @@
 const { VueLoaderPlugin } = require("vue-loader");
 
-const {PROJECT_NAME, BASE_DIR, SERVICE_STATUS} = require('./Global');
+const {PROJECT_NAME, BASE_DIR, SERVICE_STATUS} = require('./Global.js');
 
 module.exports = {
   name: PROJECT_NAME,
@@ -8,7 +8,7 @@
   devtool: 'eval',
 
   entry: {
-    app: [`${BASE_DIR}/client/views/index.js`]
+    app: [`${BASE_DIR}/client/views/index.ts`]
   },
 
   module: {
@@ -18,6 +18,9 @@
     }, {
       test: /\.(js|jsx)?$/,
       loader: 'babel-loader',
+      options: {
+        compact: true,
+      },
     }, {
       test: /\.css$/,
       use: ['vue-style-loader', 'css-loader']
@@ -30,11 +33,22 @@
           fallback:require.resolve('file-loader')
         }
       }]
+    },{
+      test: /\.(ts|tsx)$/,
+      exclude: /node_modules/,
+      loader: 'ts-loader',
+      options: { appendTsSuffixTo: [/\.vue$/] }
     }],
   },
 
   plugins: [new VueLoaderPlugin()],
 
+  resolve: {
+    //확장자를 순서대로 해석, 여러 파일에서 이름이 동일하지만 다른 확장자를 가진 경우, webpack은 배열의 앞에서부터 파일을 해석하고 남은 것은 해석하지 않음
+    //import 시, 확장자 생략 가능
+    extensions: [ '.tsx', '.ts', '.jsx', '.js', '.vue', 'json' ],
+  },
+
   output: {
     path: `${BASE_DIR}/client/build`,	// __dirname: webpack.config.js 파일이 위치한 경로
     filename: 'bundle.js'
Add a comment
List