elit 3.4.0 → 3.4.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elit",
3
- "version": "3.4.0",
3
+ "version": "3.4.2",
4
4
  "description": "Optimized lightweight library for creating DOM elements with reactive state",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -183,6 +183,7 @@
183
183
  "homepage": "https://d-osc.github.io/elit/",
184
184
  "dependencies": {
185
185
  "esbuild": "^0.27.3",
186
+ "esbuild-obfuscator-plugin": "^0.0.5",
186
187
  "open": "^11.0.0",
187
188
  "source-map": "^0.7.6"
188
189
  },
package/src/hmr.ts CHANGED
@@ -40,6 +40,11 @@ class ElitHMR implements HMRClient {
40
40
  return;
41
41
  }
42
42
 
43
+ // Skip HMR in preview mode (no WebSocket server is running)
44
+ if ((window as any).__ELIT_MODE__ === 'preview') {
45
+ return;
46
+ }
47
+
43
48
  this.connect();
44
49
  }
45
50
 
package/src/server.ts CHANGED
@@ -877,6 +877,8 @@ export function security(): Middleware {
877
877
  ctx.res.setHeader('X-Frame-Options', 'DENY');
878
878
  ctx.res.setHeader('X-XSS-Protection', '1; mode=block');
879
879
  ctx.res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
880
+ ctx.res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
881
+ ctx.res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
880
882
  await next();
881
883
  };
882
884
  }
