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.
Files changed (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +558 -0
  3. package/Zylaris.js.png +0 -0
  4. package/examples/default/index.html +13 -0
  5. package/examples/default/package.json +23 -0
  6. package/examples/default/src/app/about/page.tsx +18 -0
  7. package/examples/default/src/app/counter/page.tsx +22 -0
  8. package/examples/default/src/app/global.css +225 -0
  9. package/examples/default/src/app/layout.tsx +33 -0
  10. package/examples/default/src/app/page.tsx +14 -0
  11. package/examples/default/src/entry-client.tsx +87 -0
  12. package/examples/default/src/entry-server.tsx +52 -0
  13. package/examples/default/src/router.ts +60 -0
  14. package/examples/default/tsconfig.json +28 -0
  15. package/examples/default/zylaris.config.ts +24 -0
  16. package/package.json +34 -0
  17. package/packages/adapter/package.json +59 -0
  18. package/packages/adapter/src/adapters/bun.ts +215 -0
  19. package/packages/adapter/src/adapters/cloudflare.ts +278 -0
  20. package/packages/adapter/src/adapters/deno.ts +219 -0
  21. package/packages/adapter/src/adapters/netlify.ts +274 -0
  22. package/packages/adapter/src/adapters/node.ts +155 -0
  23. package/packages/adapter/src/adapters/static.ts +134 -0
  24. package/packages/adapter/src/adapters/vercel.ts +239 -0
  25. package/packages/adapter/src/index.ts +115 -0
  26. package/packages/adapter/src/lib/builder.ts +361 -0
  27. package/packages/adapter/src/types.ts +191 -0
  28. package/packages/adapter/tsconfig.json +8 -0
  29. package/packages/cli/package.json +43 -0
  30. package/packages/cli/src/bin.ts +107 -0
  31. package/packages/cli/src/commands/build.ts +197 -0
  32. package/packages/cli/src/commands/create.ts +222 -0
  33. package/packages/cli/src/commands/deploy.ts +90 -0
  34. package/packages/cli/src/commands/dev.ts +108 -0
  35. package/packages/cli/src/index.ts +6 -0
  36. package/packages/cli/tsconfig.json +9 -0
  37. package/packages/compiler/package.json +39 -0
  38. package/packages/compiler/src/index.ts +210 -0
  39. package/packages/compiler/src/jit.ts +187 -0
  40. package/packages/compiler/tsconfig.json +9 -0
  41. package/packages/core/package.json +55 -0
  42. package/packages/core/src/components.test.ts +125 -0
  43. package/packages/core/src/components.ts +181 -0
  44. package/packages/core/src/config.ts +204 -0
  45. package/packages/core/src/hooks.ts +142 -0
  46. package/packages/core/src/index.ts +59 -0
  47. package/packages/core/src/jsx-runtime.ts +46 -0
  48. package/packages/core/tsconfig.json +16 -0
  49. package/packages/dev-server/package.json +51 -0
  50. package/packages/dev-server/src/index.ts +306 -0
  51. package/packages/dev-server/src/jit-middleware.ts +78 -0
  52. package/packages/dev-server/tsconfig.json +9 -0
  53. package/packages/plugins/package.json +44 -0
  54. package/packages/plugins/src/cdn/loader.ts +275 -0
  55. package/packages/plugins/src/index.ts +238 -0
  56. package/packages/plugins/src/loaders/auto-import.ts +219 -0
  57. package/packages/plugins/src/loaders/external.ts +332 -0
  58. package/packages/plugins/src/transforms/index.ts +407 -0
  59. package/packages/plugins/src/types.ts +296 -0
  60. package/packages/plugins/tsconfig.json +8 -0
  61. package/packages/reactivity/package.json +36 -0
  62. package/packages/reactivity/src/computed.d.ts +3 -0
  63. package/packages/reactivity/src/computed.d.ts.map +1 -0
  64. package/packages/reactivity/src/computed.js +64 -0
  65. package/packages/reactivity/src/computed.js.map +1 -0
  66. package/packages/reactivity/src/computed.test.ts +83 -0
  67. package/packages/reactivity/src/computed.ts +69 -0
  68. package/packages/reactivity/src/index.d.ts +6 -0
  69. package/packages/reactivity/src/index.d.ts.map +1 -0
  70. package/packages/reactivity/src/index.js +7 -0
  71. package/packages/reactivity/src/index.js.map +1 -0
  72. package/packages/reactivity/src/index.ts +18 -0
  73. package/packages/reactivity/src/resource.d.ts +6 -0
  74. package/packages/reactivity/src/resource.d.ts.map +1 -0
  75. package/packages/reactivity/src/resource.js +43 -0
  76. package/packages/reactivity/src/resource.js.map +1 -0
  77. package/packages/reactivity/src/resource.test.ts +70 -0
  78. package/packages/reactivity/src/resource.ts +59 -0
  79. package/packages/reactivity/src/signal.d.ts +7 -0
  80. package/packages/reactivity/src/signal.d.ts.map +1 -0
  81. package/packages/reactivity/src/signal.js +145 -0
  82. package/packages/reactivity/src/signal.js.map +1 -0
  83. package/packages/reactivity/src/signal.test.ts +130 -0
  84. package/packages/reactivity/src/signal.ts +207 -0
  85. package/packages/reactivity/src/store.d.ts +4 -0
  86. package/packages/reactivity/src/store.d.ts.map +1 -0
  87. package/packages/reactivity/src/store.js +62 -0
  88. package/packages/reactivity/src/store.js.map +1 -0
  89. package/packages/reactivity/src/store.test.ts +38 -0
  90. package/packages/reactivity/src/store.ts +111 -0
  91. package/packages/reactivity/src/types.d.ts +43 -0
  92. package/packages/reactivity/src/types.d.ts.map +1 -0
  93. package/packages/reactivity/src/types.js +3 -0
  94. package/packages/reactivity/src/types.js.map +1 -0
  95. package/packages/reactivity/src/types.ts +43 -0
  96. package/packages/reactivity/tsconfig.json +9 -0
  97. package/packages/router/package.json +44 -0
  98. package/packages/router/src/components.tsx +150 -0
  99. package/packages/router/src/fs-router.ts +163 -0
  100. package/packages/router/src/index.ts +22 -0
  101. package/packages/router/src/router.test.ts +111 -0
  102. package/packages/router/src/router.ts +112 -0
  103. package/packages/router/src/types.ts +69 -0
  104. package/packages/router/tsconfig.json +10 -0
  105. package/packages/server/package.json +41 -0
  106. package/packages/server/src/action.test.ts +102 -0
  107. package/packages/server/src/action.ts +201 -0
  108. package/packages/server/src/api.ts +143 -0
  109. package/packages/server/src/index.ts +18 -0
  110. package/packages/server/src/types.ts +72 -0
  111. package/packages/server/tsconfig.json +9 -0
  112. package/pnpm-workspace.yaml +4 -0
  113. package/scripts/publish.ps1 +138 -0
  114. package/scripts/publish.sh +142 -0
  115. package/tsconfig.json +28 -0
  116. 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,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["dist", "node_modules"]
9
+ }
@@ -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
+ };