zylaris 1.0.2
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 +21 -0
- package/README.md +558 -0
- package/Zylaris.js.png +0 -0
- package/examples/default/index.html +13 -0
- package/examples/default/package.json +23 -0
- package/examples/default/src/app/about/page.tsx +18 -0
- package/examples/default/src/app/counter/page.tsx +22 -0
- package/examples/default/src/app/global.css +225 -0
- package/examples/default/src/app/layout.tsx +33 -0
- package/examples/default/src/app/page.tsx +14 -0
- package/examples/default/src/entry-client.tsx +87 -0
- package/examples/default/src/entry-server.tsx +52 -0
- package/examples/default/src/router.ts +60 -0
- package/examples/default/tsconfig.json +28 -0
- package/examples/default/zylaris.config.ts +24 -0
- package/package.json +34 -0
- package/packages/adapter/package.json +59 -0
- package/packages/adapter/src/adapters/bun.ts +215 -0
- package/packages/adapter/src/adapters/cloudflare.ts +278 -0
- package/packages/adapter/src/adapters/deno.ts +219 -0
- package/packages/adapter/src/adapters/netlify.ts +274 -0
- package/packages/adapter/src/adapters/node.ts +155 -0
- package/packages/adapter/src/adapters/static.ts +134 -0
- package/packages/adapter/src/adapters/vercel.ts +239 -0
- package/packages/adapter/src/index.ts +115 -0
- package/packages/adapter/src/lib/builder.ts +361 -0
- package/packages/adapter/src/types.ts +191 -0
- package/packages/adapter/tsconfig.json +8 -0
- package/packages/cli/package.json +43 -0
- package/packages/cli/src/bin.ts +107 -0
- package/packages/cli/src/commands/build.ts +197 -0
- package/packages/cli/src/commands/create.ts +222 -0
- package/packages/cli/src/commands/deploy.ts +90 -0
- package/packages/cli/src/commands/dev.ts +108 -0
- package/packages/cli/src/index.ts +6 -0
- package/packages/cli/tsconfig.json +9 -0
- package/packages/compiler/package.json +39 -0
- package/packages/compiler/src/index.ts +210 -0
- package/packages/compiler/src/jit.ts +187 -0
- package/packages/compiler/tsconfig.json +9 -0
- package/packages/core/package.json +55 -0
- package/packages/core/src/components.test.ts +125 -0
- package/packages/core/src/components.ts +181 -0
- package/packages/core/src/config.ts +204 -0
- package/packages/core/src/hooks.ts +142 -0
- package/packages/core/src/index.ts +59 -0
- package/packages/core/src/jsx-runtime.ts +46 -0
- package/packages/core/tsconfig.json +16 -0
- package/packages/dev-server/package.json +51 -0
- package/packages/dev-server/src/index.ts +306 -0
- package/packages/dev-server/src/jit-middleware.ts +78 -0
- package/packages/dev-server/tsconfig.json +9 -0
- package/packages/plugins/package.json +44 -0
- package/packages/plugins/src/cdn/loader.ts +275 -0
- package/packages/plugins/src/index.ts +238 -0
- package/packages/plugins/src/loaders/auto-import.ts +219 -0
- package/packages/plugins/src/loaders/external.ts +332 -0
- package/packages/plugins/src/transforms/index.ts +407 -0
- package/packages/plugins/src/types.ts +296 -0
- package/packages/plugins/tsconfig.json +8 -0
- package/packages/reactivity/package.json +36 -0
- package/packages/reactivity/src/computed.d.ts +3 -0
- package/packages/reactivity/src/computed.d.ts.map +1 -0
- package/packages/reactivity/src/computed.js +64 -0
- package/packages/reactivity/src/computed.js.map +1 -0
- package/packages/reactivity/src/computed.test.ts +83 -0
- package/packages/reactivity/src/computed.ts +69 -0
- package/packages/reactivity/src/index.d.ts +6 -0
- package/packages/reactivity/src/index.d.ts.map +1 -0
- package/packages/reactivity/src/index.js +7 -0
- package/packages/reactivity/src/index.js.map +1 -0
- package/packages/reactivity/src/index.ts +18 -0
- package/packages/reactivity/src/resource.d.ts +6 -0
- package/packages/reactivity/src/resource.d.ts.map +1 -0
- package/packages/reactivity/src/resource.js +43 -0
- package/packages/reactivity/src/resource.js.map +1 -0
- package/packages/reactivity/src/resource.test.ts +70 -0
- package/packages/reactivity/src/resource.ts +59 -0
- package/packages/reactivity/src/signal.d.ts +7 -0
- package/packages/reactivity/src/signal.d.ts.map +1 -0
- package/packages/reactivity/src/signal.js +145 -0
- package/packages/reactivity/src/signal.js.map +1 -0
- package/packages/reactivity/src/signal.test.ts +130 -0
- package/packages/reactivity/src/signal.ts +207 -0
- package/packages/reactivity/src/store.d.ts +4 -0
- package/packages/reactivity/src/store.d.ts.map +1 -0
- package/packages/reactivity/src/store.js +62 -0
- package/packages/reactivity/src/store.js.map +1 -0
- package/packages/reactivity/src/store.test.ts +38 -0
- package/packages/reactivity/src/store.ts +111 -0
- package/packages/reactivity/src/types.d.ts +43 -0
- package/packages/reactivity/src/types.d.ts.map +1 -0
- package/packages/reactivity/src/types.js +3 -0
- package/packages/reactivity/src/types.js.map +1 -0
- package/packages/reactivity/src/types.ts +43 -0
- package/packages/reactivity/tsconfig.json +9 -0
- package/packages/router/package.json +44 -0
- package/packages/router/src/components.tsx +150 -0
- package/packages/router/src/fs-router.ts +163 -0
- package/packages/router/src/index.ts +22 -0
- package/packages/router/src/router.test.ts +111 -0
- package/packages/router/src/router.ts +112 -0
- package/packages/router/src/types.ts +69 -0
- package/packages/router/tsconfig.json +10 -0
- package/packages/server/package.json +41 -0
- package/packages/server/src/action.test.ts +102 -0
- package/packages/server/src/action.ts +201 -0
- package/packages/server/src/api.ts +143 -0
- package/packages/server/src/index.ts +18 -0
- package/packages/server/src/types.ts +72 -0
- package/packages/server/tsconfig.json +9 -0
- package/pnpm-workspace.yaml +4 -0
- package/scripts/publish.ps1 +138 -0
- package/scripts/publish.sh +142 -0
- package/tsconfig.json +28 -0
- package/turbo.json +24 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// Zylaris Dev Server with JIT Compilation
|
|
2
|
+
|
|
3
|
+
import { createServer as createViteServer } from 'vite';
|
|
4
|
+
import { createServer as createHttpServer, Server } from 'http';
|
|
5
|
+
import connect from 'connect';
|
|
6
|
+
import { WebSocketServer, WebSocket } from 'ws';
|
|
7
|
+
import chokidar from 'chokidar';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { jitCompile, invalidateCache } from '@zylaris/compiler';
|
|
10
|
+
import fs from 'fs/promises';
|
|
11
|
+
import net from 'net';
|
|
12
|
+
|
|
13
|
+
interface DevServerOptions {
|
|
14
|
+
port?: number;
|
|
15
|
+
host?: string;
|
|
16
|
+
turbo?: boolean;
|
|
17
|
+
root?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface HMRMessage {
|
|
21
|
+
type: 'full-reload' | 'update' | 'error';
|
|
22
|
+
path?: string;
|
|
23
|
+
updates?: ModuleUpdate[];
|
|
24
|
+
error?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ModuleUpdate {
|
|
28
|
+
id: string;
|
|
29
|
+
code: string;
|
|
30
|
+
accepted: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// In-memory module cache for ultra-fast HMR
|
|
34
|
+
const moduleCache = new Map<string, { code: string; etag: string }>();
|
|
35
|
+
|
|
36
|
+
// Check if port is available
|
|
37
|
+
function isPortAvailable(port: number, host: string): Promise<boolean> {
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const tester = net.createServer()
|
|
40
|
+
.once('error', () => resolve(false))
|
|
41
|
+
.once('listening', () => {
|
|
42
|
+
tester.close();
|
|
43
|
+
resolve(true);
|
|
44
|
+
})
|
|
45
|
+
.listen(port, host);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Find available port
|
|
50
|
+
async function findAvailablePort(startPort: number, host: string): Promise<number> {
|
|
51
|
+
let port = startPort;
|
|
52
|
+
while (port < startPort + 100) {
|
|
53
|
+
if (await isPortAvailable(port, host)) {
|
|
54
|
+
return port;
|
|
55
|
+
}
|
|
56
|
+
port++;
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`No available port found between ${startPort} and ${startPort + 100}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Silent Vite logger
|
|
62
|
+
const silentLogger = {
|
|
63
|
+
info: () => {},
|
|
64
|
+
warn: () => {},
|
|
65
|
+
error: () => {},
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export async function createServer(options: DevServerOptions = {}): Promise<Server> {
|
|
69
|
+
const preferredPort = options.port || 2727;
|
|
70
|
+
const host = options.host || 'localhost';
|
|
71
|
+
const root = options.root || process.cwd();
|
|
72
|
+
|
|
73
|
+
// Find available port
|
|
74
|
+
const port = await findAvailablePort(preferredPort, host);
|
|
75
|
+
|
|
76
|
+
// Create connect app
|
|
77
|
+
const app = connect();
|
|
78
|
+
|
|
79
|
+
// JIT Compilation Middleware
|
|
80
|
+
app.use(async (req, res, next) => {
|
|
81
|
+
const url = req.url || '';
|
|
82
|
+
|
|
83
|
+
// Handle TypeScript/TSX files with JIT compilation
|
|
84
|
+
if (url.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
85
|
+
const filePath = path.join(root, 'src', url.replace(/^\//, ''));
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Check if file exists
|
|
89
|
+
const stats = await fs.stat(filePath).catch(() => null);
|
|
90
|
+
if (!stats) return next();
|
|
91
|
+
|
|
92
|
+
// Read and compile
|
|
93
|
+
const source = await fs.readFile(filePath, 'utf-8');
|
|
94
|
+
|
|
95
|
+
const startTime = performance.now();
|
|
96
|
+
const result = await jitCompile(source, filePath, {
|
|
97
|
+
target: 'es2022',
|
|
98
|
+
sourceMap: true,
|
|
99
|
+
});
|
|
100
|
+
const compileTime = performance.now() - startTime;
|
|
101
|
+
|
|
102
|
+
// Cache the result
|
|
103
|
+
const etag = `"${stats.mtime.getTime().toString(36)}"`;
|
|
104
|
+
moduleCache.set(url, { code: result.code, etag });
|
|
105
|
+
|
|
106
|
+
// Set response headers
|
|
107
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
108
|
+
res.setHeader('ETag', etag);
|
|
109
|
+
res.setHeader('X-Compile-Time', `${compileTime.toFixed(2)}ms`);
|
|
110
|
+
res.setHeader('X-JIT-Compiler', 'esbuild');
|
|
111
|
+
|
|
112
|
+
// Check if client has fresh content
|
|
113
|
+
if (req.headers['if-none-match'] === etag) {
|
|
114
|
+
res.statusCode = 304;
|
|
115
|
+
res.end();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
res.end(result.code);
|
|
120
|
+
return;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error(`[JIT Error] ${filePath}:`, error);
|
|
123
|
+
res.statusCode = 500;
|
|
124
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
125
|
+
res.end(`console.error("[Compile Error]", ${JSON.stringify(String(error))});`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
next();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Create Vite dev server for static assets and HMR (silent mode)
|
|
134
|
+
const vite = await createViteServer({
|
|
135
|
+
root,
|
|
136
|
+
server: {
|
|
137
|
+
middlewareMode: true,
|
|
138
|
+
},
|
|
139
|
+
appType: 'custom',
|
|
140
|
+
customLogger: silentLogger as any,
|
|
141
|
+
optimizeDeps: {
|
|
142
|
+
force: true,
|
|
143
|
+
esbuildOptions: {
|
|
144
|
+
target: 'es2022',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
esbuild: {
|
|
148
|
+
target: 'es2022',
|
|
149
|
+
jsx: 'automatic',
|
|
150
|
+
jsxImportSource: 'zylaris',
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Use Vite middlewares
|
|
155
|
+
app.use(vite.middlewares);
|
|
156
|
+
|
|
157
|
+
// SPA fallback - serve index.html for all routes
|
|
158
|
+
app.use(async (req, res, next) => {
|
|
159
|
+
const url = req.url || '';
|
|
160
|
+
|
|
161
|
+
// Skip API routes and file requests
|
|
162
|
+
if (url.startsWith('/api/') || url.includes('.')) {
|
|
163
|
+
return next();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
// Try to serve index.html
|
|
168
|
+
const indexPath = path.join(root, 'index.html');
|
|
169
|
+
const content = await fs.readFile(indexPath, 'utf-8');
|
|
170
|
+
res.setHeader('Content-Type', 'text/html');
|
|
171
|
+
res.end(content);
|
|
172
|
+
} catch {
|
|
173
|
+
// No index.html found
|
|
174
|
+
res.statusCode = 404;
|
|
175
|
+
res.setHeader('Content-Type', 'text/plain');
|
|
176
|
+
res.end('Cannot GET ' + url);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// Create HTTP server
|
|
181
|
+
const server = createHttpServer(app);
|
|
182
|
+
|
|
183
|
+
// Create WebSocket server for HMR on a different port
|
|
184
|
+
let wsPort: number;
|
|
185
|
+
let wss: WebSocketServer;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
wsPort = await findAvailablePort(port + 1, host);
|
|
189
|
+
wss = new WebSocketServer({ port: wsPort, host });
|
|
190
|
+
} catch (error) {
|
|
191
|
+
// Fallback: try ports up to port + 100
|
|
192
|
+
wsPort = await findAvailablePort(port + 10, host);
|
|
193
|
+
wss = new WebSocketServer({ port: wsPort, host });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const clients = new Set<WebSocket>();
|
|
197
|
+
|
|
198
|
+
wss.on('connection', (ws) => {
|
|
199
|
+
clients.add(ws);
|
|
200
|
+
ws.send(JSON.stringify({ type: 'connected' }));
|
|
201
|
+
|
|
202
|
+
ws.on('close', () => {
|
|
203
|
+
clients.delete(ws);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
ws.on('message', (data) => {
|
|
207
|
+
try {
|
|
208
|
+
const message = JSON.parse(data.toString());
|
|
209
|
+
handleHMRMessage(message, ws);
|
|
210
|
+
} catch {
|
|
211
|
+
// Ignore invalid messages
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// File watcher with debouncing
|
|
217
|
+
const watcher = chokidar.watch(path.join(root, 'src'), {
|
|
218
|
+
ignored: /node_modules|\.git/,
|
|
219
|
+
persistent: true,
|
|
220
|
+
ignoreInitial: true,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
let debounceTimer: NodeJS.Timeout | null = null;
|
|
224
|
+
|
|
225
|
+
watcher.on('change', (filePath) => {
|
|
226
|
+
// Invalidate cache
|
|
227
|
+
invalidateCache(filePath);
|
|
228
|
+
|
|
229
|
+
// Debounce HMR updates
|
|
230
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
231
|
+
debounceTimer = setTimeout(() => {
|
|
232
|
+
const relativePath = '/' + path.relative(path.join(root, 'src'), filePath);
|
|
233
|
+
moduleCache.delete(relativePath);
|
|
234
|
+
|
|
235
|
+
broadcast({
|
|
236
|
+
type: 'update',
|
|
237
|
+
path: relativePath,
|
|
238
|
+
});
|
|
239
|
+
}, 50);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
watcher.on('add', (filePath) => {
|
|
243
|
+
broadcast({
|
|
244
|
+
type: 'full-reload',
|
|
245
|
+
path: filePath,
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
watcher.on('unlink', (filePath) => {
|
|
250
|
+
invalidateCache(filePath);
|
|
251
|
+
broadcast({
|
|
252
|
+
type: 'full-reload',
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Start server
|
|
257
|
+
await new Promise<void>((resolve, reject) => {
|
|
258
|
+
server.listen(port, host, () => {
|
|
259
|
+
resolve();
|
|
260
|
+
});
|
|
261
|
+
server.once('error', reject);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Broadcast to all clients
|
|
265
|
+
function broadcast(message: HMRMessage) {
|
|
266
|
+
const data = JSON.stringify(message);
|
|
267
|
+
clients.forEach((client) => {
|
|
268
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
269
|
+
client.send(data);
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function handleHMRMessage(_message: any, _ws: WebSocket) {
|
|
275
|
+
// Handle HMR messages from client
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Warm up cache for common files
|
|
279
|
+
const commonFiles = [
|
|
280
|
+
path.join(root, 'src', 'app', 'layout.tsx'),
|
|
281
|
+
path.join(root, 'src', 'app', 'page.tsx'),
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
for (const file of commonFiles) {
|
|
285
|
+
try {
|
|
286
|
+
const source = await fs.readFile(file, 'utf-8');
|
|
287
|
+
await jitCompile(source, file);
|
|
288
|
+
} catch {
|
|
289
|
+
// File might not exist
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Return server with close method
|
|
294
|
+
return Object.assign(server, {
|
|
295
|
+
close: (callback?: () => void) => {
|
|
296
|
+
watcher.close();
|
|
297
|
+
wss.close();
|
|
298
|
+
vite.close();
|
|
299
|
+
server.close(callback);
|
|
300
|
+
moduleCache.clear();
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Export JIT utilities
|
|
306
|
+
export { invalidateCache, moduleCache };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// JIT Middleware for Dev Server
|
|
2
|
+
// Handles on-demand compilation with caching
|
|
3
|
+
|
|
4
|
+
import { jitCompile, invalidateCache } from '@zylaris/compiler';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
8
|
+
|
|
9
|
+
type Request = IncomingMessage;
|
|
10
|
+
type Response = ServerResponse;
|
|
11
|
+
type NextFunction = () => void;
|
|
12
|
+
|
|
13
|
+
interface JITMiddlewareOptions {
|
|
14
|
+
root: string;
|
|
15
|
+
cacheDir?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Create JIT middleware for connect/express
|
|
20
|
+
*/
|
|
21
|
+
export function createJITMiddleware(options: JITMiddlewareOptions) {
|
|
22
|
+
const { root } = options;
|
|
23
|
+
|
|
24
|
+
return async (req: Request, res: Response, next: NextFunction) => {
|
|
25
|
+
// Only handle JS/TS files
|
|
26
|
+
const url = req.url || '';
|
|
27
|
+
|
|
28
|
+
if (!url.match(/\.(ts|tsx|js|jsx)$/)) {
|
|
29
|
+
return next();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const filePath = path.join(root, url.replace(/^\//, ''));
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Read file
|
|
36
|
+
const source = await fs.readFile(filePath, 'utf-8');
|
|
37
|
+
|
|
38
|
+
// JIT Compile
|
|
39
|
+
const startTime = performance.now();
|
|
40
|
+
const result = await jitCompile(source, filePath, {
|
|
41
|
+
target: 'es2022',
|
|
42
|
+
sourceMap: true,
|
|
43
|
+
});
|
|
44
|
+
const compileTime = performance.now() - startTime;
|
|
45
|
+
|
|
46
|
+
// Set headers
|
|
47
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
48
|
+
res.setHeader('X-Compile-Time', `${compileTime.toFixed(2)}ms`);
|
|
49
|
+
res.setHeader('X-Compiled-By', 'zylaris-jit');
|
|
50
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
51
|
+
|
|
52
|
+
// Send compiled code
|
|
53
|
+
res.end(result.code);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// Return error as JavaScript that throws
|
|
56
|
+
const errorMessage = error instanceof Error ? error.message : 'Compilation failed';
|
|
57
|
+
res.statusCode = 500;
|
|
58
|
+
res.setHeader('Content-Type', 'application/javascript');
|
|
59
|
+
res.end(`
|
|
60
|
+
console.error('[Zylaris JIT Error]', ${JSON.stringify(errorMessage)});
|
|
61
|
+
throw new Error(${JSON.stringify(errorMessage)});
|
|
62
|
+
`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Create file watcher that invalidates cache on changes
|
|
69
|
+
*/
|
|
70
|
+
export function createCacheInvalidator(watcher: any) {
|
|
71
|
+
watcher.on('change', (filePath: string) => {
|
|
72
|
+
invalidateCache(filePath);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
watcher.on('unlink', (filePath: string) => {
|
|
76
|
+
invalidateCache(filePath);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zylaris/plugins",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Plugin system for Zylaris - Use any JS library with ease",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./auto-import": {
|
|
14
|
+
"import": "./dist/loaders/auto-import.js",
|
|
15
|
+
"types": "./dist/loaders/auto-import.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./cdn": {
|
|
18
|
+
"import": "./dist/cdn/loader.js",
|
|
19
|
+
"types": "./dist/cdn/loader.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./transforms": {
|
|
22
|
+
"import": "./dist/transforms/index.js",
|
|
23
|
+
"types": "./dist/transforms/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsc",
|
|
28
|
+
"dev": "tsc --watch",
|
|
29
|
+
"test": "vitest"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@zylaris/compiler": "workspace:*",
|
|
33
|
+
"esbuild": "^0.19.0",
|
|
34
|
+
"magic-string": "^0.30.0",
|
|
35
|
+
"unimport": "^3.7.0"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^20.0.0",
|
|
39
|
+
"typescript": "^5.3.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"zylaris": "workspace:*"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDN Loader
|
|
3
|
+
* Load libraries from CDN (esm.sh, unpkg, skypack, etc.)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CDNOptions, CDNProvider, LibraryConfig } from '../types.js';
|
|
7
|
+
|
|
8
|
+
/** Default CDN URLs */
|
|
9
|
+
const CDN_URLS: Record<CDNProvider, string> = {
|
|
10
|
+
'esm.sh': 'https://esm.sh',
|
|
11
|
+
'unpkg': 'https://unpkg.com',
|
|
12
|
+
'skypack': 'https://cdn.skypack.dev',
|
|
13
|
+
'jspm': 'https://jspm.dev',
|
|
14
|
+
'jsdelivr': 'https://cdn.jsdelivr.net/npm',
|
|
15
|
+
'cdnjs': 'https://cdnjs.cloudflare.com/ajax/libs',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** Generate CDN URL */
|
|
19
|
+
export function generateCDNUrl(
|
|
20
|
+
packageName: string,
|
|
21
|
+
options: CDNOptions = {}
|
|
22
|
+
): string {
|
|
23
|
+
const {
|
|
24
|
+
provider = 'esm.sh',
|
|
25
|
+
version,
|
|
26
|
+
query = {},
|
|
27
|
+
deps = [],
|
|
28
|
+
minify = true,
|
|
29
|
+
target = 'es2022',
|
|
30
|
+
bare = false,
|
|
31
|
+
} = options;
|
|
32
|
+
|
|
33
|
+
const baseUrl = CDN_URLS[provider];
|
|
34
|
+
const versionTag = version ? `@${version}` : '';
|
|
35
|
+
|
|
36
|
+
switch (provider) {
|
|
37
|
+
case 'esm.sh':
|
|
38
|
+
return buildEsmShUrl(baseUrl, packageName, versionTag, { query, deps, minify, target });
|
|
39
|
+
|
|
40
|
+
case 'unpkg':
|
|
41
|
+
return `${baseUrl}/${packageName}${versionTag}`;
|
|
42
|
+
|
|
43
|
+
case 'skypack':
|
|
44
|
+
return `${baseUrl}/${packageName}${versionTag}${minify ? '?min' : ''}`;
|
|
45
|
+
|
|
46
|
+
case 'jspm':
|
|
47
|
+
return `${baseUrl}/${packageName}${versionTag}`;
|
|
48
|
+
|
|
49
|
+
case 'jsdelivr':
|
|
50
|
+
return `${baseUrl}/${packageName}${versionTag}${bare ? '' : '/+esm'}`;
|
|
51
|
+
|
|
52
|
+
case 'cdnjs':
|
|
53
|
+
// cdnjs uses different path format
|
|
54
|
+
return `${baseUrl}/${packageName}/${version || 'latest'}`;
|
|
55
|
+
|
|
56
|
+
default:
|
|
57
|
+
return `${baseUrl}/${packageName}${versionTag}`;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Build esm.sh URL with options */
|
|
62
|
+
function buildEsmShUrl(
|
|
63
|
+
base: string,
|
|
64
|
+
pkg: string,
|
|
65
|
+
version: string,
|
|
66
|
+
opts: {
|
|
67
|
+
query: Record<string, string>;
|
|
68
|
+
deps: string[];
|
|
69
|
+
minify: boolean;
|
|
70
|
+
target: string;
|
|
71
|
+
}
|
|
72
|
+
): string {
|
|
73
|
+
const params = new URLSearchParams();
|
|
74
|
+
|
|
75
|
+
if (opts.target) params.set('target', opts.target);
|
|
76
|
+
if (!opts.minify) params.set('minify', 'false');
|
|
77
|
+
if (opts.deps.length > 0) params.set('deps', opts.deps.join(','));
|
|
78
|
+
|
|
79
|
+
// Add custom query params
|
|
80
|
+
for (const [key, value] of Object.entries(opts.query)) {
|
|
81
|
+
params.set(key, value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const queryString = params.toString();
|
|
85
|
+
return `${base}/${pkg}${version}${queryString ? '?' + queryString : ''}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Create import map from libraries */
|
|
89
|
+
export function createImportMap(
|
|
90
|
+
libraries: LibraryConfig[],
|
|
91
|
+
cdnOptions: CDNOptions = {}
|
|
92
|
+
): Record<string, string> {
|
|
93
|
+
const imports: Record<string, string> = {};
|
|
94
|
+
|
|
95
|
+
for (const lib of libraries) {
|
|
96
|
+
if (lib.source === 'cdn' || !lib.source) {
|
|
97
|
+
const url = generateCDNUrl(lib.name, {
|
|
98
|
+
...cdnOptions,
|
|
99
|
+
provider: lib.cdn || cdnOptions.provider,
|
|
100
|
+
version: lib.version,
|
|
101
|
+
});
|
|
102
|
+
imports[lib.name] = url;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return imports;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Generate script tag for CDN */
|
|
110
|
+
export function generateScriptTag(
|
|
111
|
+
packageName: string,
|
|
112
|
+
options: CDNOptions = {}
|
|
113
|
+
): string {
|
|
114
|
+
const url = generateCDNUrl(packageName, options);
|
|
115
|
+
return `<script type="module" src="${url}"></script>`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Generate import map script */
|
|
119
|
+
export function generateImportMapScript(
|
|
120
|
+
libraries: LibraryConfig[],
|
|
121
|
+
cdnOptions: CDNOptions = {}
|
|
122
|
+
): string {
|
|
123
|
+
const imports = createImportMap(libraries, cdnOptions);
|
|
124
|
+
|
|
125
|
+
return `<script type="importmap">
|
|
126
|
+
${JSON.stringify({ imports }, null, 2)}
|
|
127
|
+
</script>`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Resolve module from CDN with fallback */
|
|
131
|
+
export async function resolveFromCDN(
|
|
132
|
+
packageName: string,
|
|
133
|
+
providers: CDNProvider[] = ['esm.sh', 'unpkg', 'jsdelivr']
|
|
134
|
+
): Promise<{ url: string; provider: CDNProvider } | null> {
|
|
135
|
+
for (const provider of providers) {
|
|
136
|
+
const url = generateCDNUrl(packageName, { provider });
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const response = await fetch(url, { method: 'HEAD' });
|
|
140
|
+
if (response.ok) {
|
|
141
|
+
return { url, provider };
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Continue to next provider
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** CDN Cache manager */
|
|
152
|
+
export class CDNCache {
|
|
153
|
+
private cache = new Map<string, string>();
|
|
154
|
+
private maxSize: number;
|
|
155
|
+
|
|
156
|
+
constructor(maxSize = 100) {
|
|
157
|
+
this.maxSize = maxSize;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
get(key: string): string | undefined {
|
|
161
|
+
return this.cache.get(key);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
set(key: string, value: string): void {
|
|
165
|
+
if (this.cache.size >= this.maxSize) {
|
|
166
|
+
// Remove oldest entry
|
|
167
|
+
const firstKey = this.cache.keys().next().value;
|
|
168
|
+
if (firstKey) this.cache.delete(firstKey);
|
|
169
|
+
}
|
|
170
|
+
this.cache.set(key, value);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
has(key: string): boolean {
|
|
174
|
+
return this.cache.has(key);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
clear(): void {
|
|
178
|
+
this.cache.clear();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Fetch and cache module content */
|
|
183
|
+
export async function fetchModule(
|
|
184
|
+
url: string,
|
|
185
|
+
cache?: CDNCache
|
|
186
|
+
): Promise<string> {
|
|
187
|
+
const cached = cache?.get(url);
|
|
188
|
+
if (cached) return cached;
|
|
189
|
+
|
|
190
|
+
const response = await fetch(url);
|
|
191
|
+
if (!response.ok) {
|
|
192
|
+
throw new Error(`Failed to fetch ${url}: ${response.status}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const content = await response.text();
|
|
196
|
+
cache?.set(url, content);
|
|
197
|
+
|
|
198
|
+
return content;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Transform imports to CDN URLs */
|
|
202
|
+
export function transformImportsToCDN(
|
|
203
|
+
code: string,
|
|
204
|
+
importMap: Record<string, string>
|
|
205
|
+
): string {
|
|
206
|
+
// Replace bare imports with CDN URLs
|
|
207
|
+
const importRegex = /from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/g;
|
|
208
|
+
|
|
209
|
+
return code.replace(importRegex, (match, fromPath, importPath) => {
|
|
210
|
+
const path = fromPath || importPath;
|
|
211
|
+
const cdnUrl = importMap[path];
|
|
212
|
+
|
|
213
|
+
if (cdnUrl) {
|
|
214
|
+
return match.replace(path, cdnUrl);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return match;
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Detect package format from CDN */
|
|
222
|
+
export async function detectFormat(url: string): Promise<string> {
|
|
223
|
+
try {
|
|
224
|
+
const response = await fetch(url, { method: 'HEAD' });
|
|
225
|
+
const contentType = response.headers.get('content-type') || '';
|
|
226
|
+
|
|
227
|
+
if (contentType.includes('javascript')) {
|
|
228
|
+
// Try to detect ESM vs CJS
|
|
229
|
+
const content = await fetch(url).then(r => r.text());
|
|
230
|
+
if (content.includes('export ') || content.includes('import ')) {
|
|
231
|
+
return 'esm';
|
|
232
|
+
}
|
|
233
|
+
if (content.includes('module.exports') || content.includes('require(')) {
|
|
234
|
+
return 'cjs';
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return 'unknown';
|
|
239
|
+
} catch {
|
|
240
|
+
return 'unknown';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** CDN plugin for esbuild */
|
|
245
|
+
export function cdnPlugin(
|
|
246
|
+
libraries: LibraryConfig[],
|
|
247
|
+
options: CDNOptions = {}
|
|
248
|
+
) {
|
|
249
|
+
const importMap = createImportMap(libraries, options);
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
name: 'cdn-loader',
|
|
253
|
+
setup(build: { onResolve: (args: { filter: RegExp }, callback: (args: { path: string }) => { path: string; external: boolean } | null) => void }) {
|
|
254
|
+
build.onResolve({ filter: /.*/ }, (args: { path: string }) => {
|
|
255
|
+
const cdnUrl = importMap[args.path];
|
|
256
|
+
if (cdnUrl) {
|
|
257
|
+
return { path: cdnUrl, external: true };
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export default {
|
|
266
|
+
generateCDNUrl,
|
|
267
|
+
createImportMap,
|
|
268
|
+
generateScriptTag,
|
|
269
|
+
generateImportMapScript,
|
|
270
|
+
resolveFromCDN,
|
|
271
|
+
CDNCache,
|
|
272
|
+
fetchModule,
|
|
273
|
+
transformImportsToCDN,
|
|
274
|
+
cdnPlugin,
|
|
275
|
+
};
|