@@ -1534,8 +1536,8 @@ export default css;
1534
1536
  const result = await Deno.emit(resolvedPath, {
1535
1537
  check: false,
1536
1538
  compilerOptions: {
1537
- sourceMap: true,
1538
- inlineSourceMap: true,
1539
+ sourceMap: config.mode !== 'preview',
1540
+ inlineSourceMap: config.mode !== 'preview',
1539
1541
  target: 'ES2020',
1540
1542
  module: 'esnext'
1541
1543
  },
@@ -1558,16 +1560,35 @@ export default css;
1558
1560
  transpiled = transpiler.transformSync(content.toString());
1559
1561
  } else {
1560
1562
  // Node.js - use esbuild
1561
- const { transformSync } = await import('esbuild');
1562
1563
  const loader = ext === '.tsx' ? 'tsx' : 'ts';
1563
- const result = transformSync(content.toString(), {
1564
- loader: loader as any,
1565
- format: 'esm',
1566
- target: 'es2020',
1567
- sourcemap: 'inline'
1568
- });
1564
+ const { transformSync } = await import('esbuild');
1569
1565
 
1570
- transpiled = result.code;
1566
+ if (config.mode === 'preview') {
1567
+ // Preview mode: transpile then obfuscate via esbuild-obfuscator-plugin's
1568
+ // underlying javascript-obfuscator library
1569
+ const { default: JavaScriptObfuscator } = await import('javascript-obfuscator');
1570
+
1571
+ const tsResult = transformSync(content.toString(), {
1572
+ loader: loader as any,
1573
+ format: 'esm',
1574
+ target: 'es2020',
1575
+ sourcemap: false
1576
+ });
1577
+
1578
+ transpiled = JavaScriptObfuscator.obfuscate(tsResult.code, {
1579
+ compact: true,
1580
+ renameGlobals: false
1581
+ }).getObfuscatedCode();
1582
+ } else {
1583
+ // Dev mode: transpile with inline source maps
1584
+ const result = transformSync(content.toString(), {
1585
+ loader: loader as any,
1586
+ format: 'esm',
1587
+ target: 'es2020',
1588
+ sourcemap: 'inline'
1589
+ });
1590
+ transpiled = result.code;
1591
+ }
1571
1592
  }
1572
1593
 
1573
1594
  // Rewrite .ts imports to .js for browser compatibility
@@ -1604,7 +1625,7 @@ export default css;
1604
1625
  // Inject HMR client and import map for HTML files
1605
1626
  if (ext === '.html') {
1606
1627
  const wsPath = normalizeBasePath(client.basePath);
1607
- const hmrScript = createHMRScript(config.port, wsPath);
1628
+ const hmrScript = config.mode !== 'preview' ? createHMRScript(config.port, wsPath) : '';
1608
1629
  let html = content.toString();
1609
1630
 
1610
1631
  // If SSR is configured, extract and inject styles from SSR
@@ -1657,7 +1678,8 @@ export default css;
1657
1678
 
1658
1679
  // Inject import map and SSR styles into <head>
1659
1680
  const elitImportMap = await createElitImportMap(client.root, basePath, client.mode);
1660
- const headInjection = ssrStyles ? `${ssrStyles}\n${elitImportMap}` : elitImportMap;
1681
+ const modeScript = config.mode === 'preview' ? '<script>window.__ELIT_MODE__=\'preview\';</script>' : '';
1682
+ const headInjection = `${modeScript}${ssrStyles ? '\n' + ssrStyles : ''}\n${elitImportMap}`;
1661
1683
  html = html.includes('</head>') ? html.replace('</head>', `${headInjection}</head>`) : html;
1662
1684
  html = html.includes('</body>') ? html.replace('</body>', `${hmrScript}</body>`) : html + hmrScript;
1663
1685
  content = Buffer.from(html);
@@ -1670,7 +1692,11 @@ export default css;
1670
1692
 
1671
1693
  const headers: any = {
1672
1694
  'Content-Type': mimeType,
1673
- 'Cache-Control': cacheControl
1695
+ 'Cache-Control': cacheControl,
1696
+ 'X-Content-Type-Options': 'nosniff',
1697
+ 'X-Frame-Options': 'DENY',
1698
+ 'X-XSS-Protection': '1; mode=block',
1699
+ 'Referrer-Policy': 'strict-origin-when-cross-origin'
1674
1700
  };
1675
1701
 
1676
1702
  // Apply gzip compression for text-based files
@@ -1726,15 +1752,23 @@ export default css;
1726
1752
  const basePath = normalizeBasePath(client.basePath);
1727
1753
  html = rewriteRelativePaths(html, basePath);
1728
1754
 
1729
- // Inject HMR script
1730
- const hmrScript = createHMRScript(config.port, basePath);
1755
+ // Inject HMR script (dev mode only)
1756
+ const hmrScript = config.mode !== 'preview' ? createHMRScript(config.port, basePath) : '';
1731
1757
 
1732
1758
  // Inject import map in head, HMR script in body
1733
1759
  const elitImportMap = await createElitImportMap(client.root, basePath, client.mode);
1734
- html = html.includes('</head>') ? html.replace('</head>', `${elitImportMap}</head>`) : html;
1760
+ const modeScript = config.mode === 'preview' ? '<script>window.__ELIT_MODE__=\'preview\';</script>\n' : '';
1761
+ html = html.includes('</head>') ? html.replace('</head>', `${modeScript}${elitImportMap}</head>`) : html;
1735
1762
  html = html.includes('</body>') ? html.replace('</body>', `${hmrScript}</body>`) : html + hmrScript;
1736
1763
 
1737
- res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-cache, no-store, must-revalidate' });
1764
+ res.writeHead(200, {
1765
+ 'Content-Type': 'text/html',
1766
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
1767
+ 'X-Content-Type-Options': 'nosniff',
1768
+ 'X-Frame-Options': 'DENY',
1769
+ 'X-XSS-Protection': '1; mode=block',
1770
+ 'Referrer-Policy': 'strict-origin-when-cross-origin'
1771
+ });
1738
1772
  res.end(html);
1739
1773
 
1740
1774
  if (config.logging) console.log(`[200] SSR rendered`);
@@ -1744,111 +1778,117 @@ export default css;
1744
1778
  }
1745
1779
  }
1746
1780
 
