bertui 0.1.5 → 0.1.7
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/LICENSE +20 -20
- package/bin/bertui.js +7 -7
- package/index.js +35 -35
- package/package.json +5 -4
- package/src/build/css-builder.js +83 -83
- package/src/build.js +122 -122
- package/src/cli.js +65 -65
- package/src/client/compiler.js +227 -90
- package/src/config/defaultConfig.js +15 -15
- package/src/config/loadConfig.js +32 -32
- package/src/dev.js +22 -22
- package/src/logger/logger.js +17 -17
- package/src/logger/notes.md +19 -19
- package/src/router/Router.js +129 -0
- package/src/server/dev-server.js +279 -198
- package/src/styles/bertui.css +209 -209
- package/src/router/router.js +0 -216
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// src/router/Router.jsx
|
|
2
|
+
import { useState, useEffect, createContext, useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
// Router context
|
|
5
|
+
const RouterContext = createContext(null);
|
|
6
|
+
|
|
7
|
+
export function useRouter() {
|
|
8
|
+
const context = useContext(RouterContext);
|
|
9
|
+
if (!context) {
|
|
10
|
+
throw new Error('useRouter must be used within a Router component');
|
|
11
|
+
}
|
|
12
|
+
return context;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function useParams() {
|
|
16
|
+
const { params } = useRouter();
|
|
17
|
+
return params;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Router({ routes, children }) {
|
|
21
|
+
const [currentRoute, setCurrentRoute] = useState(null);
|
|
22
|
+
const [params, setParams] = useState({});
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
// Match initial route
|
|
26
|
+
matchAndSetRoute(window.location.pathname);
|
|
27
|
+
|
|
28
|
+
// Handle browser navigation
|
|
29
|
+
const handlePopState = () => {
|
|
30
|
+
matchAndSetRoute(window.location.pathname);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
window.addEventListener('popstate', handlePopState);
|
|
34
|
+
return () => window.removeEventListener('popstate', handlePopState);
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
function matchAndSetRoute(pathname) {
|
|
38
|
+
// Try exact match first (static routes)
|
|
39
|
+
for (const route of routes) {
|
|
40
|
+
if (route.type === 'static' && route.path === pathname) {
|
|
41
|
+
setCurrentRoute(route);
|
|
42
|
+
setParams({});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Try dynamic routes
|
|
48
|
+
for (const route of routes) {
|
|
49
|
+
if (route.type === 'dynamic') {
|
|
50
|
+
const pattern = route.path.replace(/\[([^\]]+)\]/g, '([^/]+)');
|
|
51
|
+
const regex = new RegExp('^' + pattern + '$');
|
|
52
|
+
const match = pathname.match(regex);
|
|
53
|
+
|
|
54
|
+
if (match) {
|
|
55
|
+
// Extract params
|
|
56
|
+
const paramNames = [...route.path.matchAll(/\[([^\]]+)\]/g)].map(m => m[1]);
|
|
57
|
+
const extractedParams = {};
|
|
58
|
+
paramNames.forEach((name, i) => {
|
|
59
|
+
extractedParams[name] = match[i + 1];
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
setCurrentRoute(route);
|
|
63
|
+
setParams(extractedParams);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// No match found - 404
|
|
70
|
+
setCurrentRoute(null);
|
|
71
|
+
setParams({});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function navigate(path) {
|
|
75
|
+
window.history.pushState({}, '', path);
|
|
76
|
+
matchAndSetRoute(path);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const routerValue = {
|
|
80
|
+
currentRoute,
|
|
81
|
+
params,
|
|
82
|
+
navigate,
|
|
83
|
+
pathname: window.location.pathname
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<RouterContext.Provider value={routerValue}>
|
|
88
|
+
{currentRoute ? (
|
|
89
|
+
<currentRoute.component />
|
|
90
|
+
) : (
|
|
91
|
+
children || <NotFound />
|
|
92
|
+
)}
|
|
93
|
+
</RouterContext.Provider>
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function Link({ to, children, className, ...props }) {
|
|
98
|
+
const { navigate } = useRouter();
|
|
99
|
+
|
|
100
|
+
function handleClick(e) {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
navigate(to);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<a href={to} onClick={handleClick} className={className} {...props}>
|
|
107
|
+
{children}
|
|
108
|
+
</a>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function NotFound() {
|
|
113
|
+
return (
|
|
114
|
+
<div style={{
|
|
115
|
+
display: 'flex',
|
|
116
|
+
flexDirection: 'column',
|
|
117
|
+
alignItems: 'center',
|
|
118
|
+
justifyContent: 'center',
|
|
119
|
+
minHeight: '100vh',
|
|
120
|
+
fontFamily: 'system-ui, sans-serif'
|
|
121
|
+
}}>
|
|
122
|
+
<h1 style={{ fontSize: '6rem', margin: 0 }}>404</h1>
|
|
123
|
+
<p style={{ fontSize: '1.5rem', color: '#666' }}>Page not found</p>
|
|
124
|
+
<a href="/" style={{ color: '#10b981', textDecoration: 'none', fontSize: '1.2rem' }}>
|
|
125
|
+
Go home
|
|
126
|
+
</a>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|
package/src/server/dev-server.js
CHANGED
|
@@ -1,199 +1,280 @@
|
|
|
1
|
-
// src/server/dev-server.js
|
|
2
|
-
import { Elysia } from 'elysia';
|
|
3
|
-
import { watch } from 'fs';
|
|
4
|
-
import { join, extname } from 'path';
|
|
5
|
-
import { existsSync } from 'fs';
|
|
6
|
-
import logger from '../logger/logger.js';
|
|
7
|
-
import { compileProject } from '../client/compiler.js';
|
|
8
|
-
|
|
9
|
-
export async function startDevServer(options = {}) {
|
|
10
|
-
const port = parseInt(options.port) || 3000;
|
|
11
|
-
const root = options.root || process.cwd();
|
|
12
|
-
const compiledDir = join(root, '.bertui', 'compiled');
|
|
13
|
-
|
|
14
|
-
const clients = new Set();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
return new Response(
|
|
96
|
-
headers: {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
1
|
+
// src/server/dev-server.js
|
|
2
|
+
import { Elysia } from 'elysia';
|
|
3
|
+
import { watch } from 'fs';
|
|
4
|
+
import { join, extname } from 'path';
|
|
5
|
+
import { existsSync } from 'fs';
|
|
6
|
+
import logger from '../logger/logger.js';
|
|
7
|
+
import { compileProject } from '../client/compiler.js';
|
|
8
|
+
|
|
9
|
+
export async function startDevServer(options = {}) {
|
|
10
|
+
const port = parseInt(options.port) || 3000;
|
|
11
|
+
const root = options.root || process.cwd();
|
|
12
|
+
const compiledDir = join(root, '.bertui', 'compiled');
|
|
13
|
+
|
|
14
|
+
const clients = new Set();
|
|
15
|
+
let hasRouter = false;
|
|
16
|
+
|
|
17
|
+
// Check if router exists
|
|
18
|
+
const routerPath = join(compiledDir, 'router.js');
|
|
19
|
+
if (existsSync(routerPath)) {
|
|
20
|
+
hasRouter = true;
|
|
21
|
+
logger.info('Router-based routing enabled');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const app = new Elysia()
|
|
25
|
+
// Main HTML route - serves all pages
|
|
26
|
+
.get('/', async () => {
|
|
27
|
+
return serveHTML(root, hasRouter);
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Catch-all route for SPA routing
|
|
31
|
+
.get('/*', async ({ params, set }) => {
|
|
32
|
+
const path = params['*'];
|
|
33
|
+
|
|
34
|
+
// Check if it's a file request
|
|
35
|
+
if (path.includes('.')) {
|
|
36
|
+
// Try to serve compiled files
|
|
37
|
+
if (path.startsWith('compiled/')) {
|
|
38
|
+
const filePath = join(compiledDir, path.replace('compiled/', ''));
|
|
39
|
+
const file = Bun.file(filePath);
|
|
40
|
+
|
|
41
|
+
if (await file.exists()) {
|
|
42
|
+
const ext = extname(path);
|
|
43
|
+
// CRITICAL FIX: Serve .js files with correct MIME type
|
|
44
|
+
const contentType = ext === '.js' || ext === '.jsx'
|
|
45
|
+
? 'application/javascript'
|
|
46
|
+
: getContentType(ext);
|
|
47
|
+
|
|
48
|
+
return new Response(await file.text(), {
|
|
49
|
+
headers: {
|
|
50
|
+
'Content-Type': contentType,
|
|
51
|
+
'Cache-Control': 'no-store'
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
set.status = 404;
|
|
58
|
+
return 'File not found';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// For non-file routes, serve the main HTML (SPA mode)
|
|
62
|
+
return serveHTML(root, hasRouter);
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
.get('/hmr-client.js', () => {
|
|
66
|
+
const script = `
|
|
67
|
+
const ws = new WebSocket('ws://localhost:${port}/hmr');
|
|
68
|
+
|
|
69
|
+
ws.onopen = () => {
|
|
70
|
+
console.log('%c🔥 BertUI HMR connected', 'color: #10b981; font-weight: bold');
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
ws.onmessage = (event) => {
|
|
74
|
+
const data = JSON.parse(event.data);
|
|
75
|
+
|
|
76
|
+
if (data.type === 'reload') {
|
|
77
|
+
console.log('%c🔄 Reloading...', 'color: #f59e0b');
|
|
78
|
+
window.location.reload();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (data.type === 'recompiling') {
|
|
82
|
+
console.log('%c⚙️ Recompiling...', 'color: #3b82f6');
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
ws.onerror = (error) => {
|
|
87
|
+
console.error('%c❌ HMR connection error', 'color: #ef4444', error);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
ws.onclose = () => {
|
|
91
|
+
console.log('%c⚠️ HMR disconnected. Refresh to reconnect.', 'color: #f59e0b');
|
|
92
|
+
};
|
|
93
|
+
`;
|
|
94
|
+
|
|
95
|
+
return new Response(script, {
|
|
96
|
+
headers: { 'Content-Type': 'application/javascript' }
|
|
97
|
+
});
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
.ws('/hmr', {
|
|
101
|
+
open(ws) {
|
|
102
|
+
clients.add(ws);
|
|
103
|
+
logger.info('Client connected to HMR');
|
|
104
|
+
},
|
|
105
|
+
close(ws) {
|
|
106
|
+
clients.delete(ws);
|
|
107
|
+
logger.info('Client disconnected from HMR');
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Serve BertUI CSS
|
|
112
|
+
.get('/styles/bertui.css', async ({ set }) => {
|
|
113
|
+
const cssPath = join(import.meta.dir, '../styles/bertui.css');
|
|
114
|
+
const file = Bun.file(cssPath);
|
|
115
|
+
|
|
116
|
+
if (!await file.exists()) {
|
|
117
|
+
set.status = 404;
|
|
118
|
+
return 'CSS file not found';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return new Response(await file.text(), {
|
|
122
|
+
headers: {
|
|
123
|
+
'Content-Type': 'text/css',
|
|
124
|
+
'Cache-Control': 'no-store'
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
// Serve compiled files with correct MIME types
|
|
130
|
+
.get('/compiled/*', async ({ params, set }) => {
|
|
131
|
+
const filepath = join(compiledDir, params['*']);
|
|
132
|
+
const file = Bun.file(filepath);
|
|
133
|
+
|
|
134
|
+
if (!await file.exists()) {
|
|
135
|
+
set.status = 404;
|
|
136
|
+
return 'File not found';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const ext = extname(filepath);
|
|
140
|
+
// CRITICAL FIX: Always serve .js files with JavaScript MIME type
|
|
141
|
+
const contentType = ext === '.js'
|
|
142
|
+
? 'application/javascript'
|
|
143
|
+
: getContentType(ext);
|
|
144
|
+
|
|
145
|
+
return new Response(await file.text(), {
|
|
146
|
+
headers: {
|
|
147
|
+
'Content-Type': contentType,
|
|
148
|
+
'Cache-Control': 'no-store'
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Serve public assets
|
|
154
|
+
.get('/public/*', async ({ params, set }) => {
|
|
155
|
+
const publicDir = join(root, 'public');
|
|
156
|
+
const filepath = join(publicDir, params['*']);
|
|
157
|
+
const file = Bun.file(filepath);
|
|
158
|
+
|
|
159
|
+
if (!await file.exists()) {
|
|
160
|
+
set.status = 404;
|
|
161
|
+
return 'File not found';
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return new Response(file);
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
.listen(port);
|
|
168
|
+
|
|
169
|
+
if (!app.server) {
|
|
170
|
+
logger.error('Failed to start server');
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
logger.success(`🚀 Server running at http://localhost:${port}`);
|
|
175
|
+
logger.info(`📁 Serving: ${root}`);
|
|
176
|
+
|
|
177
|
+
// Watch for file changes
|
|
178
|
+
setupWatcher(root, compiledDir, clients, () => {
|
|
179
|
+
hasRouter = existsSync(join(compiledDir, 'router.js'));
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return app;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function serveHTML(root, hasRouter) {
|
|
186
|
+
const html = `
|
|
187
|
+
<!DOCTYPE html>
|
|
188
|
+
<html lang="en">
|
|
189
|
+
<head>
|
|
190
|
+
<meta charset="UTF-8">
|
|
191
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
192
|
+
<title>BertUI App - Dev</title>
|
|
193
|
+
<link rel="stylesheet" href="/styles/bertui.css">
|
|
194
|
+
</head>
|
|
195
|
+
<body>
|
|
196
|
+
<div id="root"></div>
|
|
197
|
+
<script type="module" src="/hmr-client.js"></script>
|
|
198
|
+
${hasRouter
|
|
199
|
+
? '<script type="module" src="/compiled/router.js"></script>'
|
|
200
|
+
: ''
|
|
201
|
+
}
|
|
202
|
+
<script type="module" src="/compiled/main.js"></script>
|
|
203
|
+
</body>
|
|
204
|
+
</html>`;
|
|
205
|
+
|
|
206
|
+
return new Response(html, {
|
|
207
|
+
headers: { 'Content-Type': 'text/html' }
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getContentType(ext) {
|
|
212
|
+
const types = {
|
|
213
|
+
'.js': 'application/javascript',
|
|
214
|
+
'.jsx': 'application/javascript',
|
|
215
|
+
'.css': 'text/css',
|
|
216
|
+
'.html': 'text/html',
|
|
217
|
+
'.json': 'application/json',
|
|
218
|
+
'.png': 'image/png',
|
|
219
|
+
'.jpg': 'image/jpeg',
|
|
220
|
+
'.jpeg': 'image/jpeg',
|
|
221
|
+
'.gif': 'image/gif',
|
|
222
|
+
'.svg': 'image/svg+xml',
|
|
223
|
+
'.ico': 'image/x-icon',
|
|
224
|
+
'.woff': 'font/woff',
|
|
225
|
+
'.woff2': 'font/woff2',
|
|
226
|
+
'.ttf': 'font/ttf',
|
|
227
|
+
'.eot': 'application/vnd.ms-fontobject'
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
return types[ext] || 'text/plain';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function setupWatcher(root, compiledDir, clients, onRecompile) {
|
|
234
|
+
const srcDir = join(root, 'src');
|
|
235
|
+
|
|
236
|
+
if (!existsSync(srcDir)) {
|
|
237
|
+
logger.warn('src/ directory not found');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
logger.info(`👀 Watching: ${srcDir}`);
|
|
242
|
+
|
|
243
|
+
watch(srcDir, { recursive: true }, async (eventType, filename) => {
|
|
244
|
+
if (!filename) return;
|
|
245
|
+
|
|
246
|
+
const ext = extname(filename);
|
|
247
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.css'].includes(ext)) {
|
|
248
|
+
logger.info(`📝 File changed: ${filename}`);
|
|
249
|
+
|
|
250
|
+
// Notify clients
|
|
251
|
+
for (const client of clients) {
|
|
252
|
+
try {
|
|
253
|
+
client.send(JSON.stringify({ type: 'recompiling' }));
|
|
254
|
+
} catch (e) {
|
|
255
|
+
clients.delete(client);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Recompile
|
|
260
|
+
try {
|
|
261
|
+
await compileProject(root);
|
|
262
|
+
|
|
263
|
+
if (onRecompile) {
|
|
264
|
+
onRecompile();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Notify reload
|
|
268
|
+
for (const client of clients) {
|
|
269
|
+
try {
|
|
270
|
+
client.send(JSON.stringify({ type: 'reload', file: filename }));
|
|
271
|
+
} catch (e) {
|
|
272
|
+
clients.delete(client);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (error) {
|
|
276
|
+
logger.error(`Recompilation failed: ${error.message}`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
199
280
|
}
|