create-nativecore 0.1.0 → 0.2.0
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.
- package/README.md +10 -18
- package/bin/index.mjs +407 -489
- package/package.json +4 -3
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +642 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NativeCore Dev Tools
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: This module is ONLY loaded in development mode (localhost).
|
|
5
|
+
* It is completely excluded from production builds via tsconfig.build.json
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Component overlay with gear icons on hover
|
|
9
|
+
* - Live edit modal for component properties
|
|
10
|
+
* - Direct file modification via dev server API
|
|
11
|
+
* - Real-time updates via HMR
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { ComponentOverlay } from './component-overlay.js';
|
|
15
|
+
import { ComponentEditor } from './component-editor.js';
|
|
16
|
+
import { OutlinePanel } from './outline-panel.js';
|
|
17
|
+
|
|
18
|
+
class DevTools {
|
|
19
|
+
private static readonly TOGGLE_STORAGE_KEY = 'nativecore-devtools-visible';
|
|
20
|
+
private overlay: ComponentOverlay | null = null;
|
|
21
|
+
private editor: ComponentEditor | null = null;
|
|
22
|
+
private outlinePanel: OutlinePanel | null = null;
|
|
23
|
+
private indicator: HTMLButtonElement | null = null;
|
|
24
|
+
private enabled: boolean = false;
|
|
25
|
+
private overlayVisible: boolean = true;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initialize dev tools - only works on localhost
|
|
29
|
+
*/
|
|
30
|
+
init(): void {
|
|
31
|
+
// SECURITY: Triple-check we're in dev mode
|
|
32
|
+
if (!this.isDevEnvironment()) {
|
|
33
|
+
console.warn('[DevTools] Not in dev environment, refusing to initialize');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// console.log('[DevTools] Initializing NativeCore Dev Tools...');
|
|
38
|
+
|
|
39
|
+
this.overlay = new ComponentOverlay(this.onEditComponent.bind(this));
|
|
40
|
+
this.editor = new ComponentEditor();
|
|
41
|
+
this.outlinePanel = new OutlinePanel(this.onEditComponent.bind(this));
|
|
42
|
+
this.enabled = true;
|
|
43
|
+
this.overlayVisible = this.loadOverlayVisibilityPreference();
|
|
44
|
+
this.overlay.setVisible(this.overlayVisible);
|
|
45
|
+
|
|
46
|
+
// Add dev mode indicator
|
|
47
|
+
this.addDevIndicator();
|
|
48
|
+
|
|
49
|
+
// console.log('[DevTools] Ready - hover over components to see edit options');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if we're in a development environment
|
|
54
|
+
*/
|
|
55
|
+
private isDevEnvironment(): boolean {
|
|
56
|
+
const hostname = window.location.hostname;
|
|
57
|
+
const isLocalhost = hostname === 'localhost' ||
|
|
58
|
+
hostname === '127.0.0.1' ||
|
|
59
|
+
hostname.startsWith('192.168.') ||
|
|
60
|
+
hostname.endsWith('.local');
|
|
61
|
+
|
|
62
|
+
// Also check for explicit dev flag
|
|
63
|
+
const hasDevFlag = (window as any).__NATIVECORE_DEV__ === true;
|
|
64
|
+
|
|
65
|
+
return isLocalhost || hasDevFlag;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Handle edit request from overlay
|
|
70
|
+
*/
|
|
71
|
+
private async onEditComponent(element: HTMLElement, fromOutlineTree: boolean = false): Promise<void> {
|
|
72
|
+
if (!this.editor) return;
|
|
73
|
+
|
|
74
|
+
const tagName = element.tagName.toLowerCase();
|
|
75
|
+
// console.log(`[DevTools] Opening editor for <${tagName}>`);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// Fetch component metadata from server
|
|
79
|
+
const metadata = await this.fetchComponentMetadata(tagName);
|
|
80
|
+
|
|
81
|
+
// Open editor modal
|
|
82
|
+
this.editor.open(element, metadata);
|
|
83
|
+
|
|
84
|
+
// Only expand outline panel if clicked from page (not from tree)
|
|
85
|
+
if (this.outlinePanel && !fromOutlineTree) {
|
|
86
|
+
this.outlinePanel.showAndExpandTo(element);
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error('[DevTools] Failed to open editor:', error);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fetch component file and parse metadata
|
|
95
|
+
*/
|
|
96
|
+
private async fetchComponentMetadata(tagName: string): Promise<ComponentMetadata> {
|
|
97
|
+
const response = await fetch(`/api/dev/component/${tagName}`);
|
|
98
|
+
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
throw new Error(`Failed to fetch metadata for ${tagName}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return response.json();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Add visual indicator that dev mode is active
|
|
108
|
+
*/
|
|
109
|
+
private addDevIndicator(): void {
|
|
110
|
+
this.indicator?.remove();
|
|
111
|
+
|
|
112
|
+
const indicator = document.createElement('button');
|
|
113
|
+
indicator.id = 'nativecore-denc-indicator';
|
|
114
|
+
indicator.type = 'button';
|
|
115
|
+
indicator.setAttribute('aria-pressed', String(this.overlayVisible));
|
|
116
|
+
indicator.innerHTML = `
|
|
117
|
+
<style>
|
|
118
|
+
#nativecore-denc-indicator {
|
|
119
|
+
position: fixed;
|
|
120
|
+
bottom: 20px;
|
|
121
|
+
left: 50%;
|
|
122
|
+
transform: translateX(calc(-50% + 120px));
|
|
123
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
124
|
+
color: white;
|
|
125
|
+
padding: 6px 12px;
|
|
126
|
+
border-radius: 20px;
|
|
127
|
+
font-size: 11px;
|
|
128
|
+
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
129
|
+
font-weight: 600;
|
|
130
|
+
z-index: 100;
|
|
131
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
132
|
+
transition: opacity 0.2s, transform 0.2s, box-shadow 0.2s;
|
|
133
|
+
opacity: 0.95;
|
|
134
|
+
border: none;
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#nativecore-denc-indicator:hover {
|
|
139
|
+
transform: translateX(calc(-50% + 120px)) translateY(-1px);
|
|
140
|
+
box-shadow: 0 6px 18px rgba(0,0,0,0.22);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#nativecore-denc-indicator.is-off {
|
|
144
|
+
background: linear-gradient(135deg, #475569 0%, #334155 100%);
|
|
145
|
+
}
|
|
146
|
+
</style>
|
|
147
|
+
DEV MODE: ${this.overlayVisible ? 'ON' : 'OFF'}
|
|
148
|
+
`;
|
|
149
|
+
indicator.classList.toggle('is-off', !this.overlayVisible);
|
|
150
|
+
indicator.addEventListener('click', () => this.toggleOverlayVisibility());
|
|
151
|
+
|
|
152
|
+
document.body.appendChild(indicator);
|
|
153
|
+
this.indicator = indicator;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private loadOverlayVisibilityPreference(): boolean {
|
|
157
|
+
try {
|
|
158
|
+
return localStorage.getItem(DevTools.TOGGLE_STORAGE_KEY) !== 'false';
|
|
159
|
+
} catch {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private saveOverlayVisibilityPreference(): void {
|
|
165
|
+
try {
|
|
166
|
+
localStorage.setItem(DevTools.TOGGLE_STORAGE_KEY, String(this.overlayVisible));
|
|
167
|
+
} catch {
|
|
168
|
+
// Ignore storage failures in dev mode.
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private toggleOverlayVisibility(): void {
|
|
173
|
+
this.overlayVisible = !this.overlayVisible;
|
|
174
|
+
|
|
175
|
+
if (!this.overlayVisible) {
|
|
176
|
+
document.dispatchEvent(new CustomEvent('nc-close-editor'));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.overlay?.setVisible(this.overlayVisible);
|
|
180
|
+
this.outlinePanel?.setVisible(this.overlayVisible);
|
|
181
|
+
this.saveOverlayVisibilityPreference();
|
|
182
|
+
this.addDevIndicator();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Disable dev tools
|
|
187
|
+
*/
|
|
188
|
+
destroy(): void {
|
|
189
|
+
if (this.overlay) {
|
|
190
|
+
this.overlay.destroy();
|
|
191
|
+
}
|
|
192
|
+
if (this.editor) {
|
|
193
|
+
this.editor.destroy();
|
|
194
|
+
}
|
|
195
|
+
if (this.outlinePanel) {
|
|
196
|
+
this.outlinePanel.destroy();
|
|
197
|
+
}
|
|
198
|
+
// Use dom.query for consistency
|
|
199
|
+
const indicator = (window.dom?.query?.('#nativecore-denc-indicator') || document.getElementById('nativecore-denc-indicator')) as HTMLElement;
|
|
200
|
+
if (indicator) {
|
|
201
|
+
indicator.remove();
|
|
202
|
+
}
|
|
203
|
+
this.indicator = null;
|
|
204
|
+
this.enabled = false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export interface ComponentMetadata {
|
|
209
|
+
tagName: string;
|
|
210
|
+
filePath: string;
|
|
211
|
+
absoluteFilePath?: string;
|
|
212
|
+
className: string;
|
|
213
|
+
attributes: AttributeInfo[];
|
|
214
|
+
cssVariables: CssVariableInfo[];
|
|
215
|
+
slots: SlotInfo[];
|
|
216
|
+
sourceCode: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface AttributeInfo {
|
|
220
|
+
name: string;
|
|
221
|
+
type: 'string' | 'number' | 'boolean';
|
|
222
|
+
defaultValue: string;
|
|
223
|
+
currentValue: string;
|
|
224
|
+
line: number;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export interface CssVariableInfo {
|
|
228
|
+
name: string;
|
|
229
|
+
defaultValue: string;
|
|
230
|
+
currentValue: string;
|
|
231
|
+
line: number;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface SlotInfo {
|
|
235
|
+
name: string;
|
|
236
|
+
content: string;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Export singleton
|
|
240
|
+
export const devTools = new DevTools();
|
|
241
|
+
|
|
242
|
+
// Auto-initialize if in dev mode
|
|
243
|
+
if (typeof window !== 'undefined') {
|
|
244
|
+
// Wait for DOM ready
|
|
245
|
+
if (document.readyState === 'loading') {
|
|
246
|
+
document.addEventListener('DOMContentLoaded', () => devTools.init());
|
|
247
|
+
} else {
|
|
248
|
+
devTools.init();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/dev/hmr.ts
|
|
2
|
+
// NativeCore HMR client (development only)
|
|
3
|
+
|
|
4
|
+
// Extend Window interface for HMR
|
|
5
|
+
declare global {
|
|
6
|
+
interface Window {
|
|
7
|
+
__hmrWebSocket?: WebSocket;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
|
|
12
|
+
// Restore scroll position after HMR reload
|
|
13
|
+
const savedScroll = sessionStorage.getItem('hmr_scroll_pos');
|
|
14
|
+
if (savedScroll !== null) {
|
|
15
|
+
// Wait for router to load the page, then restore scroll
|
|
16
|
+
window.addEventListener('pageloaded', () => {
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
window.scrollTo(0, parseInt(savedScroll, 10));
|
|
19
|
+
sessionStorage.removeItem('hmr_scroll_pos');
|
|
20
|
+
console.warn('🔥 Scroll position restored:', savedScroll);
|
|
21
|
+
}, 100);
|
|
22
|
+
}, { once: true });
|
|
23
|
+
|
|
24
|
+
// Fallback if pageloaded doesn't fire
|
|
25
|
+
setTimeout(() => {
|
|
26
|
+
const scrollPos = sessionStorage.getItem('hmr_scroll_pos');
|
|
27
|
+
if (scrollPos) {
|
|
28
|
+
window.scrollTo(0, parseInt(scrollPos, 10));
|
|
29
|
+
sessionStorage.removeItem('hmr_scroll_pos');
|
|
30
|
+
}
|
|
31
|
+
}, 500);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Prevent multiple HMR connections
|
|
35
|
+
if (window.__hmrWebSocket) {
|
|
36
|
+
console.warn('[HMR] Connection already exists, skipping...');
|
|
37
|
+
} else {
|
|
38
|
+
const ws = new WebSocket('ws://localhost:8001');
|
|
39
|
+
window.__hmrWebSocket = ws;
|
|
40
|
+
let isConnected = false;
|
|
41
|
+
|
|
42
|
+
// Show HMR status indicator
|
|
43
|
+
function showHMRIndicator(status: 'connected' | 'disconnected' | 'updating', message?: string): void {
|
|
44
|
+
let indicator = document.getElementById('hmr-indicator');
|
|
45
|
+
|
|
46
|
+
if (!indicator) {
|
|
47
|
+
indicator = document.createElement('div');
|
|
48
|
+
indicator.id = 'hmr-indicator';
|
|
49
|
+
indicator.style.cssText = `
|
|
50
|
+
position: fixed;
|
|
51
|
+
bottom: 20px;
|
|
52
|
+
left: 50%;
|
|
53
|
+
transform: translateX(calc(-50% - 96px));
|
|
54
|
+
padding: 6px 12px;
|
|
55
|
+
background: #000;
|
|
56
|
+
color: #fff;
|
|
57
|
+
border-radius: 20px;
|
|
58
|
+
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
|
59
|
+
font-size: 11px;
|
|
60
|
+
font-weight: 600;
|
|
61
|
+
z-index: 999999;
|
|
62
|
+
opacity: 1;
|
|
63
|
+
transition: all 0.3s;
|
|
64
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
65
|
+
pointer-events: none;
|
|
66
|
+
line-height: 1.2;
|
|
67
|
+
`;
|
|
68
|
+
document.body.appendChild(indicator);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const colors: Record<string, string> = {
|
|
72
|
+
connected: '#10b981',
|
|
73
|
+
disconnected: '#ef4444',
|
|
74
|
+
updating: '#f59e0b'
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const icons: Record<string, string> = {
|
|
78
|
+
connected: '[HMR]',
|
|
79
|
+
disconnected: '[DISCONNECTED]',
|
|
80
|
+
updating: '[UPDATING]'
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
indicator.textContent = `${icons[status]} ${message || status.toUpperCase()}`;
|
|
84
|
+
indicator.style.background = colors[status] || '#000';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function flashHMRIndicator(): void {
|
|
88
|
+
const indicator = document.getElementById('hmr-indicator');
|
|
89
|
+
if (indicator) {
|
|
90
|
+
indicator.style.transform = 'translateX(calc(-50% - 96px)) scale(1.08)';
|
|
91
|
+
setTimeout(() => {
|
|
92
|
+
indicator.style.transform = 'translateX(calc(-50% - 96px))';
|
|
93
|
+
}, 200);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Hot reload CSS
|
|
98
|
+
async function hotReloadCSS(file: string): Promise<void> {
|
|
99
|
+
const links = document.querySelectorAll('link[rel="stylesheet"]');
|
|
100
|
+
let reloaded = false;
|
|
101
|
+
|
|
102
|
+
links.forEach(link => {
|
|
103
|
+
const href = link.getAttribute('href');
|
|
104
|
+
if (href && href.includes(file)) {
|
|
105
|
+
const newHref = href.split('?')[0] + '?' + Date.now();
|
|
106
|
+
(link as HTMLLinkElement).href = newHref;
|
|
107
|
+
reloaded = true;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (reloaded) {
|
|
112
|
+
console.warn(`CSS hot reloaded: ${file}`);
|
|
113
|
+
flashHMRIndicator();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Hot reload JavaScript (full reload for now, can optimize later)
|
|
118
|
+
async function hotReloadJS(file: string): Promise<void> {
|
|
119
|
+
console.warn(`Reloading JS: ${file}`);
|
|
120
|
+
showHMRIndicator('updating', 'Reloading...');
|
|
121
|
+
|
|
122
|
+
// For now, do a full reload
|
|
123
|
+
// Future: implement module-level HMR
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
location.reload();
|
|
126
|
+
}, 100);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Connection established
|
|
130
|
+
ws.onopen = () => {
|
|
131
|
+
isConnected = true;
|
|
132
|
+
console.warn('[HMR] enabled - Live reload active');
|
|
133
|
+
showHMRIndicator('connected', 'HMR Ready');
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Receive updates from server
|
|
137
|
+
ws.onmessage = async (event) => {
|
|
138
|
+
try {
|
|
139
|
+
const { type, file } = JSON.parse(event.data);
|
|
140
|
+
|
|
141
|
+
if (type === 'file-changed') {
|
|
142
|
+
|
|
143
|
+
// Handle different file types
|
|
144
|
+
if (file.endsWith('.css')) {
|
|
145
|
+
await hotReloadCSS(file);
|
|
146
|
+
} else if (file.endsWith('.js')) {
|
|
147
|
+
await hotReloadJS(file);
|
|
148
|
+
} else if (file.endsWith('.ts')) {
|
|
149
|
+
// TypeScript changed - just reload the page (compilation already done by server)
|
|
150
|
+
console.warn(`♻️ TS changed: ${file}`);
|
|
151
|
+
showHMRIndicator('updating', 'Reloading...');
|
|
152
|
+
sessionStorage.setItem('hmr_scroll_pos', window.scrollY.toString());
|
|
153
|
+
setTimeout(() => location.reload(), 100);
|
|
154
|
+
} else if (file.endsWith('.html')) {
|
|
155
|
+
// For HTML changes - just reload the page (simple and reliable)
|
|
156
|
+
console.warn(`♻️ HTML changed: ${file}`);
|
|
157
|
+
showHMRIndicator('updating', 'Reloading...');
|
|
158
|
+
|
|
159
|
+
// Save scroll position before reload
|
|
160
|
+
sessionStorage.setItem('hmr_scroll_pos', window.scrollY.toString());
|
|
161
|
+
|
|
162
|
+
setTimeout(() => location.reload(), 100);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('🔥 HMR error:', error);
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Connection lost
|
|
171
|
+
ws.onclose = () => {
|
|
172
|
+
if (isConnected) {
|
|
173
|
+
console.warn('🔥 HMR disconnected - Server may have restarted');
|
|
174
|
+
showHMRIndicator('disconnected', 'Reconnecting...');
|
|
175
|
+
|
|
176
|
+
// Try to reconnect after 1 second
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
location.reload();
|
|
179
|
+
}, 1000);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
ws.onerror = (error) => {
|
|
184
|
+
console.error('[HMR] connection error:', error);
|
|
185
|
+
};
|
|
186
|
+
} // end of else block for single HMR connection
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"folders": [
|
|
3
|
+
{
|
|
4
|
+
"path": "../.."
|
|
5
|
+
}
|
|
6
|
+
],
|
|
7
|
+
"settings": {
|
|
8
|
+
"liveServer.settings.file": "index.html",
|
|
9
|
+
"liveServer.settings.mount": [
|
|
10
|
+
[
|
|
11
|
+
"/",
|
|
12
|
+
"./"
|
|
13
|
+
]
|
|
14
|
+
],
|
|
15
|
+
"liveServer.settings.useWebExt": false,
|
|
16
|
+
"liveServer.settings.ignoreFiles": [
|
|
17
|
+
".vscode/**",
|
|
18
|
+
"**/*.scss",
|
|
19
|
+
"**/*.sass"
|
|
20
|
+
],
|
|
21
|
+
"liveServer.settings.proxy": {
|
|
22
|
+
"enable": false
|
|
23
|
+
},
|
|
24
|
+
"liveServer.settings.CustomBrowser": null,
|
|
25
|
+
"liveServer.settings.ChromeDebuggingAttachment": false
|
|
26
|
+
}
|
|
27
|
+
}
|