viewlogic 1.1.3 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1482,6 +1482,685 @@ var QueryManager = class {
1482
1482
  }
1483
1483
  };
1484
1484
 
1485
+ // src/core/FormHandler.js
1486
+ var FormHandler = class {
1487
+ constructor(router, options = {}) {
1488
+ this.router = router;
1489
+ this.config = {
1490
+ debug: options.debug || false,
1491
+ requestTimeout: options.requestTimeout || 3e4,
1492
+ ...options
1493
+ };
1494
+ this.log("debug", "FormHandler initialized");
1495
+ }
1496
+ /**
1497
+ * 로깅 래퍼 메서드
1498
+ */
1499
+ log(level, ...args) {
1500
+ if (this.router?.errorHandler) {
1501
+ this.router.errorHandler.log(level, "FormHandler", ...args);
1502
+ }
1503
+ }
1504
+ /**
1505
+ * 중복 요청 체크
1506
+ */
1507
+ isDuplicateRequest(form) {
1508
+ if (form._isSubmitting) {
1509
+ this.log("debug", "Duplicate request blocked");
1510
+ return true;
1511
+ }
1512
+ return false;
1513
+ }
1514
+ /**
1515
+ * 폼 제출 시작
1516
+ */
1517
+ startFormSubmission(form) {
1518
+ form._isSubmitting = true;
1519
+ form._abortController = new AbortController();
1520
+ form._timeoutId = setTimeout(() => {
1521
+ if (form._isSubmitting) {
1522
+ this.abortFormSubmission(form);
1523
+ }
1524
+ }, this.config.requestTimeout);
1525
+ }
1526
+ /**
1527
+ * 폼 제출 완료
1528
+ */
1529
+ finishFormSubmission(form) {
1530
+ form._isSubmitting = false;
1531
+ if (form._timeoutId) {
1532
+ clearTimeout(form._timeoutId);
1533
+ delete form._timeoutId;
1534
+ }
1535
+ delete form._abortController;
1536
+ }
1537
+ /**
1538
+ * 폼 제출 중단
1539
+ */
1540
+ abortFormSubmission(form) {
1541
+ if (form._abortController) {
1542
+ form._abortController.abort();
1543
+ }
1544
+ this.finishFormSubmission(form);
1545
+ }
1546
+ /**
1547
+ * 자동 폼 바인딩
1548
+ */
1549
+ bindAutoForms(component) {
1550
+ const forms = document.querySelectorAll("form.auto-form, form[action]");
1551
+ forms.forEach((form) => {
1552
+ form.removeEventListener("submit", form._boundSubmitHandler);
1553
+ const boundHandler = (e) => this.handleFormSubmit(e, component);
1554
+ form._boundSubmitHandler = boundHandler;
1555
+ form.addEventListener("submit", boundHandler);
1556
+ this.log("debug", `Form auto-bound: ${form.getAttribute("action")}`);
1557
+ });
1558
+ }
1559
+ /**
1560
+ * 폼 서브밋 핸들러
1561
+ */
1562
+ async handleFormSubmit(event, component) {
1563
+ event.preventDefault();
1564
+ const form = event.target;
1565
+ let action = form.getAttribute("action");
1566
+ const method = form.getAttribute("method") || "POST";
1567
+ const successHandler = form.getAttribute("data-success-handler");
1568
+ const errorHandler = form.getAttribute("data-error-handler");
1569
+ const loadingHandler = form.getAttribute("data-loading-handler");
1570
+ const redirectTo = form.getAttribute("data-redirect");
1571
+ action = this.processActionParams(action, component);
1572
+ if (!this.validateForm(form, component)) {
1573
+ return;
1574
+ }
1575
+ if (this.isDuplicateRequest(form)) {
1576
+ return;
1577
+ }
1578
+ this.startFormSubmission(form);
1579
+ const formData = new FormData(form);
1580
+ const data = Object.fromEntries(formData.entries());
1581
+ try {
1582
+ if (loadingHandler && component[loadingHandler]) {
1583
+ component[loadingHandler](true, form);
1584
+ }
1585
+ this.log("debug", `Form submitting to: ${action}`, data);
1586
+ const response = await this.submitFormData(action, method, data, form, component, form._abortController.signal);
1587
+ if (successHandler && component[successHandler]) {
1588
+ component[successHandler](response, form);
1589
+ }
1590
+ this.finishFormSubmission(form);
1591
+ if (redirectTo) {
1592
+ requestAnimationFrame(() => {
1593
+ setTimeout(() => {
1594
+ component.navigateTo(redirectTo);
1595
+ }, 1e3);
1596
+ });
1597
+ }
1598
+ } catch (error) {
1599
+ if (error.name === "AbortError") {
1600
+ this.log("debug", "Form submission aborted");
1601
+ return;
1602
+ }
1603
+ this.log("warn", "Form submission error:", error);
1604
+ this.finishFormSubmission(form);
1605
+ if (errorHandler && component[errorHandler]) {
1606
+ component[errorHandler](error, form);
1607
+ } else {
1608
+ console.error("Form submission error:", error);
1609
+ }
1610
+ } finally {
1611
+ if (loadingHandler && component[loadingHandler]) {
1612
+ component[loadingHandler](false, form);
1613
+ }
1614
+ }
1615
+ }
1616
+ /**
1617
+ * 액션 파라미터 처리 (ApiHandler 재사용)
1618
+ */
1619
+ processActionParams(actionTemplate, component) {
1620
+ return this.router.routeLoader.apiHandler.processURLParameters(actionTemplate, component);
1621
+ }
1622
+ /**
1623
+ * 폼 데이터 서브밋 (ApiHandler 활용)
1624
+ */
1625
+ async submitFormData(action, method, data, form, component, signal = null) {
1626
+ const hasFile = Array.from(form.elements).some((el) => el.type === "file" && el.files.length > 0);
1627
+ const options = {
1628
+ method: method.toUpperCase(),
1629
+ headers: {},
1630
+ signal
1631
+ // AbortController 신호 추가
1632
+ };
1633
+ if (hasFile) {
1634
+ options.data = new FormData(form);
1635
+ } else {
1636
+ options.data = data;
1637
+ options.headers["Content-Type"] = "application/json";
1638
+ }
1639
+ return await this.router.routeLoader.apiHandler.fetchData(action, component, options);
1640
+ }
1641
+ /**
1642
+ * 클라이언트 사이드 폼 검증
1643
+ */
1644
+ validateForm(form, component) {
1645
+ let isValid = true;
1646
+ const inputs = form.querySelectorAll("input, textarea, select");
1647
+ inputs.forEach((input) => {
1648
+ if (!input.checkValidity()) {
1649
+ isValid = false;
1650
+ input.classList.add("error");
1651
+ return;
1652
+ }
1653
+ const validationFunction = input.getAttribute("data-validation");
1654
+ if (validationFunction) {
1655
+ const isInputValid = this.validateInput(input, validationFunction, component);
1656
+ if (!isInputValid) {
1657
+ isValid = false;
1658
+ input.classList.add("error");
1659
+ } else {
1660
+ input.classList.remove("error");
1661
+ }
1662
+ } else {
1663
+ input.classList.remove("error");
1664
+ }
1665
+ });
1666
+ return isValid;
1667
+ }
1668
+ /**
1669
+ * 개별 입력 검증
1670
+ */
1671
+ validateInput(input, validationFunction, component) {
1672
+ const value = input.value;
1673
+ if (typeof component[validationFunction] === "function") {
1674
+ try {
1675
+ return component[validationFunction](value, input);
1676
+ } catch (error) {
1677
+ this.log("warn", `Validation function '${validationFunction}' error:`, error);
1678
+ return false;
1679
+ }
1680
+ }
1681
+ this.log("warn", `Validation function '${validationFunction}' not found`);
1682
+ return true;
1683
+ }
1684
+ /**
1685
+ * 모든 폼 요청 취소
1686
+ */
1687
+ cancelAllRequests() {
1688
+ const forms = document.querySelectorAll("form");
1689
+ forms.forEach((form) => {
1690
+ if (form._isSubmitting) {
1691
+ this.abortFormSubmission(form);
1692
+ }
1693
+ });
1694
+ }
1695
+ /**
1696
+ * 정리 (메모리 누수 방지)
1697
+ */
1698
+ destroy() {
1699
+ this.cancelAllRequests();
1700
+ const forms = document.querySelectorAll("form.auto-form, form[action]");
1701
+ forms.forEach((form) => {
1702
+ if (form._boundSubmitHandler) {
1703
+ form.removeEventListener("submit", form._boundSubmitHandler);
1704
+ delete form._boundSubmitHandler;
1705
+ }
1706
+ this.cleanupFormState(form);
1707
+ });
1708
+ this.log("debug", "FormHandler destroyed");
1709
+ this.router = null;
1710
+ }
1711
+ /**
1712
+ * 폼 상태 정리
1713
+ */
1714
+ cleanupFormState(form) {
1715
+ delete form._isSubmitting;
1716
+ delete form._abortController;
1717
+ if (form._timeoutId) {
1718
+ clearTimeout(form._timeoutId);
1719
+ delete form._timeoutId;
1720
+ }
1721
+ }
1722
+ };
1723
+
1724
+ // src/core/ApiHandler.js
1725
+ var ApiHandler = class {
1726
+ constructor(router, options = {}) {
1727
+ this.router = router;
1728
+ this.config = {
1729
+ debug: options.debug || false,
1730
+ timeout: options.timeout || 1e4,
1731
+ retries: options.retries || 1,
1732
+ ...options
1733
+ };
1734
+ this.log("debug", "ApiHandler initialized");
1735
+ }
1736
+ /**
1737
+ * 로깅 래퍼 메서드
1738
+ */
1739
+ log(level, ...args) {
1740
+ if (this.router?.errorHandler) {
1741
+ this.router.errorHandler.log(level, "ApiHandler", ...args);
1742
+ }
1743
+ }
1744
+ /**
1745
+ * 컴포넌트 데이터 가져오기 (파라미터 치환 지원)
1746
+ */
1747
+ async fetchData(dataURL, component = null, options = {}) {
1748
+ try {
1749
+ let processedURL = this.processURLParameters(dataURL, component);
1750
+ const queryString = this.router.queryManager?.buildQueryString(this.router.queryManager?.getQueryParams()) || "";
1751
+ const fullURL = queryString ? `${processedURL}?${queryString}` : processedURL;
1752
+ this.log("debug", `Fetching data from: ${fullURL}`);
1753
+ const requestOptions = {
1754
+ method: options.method || "GET",
1755
+ headers: {
1756
+ "Content-Type": "application/json",
1757
+ "Accept": "application/json",
1758
+ ...options.headers
1759
+ },
1760
+ ...options
1761
+ };
1762
+ if (component?.$getToken && component.$getToken()) {
1763
+ requestOptions.headers["Authorization"] = `Bearer ${component.$getToken()}`;
1764
+ }
1765
+ if (options.data && ["POST", "PUT", "PATCH"].includes(requestOptions.method.toUpperCase())) {
1766
+ if (options.data instanceof FormData) {
1767
+ requestOptions.body = options.data;
1768
+ delete requestOptions.headers["Content-Type"];
1769
+ } else {
1770
+ requestOptions.body = JSON.stringify(options.data);
1771
+ }
1772
+ }
1773
+ const response = await fetch(fullURL, requestOptions);
1774
+ if (!response.ok) {
1775
+ let error;
1776
+ try {
1777
+ error = await response.json();
1778
+ } catch (e) {
1779
+ error = { message: `HTTP ${response.status}: ${response.statusText}` };
1780
+ }
1781
+ throw new Error(error.message || `HTTP ${response.status}`);
1782
+ }
1783
+ try {
1784
+ const data = await response.json();
1785
+ if (typeof data !== "object" || data === null) {
1786
+ throw new Error("Invalid data format: expected object");
1787
+ }
1788
+ return data;
1789
+ } catch (e) {
1790
+ return { success: true };
1791
+ }
1792
+ } catch (error) {
1793
+ this.log("error", "Failed to fetch data:", error);
1794
+ throw error;
1795
+ }
1796
+ }
1797
+ /**
1798
+ * 여러 API 엔드포인트에서 데이터 가져오기
1799
+ */
1800
+ async fetchMultipleData(dataConfig, component = null) {
1801
+ if (!dataConfig || typeof dataConfig !== "object") {
1802
+ return {};
1803
+ }
1804
+ const results = {};
1805
+ const errors = {};
1806
+ const promises = Object.entries(dataConfig).map(async ([key, config]) => {
1807
+ try {
1808
+ let url, options = {};
1809
+ if (typeof config === "string") {
1810
+ url = config;
1811
+ } else if (typeof config === "object") {
1812
+ url = config.url;
1813
+ options = { ...config };
1814
+ delete options.url;
1815
+ }
1816
+ if (url) {
1817
+ const data = await this.fetchData(url, component, options);
1818
+ results[key] = data;
1819
+ }
1820
+ } catch (error) {
1821
+ errors[key] = error;
1822
+ this.log("warn", `Failed to fetch data for '${key}':`, error);
1823
+ }
1824
+ });
1825
+ await Promise.all(promises);
1826
+ return { results, errors };
1827
+ }
1828
+ /**
1829
+ * URL에서 파라미터 치환 처리 ({param} 형식)
1830
+ */
1831
+ processURLParameters(url, component = null) {
1832
+ if (!url || typeof url !== "string") return url;
1833
+ let processedURL = url;
1834
+ const paramMatches = url.match(/\{([^}]+)\}/g);
1835
+ if (paramMatches && component) {
1836
+ paramMatches.forEach((match) => {
1837
+ const paramName = match.slice(1, -1);
1838
+ try {
1839
+ let paramValue = null;
1840
+ if (component.getParam) {
1841
+ paramValue = component.getParam(paramName);
1842
+ }
1843
+ if (paramValue === null || paramValue === void 0) {
1844
+ paramValue = component[paramName];
1845
+ }
1846
+ if (paramValue === null || paramValue === void 0) {
1847
+ if (component.$options?.computed?.[paramName]) {
1848
+ paramValue = component[paramName];
1849
+ }
1850
+ }
1851
+ if (paramValue === null || paramValue === void 0) {
1852
+ paramValue = this.router.queryManager?.getParam(paramName);
1853
+ }
1854
+ if (paramValue !== null && paramValue !== void 0) {
1855
+ processedURL = processedURL.replace(
1856
+ match,
1857
+ encodeURIComponent(paramValue)
1858
+ );
1859
+ this.log("debug", `URL parameter resolved: ${paramName} = ${paramValue}`);
1860
+ } else {
1861
+ this.log("warn", `URL parameter '${paramName}' not found, keeping original: ${match}`);
1862
+ }
1863
+ } catch (error) {
1864
+ this.log("warn", `Error processing URL parameter '${paramName}':`, error);
1865
+ }
1866
+ });
1867
+ }
1868
+ return processedURL;
1869
+ }
1870
+ /**
1871
+ * HTTP 메서드별 헬퍼 함수들
1872
+ */
1873
+ async get(url, component = null, options = {}) {
1874
+ return this.fetchData(url, component, { ...options, method: "GET" });
1875
+ }
1876
+ async post(url, data, component = null, options = {}) {
1877
+ return this.fetchData(url, component, { ...options, method: "POST", data });
1878
+ }
1879
+ async put(url, data, component = null, options = {}) {
1880
+ return this.fetchData(url, component, { ...options, method: "PUT", data });
1881
+ }
1882
+ async patch(url, data, component = null, options = {}) {
1883
+ return this.fetchData(url, component, { ...options, method: "PATCH", data });
1884
+ }
1885
+ async delete(url, component = null, options = {}) {
1886
+ return this.fetchData(url, component, { ...options, method: "DELETE" });
1887
+ }
1888
+ /**
1889
+ * 정리 (메모리 누수 방지)
1890
+ */
1891
+ destroy() {
1892
+ this.log("debug", "ApiHandler destroyed");
1893
+ this.router = null;
1894
+ }
1895
+ };
1896
+
1897
+ // src/core/ComponentLoader.js
1898
+ var ComponentLoader = class {
1899
+ constructor(router = null, options = {}) {
1900
+ this.config = {
1901
+ componentsPath: options.componentsPath || "/components",
1902
+ // srcPath 기준 상대 경로
1903
+ debug: options.debug || false,
1904
+ environment: options.environment || "development",
1905
+ ...options
1906
+ };
1907
+ this.router = router;
1908
+ this.loadingPromises = /* @__PURE__ */ new Map();
1909
+ this.unifiedComponents = null;
1910
+ }
1911
+ /**
1912
+ * 로깅 래퍼 메서드
1913
+ */
1914
+ log(level, ...args) {
1915
+ if (this.router?.errorHandler) {
1916
+ this.router.errorHandler.log(level, "ComponentLoader", ...args);
1917
+ }
1918
+ }
1919
+ /**
1920
+ * 컴포넌트를 비동기로 로드 (캐시 지원)
1921
+ */
1922
+ async loadComponent(componentName) {
1923
+ if (!componentName || typeof componentName !== "string") {
1924
+ throw new Error("Component name must be a non-empty string");
1925
+ }
1926
+ const cacheKey = `component_${componentName}`;
1927
+ const cachedComponent = this.router?.cacheManager?.getFromCache(cacheKey);
1928
+ if (cachedComponent) {
1929
+ this.log("debug", `Component '${componentName}' loaded from cache`);
1930
+ return cachedComponent;
1931
+ }
1932
+ if (this.loadingPromises.has(componentName)) {
1933
+ return this.loadingPromises.get(componentName);
1934
+ }
1935
+ const loadPromise = this._loadComponentFromFile(componentName);
1936
+ this.loadingPromises.set(componentName, loadPromise);
1937
+ try {
1938
+ const component = await loadPromise;
1939
+ if (component && this.router?.cacheManager) {
1940
+ this.router.cacheManager.setCache(cacheKey, component);
1941
+ this.log("debug", `Component '${componentName}' cached successfully`);
1942
+ }
1943
+ return component;
1944
+ } catch (error) {
1945
+ throw error;
1946
+ } finally {
1947
+ this.loadingPromises.delete(componentName);
1948
+ }
1949
+ }
1950
+ /**
1951
+ * 파일에서 컴포넌트 로드
1952
+ */
1953
+ async _loadComponentFromFile(componentName) {
1954
+ const componentRelativePath = `${this.config.componentsPath}/${componentName}.js`;
1955
+ let componentPath;
1956
+ if (this.router && this.router.config.srcPath) {
1957
+ const srcPath = this.router.config.srcPath;
1958
+ if (srcPath.startsWith("http")) {
1959
+ const cleanSrcPath = srcPath.endsWith("/") ? srcPath.slice(0, -1) : srcPath;
1960
+ const cleanComponentPath = componentRelativePath.startsWith("/") ? componentRelativePath : `/${componentRelativePath}`;
1961
+ componentPath = `${cleanSrcPath}${cleanComponentPath}`;
1962
+ } else {
1963
+ componentPath = this.router.resolvePath(`${srcPath}${componentRelativePath}`);
1964
+ }
1965
+ } else {
1966
+ componentPath = this.router ? this.router.resolvePath(`/src${componentRelativePath}`) : `/src${componentRelativePath}`;
1967
+ }
1968
+ try {
1969
+ const module = await import(componentPath);
1970
+ const component = module.default;
1971
+ if (!component) {
1972
+ throw new Error(`Component '${componentName}' has no default export`);
1973
+ }
1974
+ if (!component.name) {
1975
+ component.name = componentName;
1976
+ }
1977
+ this.log("debug", `Component '${componentName}' loaded successfully`);
1978
+ return component;
1979
+ } catch (error) {
1980
+ this.log("error", `Failed to load component '${componentName}':`, error);
1981
+ throw new Error(`Component '${componentName}' not found: ${error.message}`);
1982
+ }
1983
+ }
1984
+ /**
1985
+ * 컴포넌트 모듈 클리어
1986
+ */
1987
+ clearComponents() {
1988
+ this.loadingPromises.clear();
1989
+ this.unifiedComponents = null;
1990
+ this.log("debug", "All components cleared");
1991
+ }
1992
+ /**
1993
+ * 환경에 따른 모든 컴포넌트 로딩 (캐싱 지원)
1994
+ */
1995
+ async loadAllComponents(componentNames = null) {
1996
+ let components;
1997
+ if (this.config.environment === "production") {
1998
+ if (this.unifiedComponents) {
1999
+ this.log("debug", "Using existing unified components");
2000
+ return this.unifiedComponents;
2001
+ }
2002
+ components = await this._loadProductionComponents();
2003
+ } else {
2004
+ components = await this._loadDevelopmentComponents(componentNames);
2005
+ }
2006
+ return components;
2007
+ }
2008
+ /**
2009
+ * 운영 모드: 통합 컴포넌트 로딩
2010
+ */
2011
+ async _loadProductionComponents() {
2012
+ try {
2013
+ const componentsPath = `${this.router?.config?.routesPath || "/routes"}/_components.js`;
2014
+ this.log("info", "[PRODUCTION] Loading unified components from:", componentsPath);
2015
+ const componentsModule = await import(componentsPath);
2016
+ if (typeof componentsModule.registerComponents === "function") {
2017
+ this.unifiedComponents = componentsModule.components || {};
2018
+ this.log("info", `[PRODUCTION] Unified components loaded: ${Object.keys(this.unifiedComponents).length} components`);
2019
+ return this.unifiedComponents;
2020
+ } else {
2021
+ throw new Error("registerComponents function not found in components module");
2022
+ }
2023
+ } catch (error) {
2024
+ this.log("warn", "[PRODUCTION] Failed to load unified components:", error.message);
2025
+ this.unifiedComponents = {};
2026
+ return {};
2027
+ }
2028
+ }
2029
+ /**
2030
+ * 개발 모드: 개별 컴포넌트 로딩
2031
+ */
2032
+ async _loadDevelopmentComponents(componentNames = null) {
2033
+ const namesToLoad = componentNames || [];
2034
+ const components = {};
2035
+ if (namesToLoad.length === 0) {
2036
+ this.log("info", "[DEVELOPMENT] No components to load");
2037
+ return components;
2038
+ }
2039
+ this.log("info", `[DEVELOPMENT] Loading individual components: ${namesToLoad.join(", ")}`);
2040
+ for (const name of namesToLoad) {
2041
+ try {
2042
+ const component = await this.loadComponent(name);
2043
+ if (component) {
2044
+ components[name] = component;
2045
+ }
2046
+ } catch (loadError) {
2047
+ this.log("warn", `[DEVELOPMENT] Failed to load component ${name}:`, loadError.message);
2048
+ }
2049
+ }
2050
+ this.log("info", `[DEVELOPMENT] Individual components loaded: ${Object.keys(components).length} components`);
2051
+ return components;
2052
+ }
2053
+ /**
2054
+ * 템플릿과 레이아웃에서 사용된 컴포넌트 추출
2055
+ */
2056
+ getComponentNames(template, layout = null, layoutName = null) {
2057
+ const componentSet = layout ? this._getLayoutComponents(layout, layoutName) : /* @__PURE__ */ new Set();
2058
+ if (template) {
2059
+ this._extractComponentsFromContent(template, componentSet);
2060
+ }
2061
+ const components = Array.from(componentSet);
2062
+ this.log("debug", `Discovered ${components.length} components:`, components);
2063
+ return components;
2064
+ }
2065
+ /**
2066
+ * 레이아웃에서 컴포넌트 추출 (캐시 활용)
2067
+ */
2068
+ _getLayoutComponents(layout, layoutName) {
2069
+ if (!layout || typeof layout !== "string") return /* @__PURE__ */ new Set();
2070
+ if (!layoutName || typeof layoutName !== "string") return /* @__PURE__ */ new Set();
2071
+ const cacheKey = `layout_components_${layoutName}`;
2072
+ const cachedComponents = this.router?.cacheManager?.getFromCache(cacheKey);
2073
+ if (cachedComponents) {
2074
+ this.log("debug", `Using cached layout components for '${layoutName}'`);
2075
+ return cachedComponents;
2076
+ }
2077
+ const componentSet = /* @__PURE__ */ new Set();
2078
+ this._extractComponentsFromContent(layout, componentSet);
2079
+ if (this.router?.cacheManager) {
2080
+ this.router.cacheManager.setCache(cacheKey, componentSet);
2081
+ this.log("debug", `Cached layout components for '${layoutName}': ${Array.from(componentSet).join(", ")}`);
2082
+ }
2083
+ return componentSet;
2084
+ }
2085
+ /**
2086
+ * HTML 컨텐츠에서 Vue 컴포넌트 추출
2087
+ */
2088
+ _extractComponentsFromContent(content, componentSet) {
2089
+ if (!content || typeof content !== "string") return;
2090
+ const componentPattern = /<([A-Z][a-zA-Z0-9]*)(?:\s[^>]*)?\/?>|<\/([A-Z][a-zA-Z0-9]*)\s*>/gs;
2091
+ let match;
2092
+ while ((match = componentPattern.exec(content)) !== null) {
2093
+ const componentName = match[1] || match[2];
2094
+ if (componentName && !this._isHtmlTag(componentName)) {
2095
+ componentSet.add(componentName);
2096
+ this.log("debug", `Found component: ${componentName}`);
2097
+ }
2098
+ }
2099
+ }
2100
+ /**
2101
+ * HTML 기본 태그인지 확인
2102
+ */
2103
+ _isHtmlTag(tagName) {
2104
+ const htmlTags = [
2105
+ "div",
2106
+ "span",
2107
+ "p",
2108
+ "a",
2109
+ "img",
2110
+ "ul",
2111
+ "ol",
2112
+ "li",
2113
+ "h1",
2114
+ "h2",
2115
+ "h3",
2116
+ "h4",
2117
+ "h5",
2118
+ "h6",
2119
+ "table",
2120
+ "tr",
2121
+ "td",
2122
+ "th",
2123
+ "form",
2124
+ "select",
2125
+ "option",
2126
+ "textarea",
2127
+ "nav",
2128
+ "header",
2129
+ "footer",
2130
+ "main",
2131
+ "section",
2132
+ "article",
2133
+ "aside",
2134
+ "figure",
2135
+ "figcaption",
2136
+ "video",
2137
+ "audio",
2138
+ "canvas",
2139
+ "svg",
2140
+ "iframe",
2141
+ "script",
2142
+ "style",
2143
+ "link",
2144
+ "meta",
2145
+ "title",
2146
+ "body",
2147
+ "html",
2148
+ "head",
2149
+ "template",
2150
+ "slot"
2151
+ ];
2152
+ return htmlTags.includes(tagName);
2153
+ }
2154
+ /**
2155
+ * 메모리 정리
2156
+ */
2157
+ dispose() {
2158
+ this.clearComponents();
2159
+ this.log("debug", "ComponentLoader disposed");
2160
+ this.router = null;
2161
+ }
2162
+ };
2163
+
1485
2164
  // src/core/RouteLoader.js