1747
- // WebSocket Server for HMR
1748
- const wss = new WebSocketServer({ server });
1781
+ // WebSocket Server for HMR - only in dev mode (not needed for preview)
1782
+ let wss: any = null;
1783
+ if (config.mode !== 'preview') {
1784
+ wss = new WebSocketServer({ server });
1749
1785
 
1750
- if (config.logging) {
1751
- console.log('[HMR] WebSocket server initialized');
1752
- }
1786
+ if (config.logging) {
1787
+ console.log('[HMR] WebSocket server initialized');
1788
+ }
1753
1789
 
1754
- wss.on('connection', (ws: WebSocket, req) => {
1755
- wsClients.add(ws);
1790
+ wss.on('connection', (ws: WebSocket, req: IncomingMessage) => {
1791
+ wsClients.add(ws);
1756
1792
 
1757
- const message: HMRMessage = { type: 'connected', timestamp: Date.now() };
1758
- ws.send(JSON.stringify(message));
1793
+ const message: HMRMessage = { type: 'connected', timestamp: Date.now() };
1794
+ ws.send(JSON.stringify(message));
1759
1795
 
1760
- if (config.logging) {
1761
- console.log('[HMR] Client connected from', req.socket.remoteAddress);
1762
- }
1796
+ if (config.logging) {
1797
+ console.log('[HMR] Client connected from', req.socket.remoteAddress);
1798
+ }
1763
1799
 
1764
- // Handle incoming messages
1765
- ws.on('message', (data: string) => {
1766
- try {
1767
- const msg = JSON.parse(data.toString());
1800
+ // Handle incoming messages
1801
+ ws.on('message', (data: string) => {
1802
+ try {
1803
+ const msg = JSON.parse(data.toString());
1768
1804
 
1769
- // Handle state subscription
1770
- if (msg.type === 'state:subscribe') {
1771
- stateManager.subscribe(msg.key, ws);
1772
- if (config.logging) {
1773
- console.log(`[State] Client subscribed to "${msg.key}"`);
1805
+ // Handle state subscription
1806
+ if (msg.type === 'state:subscribe') {
1807
+ stateManager.subscribe(msg.key, ws);
1808
+ if (config.logging) {
1809
+ console.log(`[State] Client subscribed to "${msg.key}"`);
1810
+ }
1774
1811
  }
1775
- }
1776
1812
 
1777
- // Handle state unsubscribe
1778
- else if (msg.type === 'state:unsubscribe') {
1779
- stateManager.unsubscribe(msg.key, ws);
1780
- if (config.logging) {
1781
- console.log(`[State] Client unsubscribed from "${msg.key}"`);
1813
+ // Handle state unsubscribe
1814
+ else if (msg.type === 'state:unsubscribe') {
1815
+ stateManager.unsubscribe(msg.key, ws);
1816
+ if (config.logging) {
1817
+ console.log(`[State] Client unsubscribed from "${msg.key}"`);
1818
+ }
1782
1819
  }
1783
- }
1784
1820
 
1785
- // Handle state change from client
1786
- else if (msg.type === 'state:change') {
1787
- stateManager.handleStateChange(msg.key, msg.value);
1821
+ // Handle state change from client
1822
+ else if (msg.type === 'state:change') {
1823
+ stateManager.handleStateChange(msg.key, msg.value);
1824
+ if (config.logging) {
1825
+ console.log(`[State] Client updated "${msg.key}"`);
1826
+ }
1827
+ }
1828
+ } catch (error) {
1788
1829
  if (config.logging) {
1789
- console.log(`[State] Client updated "${msg.key}"`);
1830
+ console.error('[WebSocket] Message parse error:', error);
1790
1831
  }
1791
1832
  }
1792
- } catch (error) {
1833
+ });
1834
+
1835
+ ws.on('close', () => {
1836
+ wsClients.delete(ws);
1837
+ stateManager.unsubscribeAll(ws);
1793
1838
  if (config.logging) {
1794
- console.error('[WebSocket] Message parse error:', error);
1839
+ console.log('[HMR] Client disconnected');
1795
1840
  }
1796
- }
1841
+ });
1797
1842
  });
1843
+ }
1798
1844
 
1799
- ws.on('close', () => {
1800
- wsClients.delete(ws);
1801
- stateManager.unsubscribeAll(ws);
1802
- if (config.logging) {
1803
- console.log('[HMR] Client disconnected');
1804
- }
1845
+ // File watcher - only in dev mode (not needed for preview)
1846
+ let watcher: any = null;
1847
+ if (config.mode !== 'preview') {
1848
+ const watchPaths = normalizedClients.flatMap(client =>
1849
+ config.watch.map(pattern => join(client.root, pattern))
1850
+ );
1851
+
1852
+ watcher = watch(watchPaths, {
1853
+ ignored: (path: string) => config.ignore.some(pattern => path.includes(pattern.replace('/**', '').replace('**/', ''))),
1854
+ ignoreInitial: true,
1855
+ persistent: true
1805
1856
  });
1806
- });
1807
-
1808
- // File watcher - watch all client roots
1809
- const watchPaths = normalizedClients.flatMap(client =>
1810
- config.watch.map(pattern => join(client.root, pattern))
1811
- );
1812
1857
 
1813
- const watcher = watch(watchPaths, {
1814
- ignored: (path: string) => config.ignore.some(pattern => path.includes(pattern.replace('/**', '').replace('**/', ''))),
1815
- ignoreInitial: true,
1816
- persistent: true
1817
- });
1818
-
1819
- watcher.on('change', (path: string) => {
1820
- if (config.logging) console.log(`[HMR] File changed: ${path}`);
1821
- const message = JSON.stringify({ type: 'update', path, timestamp: Date.now() } as HMRMessage);
1822
- // Broadcast to all open clients with error handling
1823
- wsClients.forEach(client => {
1824
- if (client.readyState === ReadyState.OPEN) {
1825
- client.send(message, {}, (err?: Error) => {
1826
- // Silently ignore connection errors during HMR
1827
- const code = (err as any)?.code;
1828
- if (code === 'ECONNABORTED' || code === 'ECONNRESET' || code === 'EPIPE' || code === 'WS_NOT_OPEN') {
1829
- // Client disconnected - will be removed from clients set by close event
1830
- return;
1831
- }
1832
- });
1833
- }
1858
+ watcher.on('change', (path: string) => {
1859
+ if (config.logging) console.log(`[HMR] File changed: ${path}`);
1860
+ const message = JSON.stringify({ type: 'update', path, timestamp: Date.now() } as HMRMessage);
1861
+ // Broadcast to all open clients with error handling
1862
+ wsClients.forEach(client => {
1863
+ if (client.readyState === ReadyState.OPEN) {
1864
+ client.send(message, {}, (err?: Error) => {
1865
+ // Silently ignore connection errors during HMR
1866
+ const code = (err as any)?.code;
1867
+ if (code === 'ECONNABORTED' || code === 'ECONNRESET' || code === 'EPIPE' || code === 'WS_NOT_OPEN') {
1868
+ // Client disconnected - will be removed from clients set by close event
1869
+ return;
1870
+ }
1871
+ });
1872
+ }
1873
+ });
1834
1874
  });
1835
- });
1836
1875
 
1837
- watcher.on('add', (path: string) => {
1838
- if (config.logging) console.log(`[HMR] File added: ${path}`);
1839
- const message = JSON.stringify({ type: 'update', path, timestamp: Date.now() } as HMRMessage);
1840
- wsClients.forEach(client => {
1841
- if (client.readyState === ReadyState.OPEN) client.send(message, {});
1876
+ watcher.on('add', (path: string) => {
1877
+ if (config.logging) console.log(`[HMR] File added: ${path}`);
1878
+ const message = JSON.stringify({ type: 'update', path, timestamp: Date.now() } as HMRMessage);
1879
+ wsClients.forEach(client => {
1880
+ if (client.readyState === ReadyState.OPEN) client.send(message, {});
1881
+ });
1842
1882
  });
1843
- });
1844
1883
 
1845
- watcher.on('unlink', (path: string) => {
1846
- if (config.logging) console.log(`[HMR] File removed: ${path}`);
1847
- const message = JSON.stringify({ type: 'reload', path, timestamp: Date.now() } as HMRMessage);
1848
- wsClients.forEach(client => {
1849
- if (client.readyState === ReadyState.OPEN) client.send(message, {});
1884
+ watcher.on('unlink', (path: string) => {
1885
+ if (config.logging) console.log(`[HMR] File removed: ${path}`);
1886
+ const message = JSON.stringify({ type: 'reload', path, timestamp: Date.now() } as HMRMessage);
1887
+ wsClients.forEach(client => {
1888
+ if (client.readyState === ReadyState.OPEN) client.send(message, {});
1889
+ });
1850
1890
  });
1851
- });
1891
+ }
1852
1892
 
1853
1893
  // Increase max listeners to prevent warnings
1854
1894
  server.setMaxListeners(20);
@@ -1873,7 +1913,7 @@ export default css;
1873
1913
  }
1874
1914
  }
1875
1915
 
1876
- console.log(`\n[HMR] Watching for file changes...\n`);
1916
+ if (config.mode !== 'preview') console.log(`\n[HMR] Watching for file changes...\n`);
1877
1917
  }
1878
1918
 
1879
1919
  // Open browser to first client
@@ -1897,10 +1937,12 @@ export default css;
1897
1937
  if (isClosing) return;
1898
1938
  isClosing = true;
1899
1939
  if (config.logging) console.log('\n[Server] Shutting down...');
1900
- await watcher.close();
1901
- wss.close();
1902
- wsClients.forEach(client => client.close());
1903
- wsClients.clear();
1940
+ if (watcher) await watcher.close();
1941
+ if (wss) {
1942
+ wss.close();
1943
+ wsClients.forEach(client => client.close());
1944
+ wsClients.clear();
1945
+ }
1904
1946
  return new Promise<void>((resolve) => {
1905
1947
  server.close(() => {
1906
1948
  if (config.logging) console.log('[Server] Closed');