1486
2165
  var RouteLoader = class {
1487
2166
  constructor(router, options = {}) {
@@ -1493,10 +2172,12 @@ var RouteLoader = class {
1493
2172
  environment: options.environment || "development",
1494
2173
  useLayout: options.useLayout !== false,
1495
2174
  defaultLayout: options.defaultLayout || "default",
1496
- useComponents: options.useComponents !== false,
1497
2175
  debug: options.debug || false
1498
2176
  };
1499
2177
  this.router = router;
2178
+ this.formHandler = new FormHandler(router, this.config);
2179
+ this.apiHandler = new ApiHandler(router, this.config);
2180
+ this.componentLoader = new ComponentLoader(router, this.config);
1500
2181
  this.log("debug", "RouteLoader initialized with config:", this.config);
1501
2182
  }
1502
2183
  /**
@@ -1614,17 +2295,33 @@ ${template}`;
1614
2295
  if (isProduction) {
1615
2296
  template = script.template || this.generateDefaultTemplate(routeName);
1616
2297
  } else {
1617
- template = await this.loadTemplate(routeName);
1618
- style = await this.loadStyle(routeName);
1619
- layout = this.config.useLayout && script.layout !== null ? await this.loadLayout(script.layout || this.config.defaultLayout) : null;
2298
+ const loadPromises = [
2299
+ this.loadTemplate(routeName),
2300
+ this.loadStyle(routeName)
2301
+ ];
2302
+ if (this.config.useLayout && script.layout !== null) {
2303
+ loadPromises.push(this.loadLayout(script.layout || this.config.defaultLayout));
2304
+ } else {
2305
+ loadPromises.push(Promise.resolve(null));
2306
+ }
2307
+ const [loadedTemplate, loadedStyle, loadedLayout] = await Promise.all(loadPromises);
2308
+ template = loadedTemplate;
2309
+ style = loadedStyle;
2310
+ layout = loadedLayout;
1620
2311
  if (layout) {
1621
2312
  template = this.mergeLayoutWithTemplate(routeName, layout, template);
1622
2313
  }
1623
2314
  }
1624
2315
  let loadedComponents = {};
1625
- if (this.config.useComponents && router.componentLoader) {
2316
+ if (this.componentLoader) {
1626
2317
  try {
1627
- loadedComponents = await router.componentLoader.loadAllComponents();
2318
+ let componentNames = null;
2319
+ if (!isProduction) {
2320
+ const layoutName = script.layout || this.config.defaultLayout;
2321
+ componentNames = this.componentLoader.getComponentNames(template, layout, layoutName);
2322
+ this.log("info", `[DEVELOPMENT] Discovered components for route '${routeName}':`, componentNames);
2323
+ }
2324
+ loadedComponents = await this.componentLoader.loadAllComponents(componentNames);
1628
2325
  this.log("debug", `Components loaded successfully for route: ${routeName}`);
1629
2326
  } catch (error) {
1630
2327
  this.log("warn", `Component loading failed for route '${routeName}', continuing without components:`, error.message);
@@ -1666,14 +2363,10 @@ ${template}`;
1666
2363
  await script.mounted.call(this);
1667
2364
  }
1668
2365
  if (script.dataURL) {
1669
- if (typeof script.dataURL === "string") {
1670
- await this.$fetchData();
1671
- } else if (typeof script.dataURL === "object") {
1672
- await this.$fetchMultipleData();
1673
- }
2366
+ await this.$fetchData();
1674
2367
  }
1675
2368
  await this.$nextTick();
1676
- this.$bindAutoForms();
2369
+ router.routeLoader.formHandler.bindAutoForms(this);
1677
2370
  },
1678
2371
  methods: {
1679
2372
  ...script.methods,
@@ -1702,245 +2395,52 @@ ${template}`;
1702
2395
  $removeToken: (storage) => router.authManager?.removeAccessToken(storage) || null,
1703
2396
  $getAuthCookie: () => router.authManager?.getAuthCookie() || null,
1704
2397
  $getCookie: (name) => router.authManager?.getCookieValue(name) || null,
1705
- // 데이터 fetch (단일 API 또는 특정 API)
1706
- async $fetchData(apiName) {
1707
- if (!script.dataURL) return;
2398
+ // 데이터 fetch (ApiHandler 래퍼)
2399
+ async $fetchData(dataConfig = null) {
2400
+ const configToUse = dataConfig || script.dataURL;
2401
+ if (!configToUse) return null;
1708
2402
  this.$dataLoading = true;
1709
2403
  try {
1710
- if (typeof script.dataURL === "string") {
1711
- const data = await router.routeLoader.fetchComponentData(script.dataURL);
1712
- if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Data fetched for ${routeName}:`, data);
2404
+ if (typeof configToUse === "string") {
2405
+ const data = await router.routeLoader.apiHandler.fetchData(configToUse, this);
1713
2406
  Object.assign(this, data);
1714
2407
  this.$emit("data-loaded", data);
1715
- } else if (typeof script.dataURL === "object" && apiName) {
1716
- const url = script.dataURL[apiName];
1717
- if (url) {
1718
- const data = await router.routeLoader.fetchComponentData(url);
1719
- if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Data fetched for ${routeName}.${apiName}:`, data);
1720
- this[apiName] = data;
1721
- this.$emit("data-loaded", { [apiName]: data });
1722
- }
1723
- } else {
1724
- await this.$fetchMultipleData();
1725
- }
1726
- } catch (error) {
1727
- if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Failed to fetch data for ${routeName}:`, error);
1728
- this.$emit("data-error", error);
1729
- } finally {
1730
- this.$dataLoading = false;
1731
- }
1732
- },
1733
- // 다중 API 데이터 fetch
1734
- async $fetchMultipleData() {
1735
- if (!script.dataURL || typeof script.dataURL !== "object") return;
1736
- const dataURLs = script.dataURL;
1737
- this.$dataLoading = true;
1738
- try {
1739
- const promises = Object.entries(dataURLs).map(async ([key, url]) => {
1740
- try {
1741
- const data = await router.routeLoader.fetchComponentData(url);
1742
- return { key, data, success: true };
1743
- } catch (error) {
1744
- if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Failed to fetch ${key} for ${routeName}:`, error);
1745
- return { key, error, success: false };
2408
+ return data;
2409
+ } else if (typeof configToUse === "object") {
2410
+ const { results, errors } = await router.routeLoader.apiHandler.fetchMultipleData(configToUse, this);
2411
+ Object.assign(this, results);
2412
+ if (Object.keys(results).length > 0) {
2413
+ this.$emit("data-loaded", results);
1746
2414
  }
1747
- });
1748
- const results = await Promise.all(promises);
1749
- const successfulResults = {};
1750
- const errors = {};
1751
- results.forEach(({ key, data, error, success }) => {
1752
- if (success) {
1753
- this[key] = data;
1754
- successfulResults[key] = data;
1755
- } else {
1756
- errors[key] = error;
2415
+ if (Object.keys(errors).length > 0) {
2416
+ this.$emit("data-error", errors);
1757
2417
  }
1758
- });
1759
- if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Multiple data fetched for ${routeName}:`, successfulResults);
1760
- if (Object.keys(successfulResults).length > 0) {
1761
- this.$emit("data-loaded", successfulResults);
1762
- }
1763
- if (Object.keys(errors).length > 0) {
1764
- this.$emit("data-error", errors);
2418
+ return results;
1765
2419
  }
2420
+ return null;
1766
2421
  } catch (error) {
1767
- if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Failed to fetch multiple data for ${routeName}:`, error);
2422
+ if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Failed to fetch data for ${routeName}:`, error);
1768
2423
  this.$emit("data-error", error);
2424
+ throw error;
1769
2425
  } finally {
1770
2426
  this.$dataLoading = false;
1771
2427
  }
1772
2428
  },
1773
- // 전체 데이터 새로고침 (명시적 메서드)
1774
- async $fetchAllData() {
1775
- if (typeof script.dataURL === "string") {
1776
- await this.$fetchData();
1777
- } else if (typeof script.dataURL === "object") {
1778
- await this.$fetchMultipleData();
1779
- }
1780
- },
1781
- // 🆕 자동 폼 바인딩 메서드
1782
- $bindAutoForms() {
1783
- const forms = document.querySelectorAll("form.auto-form, form[action]");
1784
- forms.forEach((form) => {
1785
- form.removeEventListener("submit", form._boundSubmitHandler);
1786
- const boundHandler = (e) => this.$handleFormSubmit(e);
1787
- form._boundSubmitHandler = boundHandler;
1788
- form.addEventListener("submit", boundHandler);
1789
- if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Form auto-bound: ${form.getAttribute("action")}`);
1790
- });
1791
- },
1792
- // 🆕 폼 서브밋 핸들러
1793
- async $handleFormSubmit(event) {
1794
- event.preventDefault();
1795
- const form = event.target;
1796
- let action = form.getAttribute("action");
1797
- const method = form.getAttribute("method") || "POST";
1798
- const successHandler = form.getAttribute("data-success-handler");
1799
- const errorHandler = form.getAttribute("data-error-handler");
1800
- const loadingHandler = form.getAttribute("data-loading-handler");
1801
- const redirectTo = form.getAttribute("data-redirect");
1802
- try {
1803
- if (loadingHandler && this[loadingHandler]) {
1804
- this[loadingHandler](true, form);
1805
- }
1806
- action = this.$processActionParams(action);
1807
- if (!this.$validateForm(form)) {
1808
- return;
1809
- }
1810
- const formData = new FormData(form);
1811
- const data = Object.fromEntries(formData.entries());
1812
- if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Form submitting to: ${action}`, data);
1813
- const response = await this.$submitFormData(action, method, data, form);
1814
- if (successHandler && this[successHandler]) {
1815
- this[successHandler](response, form);
1816
- }
1817
- if (redirectTo) {
1818
- setTimeout(() => {
1819
- this.navigateTo(redirectTo);
1820
- }, 1e3);
1821
- }
1822
- } catch (error) {
1823
- if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Form submission error:`, error);
1824
- if (errorHandler && this[errorHandler]) {
1825
- this[errorHandler](error, form);
1826
- } else {
1827
- console.error("Form submission error:", error);
1828
- }
1829
- } finally {
1830
- if (loadingHandler && this[loadingHandler]) {
1831
- this[loadingHandler](false, form);
1832
- }
1833
- }
2429
+ // HTTP 메서드 래퍼들 (ApiHandler 직접 접근)
2430
+ async $get(url, options = {}) {
2431
+ return await router.routeLoader.apiHandler.get(url, this, options);
1834
2432
  },
1835
- // 🆕 액션 파라미터 처리 메서드 (간단한 템플릿 치환)
1836
- $processActionParams(actionTemplate) {
1837
- let processedAction = actionTemplate;
1838
- const paramMatches = actionTemplate.match(/\{([^}]+)\}/g);
1839
- if (paramMatches) {
1840
- paramMatches.forEach((match) => {
1841
- const paramName = match.slice(1, -1);
1842
- try {
1843
- let paramValue = null;
1844
- paramValue = this.getParam(paramName);
1845
- if (paramValue === null || paramValue === void 0) {
1846
- paramValue = this[paramName];
1847
- }
1848
- if (paramValue === null || paramValue === void 0) {
1849
- if (this.$options.computed && this.$options.computed[paramName]) {
1850
- paramValue = this[paramName];
1851
- }
1852
- }
1853
- if (paramValue !== null && paramValue !== void 0) {
1854
- processedAction = processedAction.replace(
1855
- match,
1856
- encodeURIComponent(paramValue)
1857
- );
1858
- if (router.errorHandler) router.errorHandler.debug("RouteLoader", `Parameter resolved: ${paramName} = ${paramValue}`);
1859
- } else {
1860
- if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Parameter '${paramName}' not found in component data, computed, or route params`);
1861
- }
1862
- } catch (error) {
1863
- if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Error processing parameter '${paramName}':`, error);
1864
- }
1865
- });
1866
- }
1867
- return processedAction;
2433
+ async $post(url, data, options = {}) {
2434
+ return await router.routeLoader.apiHandler.post(url, data, this, options);
1868
2435
  },
1869
- // 🆕 데이터 서브밋
1870
- async $submitFormData(action, method, data, form) {
1871
- const hasFile = Array.from(form.elements).some((el) => el.type === "file" && el.files.length > 0);
1872
- const headers = {
1873
- "Accept": "application/json",
1874
- // 인증 토큰 자동 추가
1875
- ...this.$getToken() && {
1876
- "Authorization": `Bearer ${this.$getToken()}`
1877
- }
1878
- };
1879
- let body;
1880
- if (hasFile) {
1881
- body = new FormData(form);
1882
- } else {
1883
- headers["Content-Type"] = "application/json";
1884
- body = JSON.stringify(data);
1885
- }
1886
- const response = await fetch(action, {
1887
- method: method.toUpperCase(),
1888
- headers,
1889
- body
1890
- });
1891
- if (!response.ok) {
1892
- let error;
1893
- try {
1894
- error = await response.json();
1895
- } catch (e) {
1896
- error = { message: `HTTP ${response.status}: ${response.statusText}` };
1897
- }
1898
- throw new Error(error.message || `HTTP ${response.status}`);
1899
- }
1900
- try {
1901
- return await response.json();
1902
- } catch (e) {
1903
- return { success: true };
1904
- }
2436
+ async $put(url, data, options = {}) {
2437
+ return await router.routeLoader.apiHandler.put(url, data, this, options);
1905
2438
  },
1906
- // 🆕 클라이언트 사이드 검증
1907
- $validateForm(form) {
1908
- let isValid = true;
1909
- const inputs = form.querySelectorAll("input, textarea, select");
1910
- inputs.forEach((input) => {
1911
- if (!input.checkValidity()) {
1912
- isValid = false;
1913
- input.classList.add("error");
1914
- return;
1915
- }
1916
- const validationFunction = input.getAttribute("data-validation");
1917
- if (validationFunction) {
1918
- const isInputValid = this.$validateInput(input, validationFunction);
1919
- if (!isInputValid) {
1920
- isValid = false;
1921
- input.classList.add("error");
1922
- } else {
1923
- input.classList.remove("error");
1924
- }
1925
- } else {
1926
- input.classList.remove("error");
1927
- }
1928
- });
1929
- return isValid;
2439
+ async $patch(url, data, options = {}) {
2440
+ return await router.routeLoader.apiHandler.patch(url, data, this, options);
1930
2441
  },
1931
- // 🆕 개별 입력 검증
1932
- $validateInput(input, validationFunction) {
1933
- const value = input.value;
1934
- if (typeof this[validationFunction] === "function") {
1935
- try {
1936
- return this[validationFunction](value, input);
1937
- } catch (error) {
1938
- if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Validation function '${validationFunction}' error:`, error);
1939
- return false;
1940
- }
1941
- }
1942
- if (router.errorHandler) router.errorHandler.warn("RouteLoader", `Validation function '${validationFunction}' not found`);
1943
- return true;
2442
+ async $delete(url, options = {}) {
2443
+ return await router.routeLoader.apiHandler.delete(url, this, options);
1944
2444
  }
1945
2445
  },
1946
2446
  _routeName: routeName
@@ -1963,61 +2463,6 @@ ${template}`;
1963
2463
  generateDefaultTemplate(routeName) {
1964
2464
  return `<div class="route-${routeName}"><h1>Route: ${routeName}</h1></div>`;
1965
2465
  }
1966
- /**
1967
- * 컴포넌트 데이터 가져오기
1968
- */
1969
- async fetchComponentData(dataURL) {
1970
- try {
1971
- const queryString = this.router.queryManager?.buildQueryString(this.router.queryManager?.getQueryParams()) || "";
1972
- const fullURL = queryString ? `${dataURL}?${queryString}` : dataURL;
1973
- this.log("debug", `Fetching data from: ${fullURL}`);
1974
- const response = await fetch(fullURL, {
1975
- method: "GET",
1976
- headers: {
1977
- "Content-Type": "application/json",
1978
- "Accept": "application/json"
1979
- }
1980
- });
1981
- if (!response.ok) {
1982
- throw new Error(`HTTP error! status: ${response.status}`);
1983
- }
1984
- const data = await response.json();
1985
- if (typeof data !== "object" || data === null) {
1986
- throw new Error("Invalid data format: expected object");
1987
- }
1988
- return data;
1989
- } catch (error) {
1990
- this.log("error", "Failed to fetch component data:", error);
1991
- throw error;
1992
- }
1993
- }
1994
- /**
1995
- * 캐시 무효화
1996
- */
1997
- invalidateCache(routeName) {
1998
- if (this.router.cacheManager) {
1999
- this.router.cacheManager.invalidateComponentCache(routeName);
2000
- }
2001
- this.log("debug", `Cache invalidated for route: ${routeName}`);
2002
- }
2003
- /**
2004
- * 통계 정보 반환
2005
- */
2006
- getStats() {
2007
- return {
2008
- environment: this.config.environment,
2009
- srcPath: this.config.srcPath,
2010
- routesPath: this.config.routesPath,
2011
- useLayout: this.config.useLayout,
2012
- useComponents: this.config.useComponents
2013
- };
2014
- }
2015
- /**
2016
- * 페이지 제목 생성
2017
- */
2018
- generatePageTitle(routeName) {
2019
- return this.toPascalCase(routeName).replace(/([A-Z])/g, " $1").trim();
2020
- }
2021
2466
  /**
2022
2467
  * 로깅 래퍼 메서드
2023
2468
  */
@@ -2030,6 +2475,18 @@ ${template}`;
2030
2475
  * 정리 (메모리 누수 방지)
2031
2476
  */
2032
2477
  destroy() {
2478
+ if (this.formHandler) {
2479
+ this.formHandler.destroy();
2480
+ this.formHandler = null;
2481
+ }
2482
+ if (this.apiHandler) {
2483
+ this.apiHandler.destroy();
2484
+ this.apiHandler = null;
2485
+ }
2486
+ if (this.componentLoader) {
2487
+ this.componentLoader.dispose();
2488
+ this.componentLoader = null;
2489
+ }
2033
2490
  this.log("debug", "RouteLoader destroyed");
2034
2491
  this.router = null;
2035
2492
  }
@@ -2306,176 +2763,6 @@ var ErrorHandler = class {
2306
2763
  }
2307
2764
  };
2308
2765
 
2309
- // src/core/ComponentLoader.js
2310
- var ComponentLoader = class {
2311
- constructor(router = null, options = {}) {
2312
- this.config = {
2313
- componentsPath: options.componentsPath || "/components",
2314
- // srcPath 기준 상대 경로
2315
- debug: options.debug || false,
2316
- environment: options.environment || "development",
2317
- ...options
2318
- };
2319
- this.router = router;
2320
- this.loadingPromises = /* @__PURE__ */ new Map();
2321
- this.unifiedComponents = null;
2322
- }
2323
- /**
2324
- * 로깅 래퍼 메서드
2325
- */
2326
- log(level, ...args) {
2327
- if (this.router?.errorHandler) {
2328
- this.router.errorHandler.log(level, "ComponentLoader", ...args);
2329
- }
2330
- }
2331
- /**
2332
- * 컴포넌트를 비동기로 로드
2333
- */
2334
- async loadComponent(componentName) {
2335
- if (!componentName || typeof componentName !== "string") {
2336
- throw new Error("Component name must be a non-empty string");
2337
- }
2338
- if (this.loadingPromises.has(componentName)) {
2339
- return this.loadingPromises.get(componentName);
2340
- }
2341
- const loadPromise = this._loadComponentFromFile(componentName);
2342
- this.loadingPromises.set(componentName, loadPromise);
2343
- try {
2344
- const component = await loadPromise;
2345
- return component;
2346
- } catch (error) {
2347
- throw error;
2348
- } finally {
2349
- this.loadingPromises.delete(componentName);
2350
- }
2351
- }
2352
- /**
2353
- * 파일에서 컴포넌트 로드
2354
- */
2355
- async _loadComponentFromFile(componentName) {
2356
- const componentRelativePath = `${this.config.componentsPath}/${componentName}.js`;
2357
- let componentPath;
2358
- if (this.router && this.router.config.srcPath) {
2359
- const srcPath = this.router.config.srcPath;
2360
- if (srcPath.startsWith("http")) {
2361
- const cleanSrcPath = srcPath.endsWith("/") ? srcPath.slice(0, -1) : srcPath;
2362
- const cleanComponentPath = componentRelativePath.startsWith("/") ? componentRelativePath : `/${componentRelativePath}`;
2363
- componentPath = `${cleanSrcPath}${cleanComponentPath}`;
2364
- } else {
2365
- componentPath = this.router.resolvePath(`${srcPath}${componentRelativePath}`);
2366
- }
2367
- } else {
2368
- componentPath = this.router ? this.router.resolvePath(`/src${componentRelativePath}`) : `/src${componentRelativePath}`;
2369
- }
2370
- try {
2371
- const module = await import(componentPath);
2372
- const component = module.default;
2373
- if (!component) {
2374
- throw new Error(`Component '${componentName}' has no default export`);
2375
- }
2376
- if (!component.name) {
2377
- component.name = componentName;
2378
- }
2379
- this.log("debug", `Component '${componentName}' loaded successfully`);
2380
- return component;
2381
- } catch (error) {
2382
- this.log("error", `Failed to load component '${componentName}':`, error);
2383
- throw new Error(`Component '${componentName}' not found: ${error.message}`);
2384
- }
2385
- }
2386
- /**
2387
- * 컴포넌트 모듈 클리어
2388
- */
2389
- clearComponents() {
2390
- this.loadingPromises.clear();
2391
- this.unifiedComponents = null;
2392
- this.log("debug", "All components cleared");
2393
- }
2394
- /**
2395
- * 환경에 따른 모든 컴포넌트 로딩 (캐싱 지원)
2396
- */
2397
- async loadAllComponents() {
2398
- if (this.unifiedComponents) {
2399
- this.log("debug", "Using existing unified components");
2400
- return this.unifiedComponents;
2401
- }
2402
- if (this.config.environment === "production") {
2403
- return await this._loadProductionComponents();
2404
- }
2405
- return await this._loadDevelopmentComponents();
2406
- }
2407
- /**
2408
- * 운영 모드: 통합 컴포넌트 로딩
2409
- */
2410
- async _loadProductionComponents() {
2411
- try {
2412
- const componentsPath = `${this.router?.config?.routesPath || "/routes"}/_components.js`;
2413
- this.log("info", "[PRODUCTION] Loading unified components from:", componentsPath);
2414
- const componentsModule = await import(componentsPath);
2415
- if (typeof componentsModule.registerComponents === "function") {
2416
- this.unifiedComponents = componentsModule.components || {};
2417
- this.log("info", `[PRODUCTION] Unified components loaded: ${Object.keys(this.unifiedComponents).length} components`);
2418
- return this.unifiedComponents;
2419
- } else {
2420
- throw new Error("registerComponents function not found in components module");
2421
- }
2422
- } catch (error) {
2423
- this.log("warn", "[PRODUCTION] Failed to load unified components:", error.message);
2424
- this.unifiedComponents = {};
2425
- return {};
2426
- }
2427
- }
2428
- /**
2429
- * 개발 모드: 개별 컴포넌트 로딩
2430
- */
2431
- async _loadDevelopmentComponents() {
2432
- const componentNames = this._getComponentNames();
2433
- const components = {};
2434
- this.log("info", `[DEVELOPMENT] Loading individual components: ${componentNames.join(", ")}`);
2435
- for (const name of componentNames) {
2436
- try {
2437
- const component = await this.loadComponent(name);
2438
- if (component) {
2439
- components[name] = component;
2440
- }
2441
- } catch (loadError) {
2442
- this.log("warn", `[DEVELOPMENT] Failed to load component ${name}:`, loadError.message);
2443
- }
2444
- }
2445
- this.unifiedComponents = components;
2446
- this.log("info", `[DEVELOPMENT] Individual components loaded: ${Object.keys(components).length} components`);
2447
- return components;
2448
- }
2449
- /**
2450
- * 컴포넌트 이름 목록 가져오기
2451
- */
2452
- _getComponentNames() {
2453
- if (Array.isArray(this.config.componentNames) && this.config.componentNames.length > 0) {
2454
- return [...this.config.componentNames];
2455
- }
2456
- return [
2457
- "Button",
2458
- "Modal",
2459
- "Card",
2460
- "Toast",
2461
- "Input",
2462
- "Tabs",
2463
- "Checkbox",
2464
- "Alert",
2465
- "DynamicInclude",
2466
- "HtmlInclude"
2467
- ];
2468
- }
2469
- /**
2470
- * 메모리 정리
2471
- */
2472
- dispose() {
2473
- this.clearComponents();
2474
- this.log("debug", "ComponentLoader disposed");
2475
- this.router = null;
2476
- }
2477
- };
2478
-
2479
2766
  // src/viewlogic-router.js
2480
2767
  var ViewLogicRouter = class {
2481
2768
  constructor(options = {}) {
@@ -2484,7 +2771,6 @@ var ViewLogicRouter = class {
2484
2771
  this.currentHash = "";
2485
2772
  this.currentVueApp = null;
2486
2773
  this.previousVueApp = null;
2487
- this.componentLoader = null;
2488
2774
  this.transitionInProgress = false;
2489
2775
  this.isReady = false;
2490
2776
  this.readyPromise = null;
@@ -2511,8 +2797,6 @@ var ViewLogicRouter = class {
2511
2797
  routesPath: "/routes",
2512
2798
  // 프로덕션 라우트 경로
2513
2799
  enableErrorReporting: true,
2514
- useComponents: true,
2515
- componentNames: ["Button", "Modal", "Card", "Toast", "Input", "Tabs", "Checkbox", "Alert", "DynamicInclude", "HtmlInclude"],
2516
2800
  useI18n: false,
2517
2801
  defaultLanguage: "ko",
2518
2802
  i18nPath: "/i18n",
@@ -2621,21 +2905,6 @@ var ViewLogicRouter = class {
2621
2905
  if (this.config.authEnabled) {
2622
2906
  this.authManager = new AuthManager(this, this.config);
2623
2907
  }
2624
- if (this.config.useComponents) {
2625
- try {
2626
- this.componentLoader = new ComponentLoader(this, {
2627
- ...this.config,
2628
- basePath: `${this.config.basePath}/components`,
2629
- cache: true,
2630
- componentNames: this.config.componentNames
2631
- });
2632
- await this.componentLoader.loadAllComponents();
2633
- this.log("info", "ComponentLoader initialized successfully");
2634
- } catch (componentError) {
2635
- this.log("warn", "ComponentLoader initialization failed, continuing without components:", componentError.message);
2636
- this.componentLoader = null;
2637
- }
2638
- }
2639
2908
  this.isReady = true;
2640
2909
  this.init();
2641
2910
  } catch (error) {