Releases: elitjs/elit
Releases · elitjs/elit
Release list
v4.0.4
Added
- HTTPS dev/preview server support -
dev.https/preview.https(previously declared but ignored) now serves the dev/preview server over HTTPS.https: truegenerates a self-signed dev certificate;https: { cert, key }uses real certs (PEM content or file paths). CLI flags--https,--cert <path>,--key <path>added todevandpreview. The WebSocket server stays attached to the same HTTPS server, so WSS works on the same origin.@elitjs/httpsServer now proxies theupgradeevent (previously onlyerror/close), enabling WebSocket-over-HTTPS on Node and fixing the standalone@elitjs/wsspackage.- WebSocket-over-HTTPS also works on Bun (
@elitjs/httpsBun branch now mirrors@elitjs/http's WebSocket plumbing). Deno still has no WebSocket support at all, not only over HTTPS — tracked as a separate effort. - Self-signed dev certs via
selfsigned@2(sync API, keepscreateDevServersynchronous and avoids a breaking public-API change).
Changed
- Preview mode minifies browser modules with esbuild instead of obfuscating them -
elit previewpreviously ran transpiled.ts/.tsxmodules throughjavascript-obfuscator(string-array obfuscation); preview output is now minified (identifiers + whitespace) by the same esbuild transform that transpiles the module. Removes thejavascript-obfuscatordependency (~11.9 MB unpacked, 20 transitive deps) from@elitjs/server,@elitjs/dev-build,@elitjs/preview-build, and the rootelitpackage. Trade-off: minification mangles code structure but, unlike string-array obfuscation, leaves string literals readable. Dev mode is unchanged. - Client package
browserexports now point at the ES module build -@elitjs/el,@elitjs/dom,@elitjs/style,@elitjs/state,@elitjs/router, and@elitjs/render-contextmapped theirbrowsercondition todist/index.iife.js, which has no exports; every bundler build with browser platform resolution gotundefinedfor all named imports (e.g.import { div } from '@elitjs/el'). Thebrowsercondition now resolves todist/index.mjs.
Fixed
- Client WebSocket behind TLS-terminating proxies (e.g. Cloudflare) - the
@elitjs/hmrclient and the dev-server-injected HMR script derived the WebSocket URL from the dev server's host:port instead of the page origin, so behind a proxy the public origin differs from the server's port and everywss://connection failed. Both now usewindow.location.host(same origin); the hmr client also targets the/__elit_wsendpoint it previously omitted. SharedStatereconnect spam - it retried every 1s forever and logged each failure. Now uses capped exponential backoff (1s→30s, max 10 attempts), stays quiet per attempt, warns once when giving up, and stops reconnecting afterdestroy()even if a close event arrives late.reactive()sometimes didn't update - the DOM renderer firesrefasynchronously (viasetTimeout), so the first state change could land beforereactive()'s element reference was set; the update was silently dropped and the element stayed on its initial snapshot.reactive()andreactiveAs()now track the missed update and replay it once the element exists. Also,scheduleRAFUpdatefalls back tosetTimeoutwhen the tab is hidden, so state-driven updates keep flowing in background tabs whererequestAnimationFrameis paused.@elitjs/serverbundled a broken inlined copy of esbuild - the server's tsup config did not markesbuildas external, so the runtimeimport('esbuild')intranspileNodeBrowserModuleresolved to an inlined chunk that fails to load ("Dynamic require of fs is not supported"), silently disabling every esbuild-backed transpile path (.tsxfiles, the.tsesbuild fallback, and now preview minification).esbuildis now external and resolved fromnode_modulesat runtime, matching@elitjs/dev-buildand@elitjs/preview-build.elit testcould not load test files importing Node builtins or packages - the test runner'snodeRequireprobe (typeof require !== 'undefined') was rewritten by esbuild's require shim into a tautology, so it always used the throwing shim instead ofcreateRequire(import.meta.url). The same pattern in@elitjs/ws,@elitjs/chokidar,@elitjs/fs,@elitjs/http, and@elitjs/httpscrashed those packages whenever the test runner evaluated their source (createRequire(undefined)). All sites now try__filenamefirst (present when the runner evals transpiled CJS) and fall back toimport.meta.url(real ESM builds).elit testleaked mocked browser globals across test files - example tests that mockdocument,fetch,window,EventSource, timers, etc. at module scope left the mocks installed for every later file in the same process, which broke tsup'simport.meta.urlCJS shim ("Invalid URL" loadingcli.test.ts), brokedom.test.tswith a partialdocument, and hung thee2e-*suites on a never-firing mockedsetTimeout. The runner now snapshots these globals before the first test file and restores them before every subsequent file.- Lifecycle hooks were file-global instead of suite-scoped -
beforeEach/afterEach/beforeAll/afterAllregistered inside onedescriberan for every test in the file, so hooks from sibling describes overwrote each other's global mocks (e.g.state.test.ts's WebSocket mock being replaced before its assertions ran). Hooks now attach to the suite where they are declared;beforeEach/afterEachcompose down the suite ancestry (outer→inner / inner→outer) andbeforeAll/afterAllrun once per suite. - PM record writes crashed the process -
writePmRecordwroteapps/<id>.jsonwithout creating its directory, so the process-manager runner crashed with ENOENT when the record directory did not exist yet. It now creates the directory recursively. - E2E request interception could kill the test host -
Fetch.requestPauseddispatch discarded thehandlePausedRequestpromise, so a paused request that failed to resume (mocked session, command timeout) escaped as an unhandled rejection and crashed the wholeelit testprocess before the summary printed. The dispatch now catches and reports these failures. - PM proxy blocked its own managed instances - the SSRF guard rejected loopback targets (
127.0.0.1,::1), but the PM proxy's primary job is forwarding to the local instances it spawns. Loopback is now allowed while all other private/reserved ranges stay blocked. - Stale tests after the refactors -
database.test.tsnow targetspackages/function-store(renamed frompackages/database);html-entrypoint.test.tspoints at the restructuredcreate-elittemplates (basic-example);dom.test.tsreads the expectedelit-versionfrompackages/dom/package.jsoninstead of a hardcoded version;desktop-cli.test.tscompares resolved paths viapath.resolveinstead of hardcoded POSIX strings (the functions intentionally return native separators); and the full-db client test expects the current/main.jsoutput layout.
Full Changelog: v4.0.3...v4.0.4
v4.0.2
What's Changed
- chore: remove old publish workflow and add new automated publish work… by @nattbl49 in #4
- Fix website deploy workflow failures on Pages setup and clean-checkout builds by @nattbl49 with @Copilot in #5
- refactor: update import paths to use package structure and clean up u… by @nattbl49 in #6
- chore: update version to 4.0.1 and add new dependencies in package.js… by @nattbl49 in #7
- chore: update package.json to include @elitjs/server in dependencies … by @nattbl49 in #8
- feat: add @elitjs/fs dependency and update dev tools configuration by @nattbl49 in #9
- ๊update: version 4.0.2 by @n-devs in #10
- Feature/v4.0.2 by @n-devs in #12
- update: v4.0.2 by @n-devs in #13
- fix(ci): allow same-version npm version in publish workflow by @n-devs in #14
New Contributors
- @nattbl49 made their first contribution in #4
- @nattbl49 with @Copilot made their first contribution in #5
Full Changelog: v4.0.0...v4.0.2
[4.0.2] - 2026-07-23
Fixed
reactive()no longer drops input focus on each keystroke - when an<input>,<textarea>, or<select>lived anywhere inside areactive()block, every state update destroyed and recreated the element, so users had to re-click the input before each character ("พิมพ์ได้ทีละครั้ง"). The update path now reconciles children positionally: nodes whose text content or tagName matches the previous render are patched in place (props + recursive children), and only genuinely different nodes are replaced. Comment placeholders left by null/conditional reactive results are skipped so they keep working as anchors.- Applies to both
reactive()andreactiveAs()update paths. - Verified by simulating four consecutive
inputevents on an input nested insidereactive(): focus and the underlying<input>DOM node are preserved across all keystrokes, while sibling reactive content (a mirror span) stays in sync.
- Applies to both
- Stale props on reused elements -
updateElementPropsnow diffs the new props against the previous render's props (tracked via__elitPrevPropson the element) and removes any prop that is no longer present. Without this, properties and event handlers from earlier renders accumulated on reused DOM nodes: a removedonclickwould keep firing (memory leak + wrong behavior), a removedclasswould linger, and a removedvalue/checkedon a form control would keep the old input state. Initial-renderapplyProps(@elitjs/dom) also seeds__elitPrevPropsso the first reactive update can diff against it. - Form-control props during reactive updates -
updateElementPropsnow assignsvalueon<input>/<textarea>/<select>andcheckedon<input>via the DOM property (with an equality check) instead ofsetAttribute. The previoussetAttribute('value', …)path was semantically wrong for live form values and could cause cursor jumps when the input was the root of areactive()block. - State-as-prop regression -
updateElementPropsnow skipsStatevalues (they already have their own subscription from the initialapplyPropspass) instead of coercing them to"[object Object]"viaString(), which broke form controls that receivedvalue: someState. - Snapshot-based prev-props storage - the previous-props cache used to diff reused DOM nodes is now stored in a shared
WeakMap<HTMLElement | SVGElement, Props>exported from@elitjs/dom(instead of an__elitPrevPropsexpando property on the element). Removes DOM pollution andas anycasts; entries are GC'd with the element. The accompanyingsnapshotPropshelper shallow-clones nested object/array values (notablystyle, arrays passed toclass/className, anddangerouslySetInnerHTML), so diffing detects in-place mutation of reused prop objects — e.g. a caller doingdelete sharedStyle.fontSizebetween renders now correctly clearsfont-sizeon the element.
Security
- XSS audit of the reactive update path - confirmed that all assignments in the new reconcile/patch logic are XSS-safe by construction: text children use
textContentandcreateTextNode(not parsed as HTML), element props usesetAttributeand direct property assignment (also not HTML-parsed). The onlyinnerHTMLwrite is for the explicitdangerouslySetInnerHTMLprop (user opt-in, same contract as React);updateElementPropsnow handles that key for consistency with initial render. Verified with a<img src=x onerror=…>payload that was rendered as visible text without any script execution.
Changed
- Version metadata refresh - release-facing version references across
package.json,package-lock.json, all@elitjs/*subpackages,create-elit,create-elit-skills, and the docs hero on the website now trackv4.0.2. Rebuilt@elitjs/domand@elitjs/statedist so the baked-inELIT_VERSIONconstant matches.
v4.0.2-rc
v4.0.2-beta
What's Changed
Full Changelog: v4.0.1-rc...v4.0.2-beta
[4.0.2] - 2026-07-23
Fixed
reactive()no longer drops input focus on each keystroke - when an<input>,<textarea>, or<select>lived anywhere inside areactive()block, every state update destroyed and recreated the element, so users had to re-click the input before each character ("พิมพ์ได้ทีละครั้ง"). The update path now reconciles children positionally: nodes whose text content or tagName matches the previous render are patched in place (props + recursive children), and only genuinely different nodes are replaced. Comment placeholders left by null/conditional reactive results are skipped so they keep working as anchors.- Applies to both
reactive()andreactiveAs()update paths. - Verified by simulating four consecutive
inputevents on an input nested insidereactive(): focus and the underlying<input>DOM node are preserved across all keystrokes, while sibling reactive content (a mirror span) stays in sync.
- Applies to both
- Stale props on reused elements -
updateElementPropsnow diffs the new props against the previous render's props (tracked via__elitPrevPropson the element) and removes any prop that is no longer present. Without this, properties and event handlers from earlier renders accumulated on reused DOM nodes: a removedonclickwould keep firing (memory leak + wrong behavior), a removedclasswould linger, and a removedvalue/checkedon a form control would keep the old input state. Initial-renderapplyProps(@elitjs/dom) also seeds__elitPrevPropsso the first reactive update can diff against it. - Form-control props during reactive updates -
updateElementPropsnow assignsvalueon<input>/<textarea>/<select>andcheckedon<input>via the DOM property (with an equality check) instead ofsetAttribute. The previoussetAttribute('value', …)path was semantically wrong for live form values and could cause cursor jumps when the input was the root of areactive()block. - State-as-prop regression -
updateElementPropsnow skipsStatevalues (they already have their own subscription from the initialapplyPropspass) instead of coercing them to"[object Object]"viaString(), which broke form controls that receivedvalue: someState. - Snapshot-based prev-props storage - the previous-props cache used to diff reused DOM nodes is now stored in a shared
WeakMap<HTMLElement | SVGElement, Props>exported from@elitjs/dom(instead of an__elitPrevPropsexpando property on the element). Removes DOM pollution andas anycasts; entries are GC'd with the element. The accompanyingsnapshotPropshelper shallow-clones nested object/array values (notablystyle, arrays passed toclass/className, anddangerouslySetInnerHTML), so diffing detects in-place mutation of reused prop objects — e.g. a caller doingdelete sharedStyle.fontSizebetween renders now correctly clearsfont-sizeon the element.
Security
- XSS audit of the reactive update path - confirmed that all assignments in the new reconcile/patch logic are XSS-safe by construction: text children use
textContentandcreateTextNode(not parsed as HTML), element props usesetAttributeand direct property assignment (also not HTML-parsed). The onlyinnerHTMLwrite is for the explicitdangerouslySetInnerHTMLprop (user opt-in, same contract as React);updateElementPropsnow handles that key for consistency with initial render. Verified with a<img src=x onerror=…>payload that was rendered as visible text without any script execution.
Changed
- Version metadata refresh - release-facing version references across
package.json,package-lock.json, all@elitjs/*subpackages,create-elit,create-elit-skills, and the docs hero on the website now trackv4.0.2. Rebuilt@elitjs/domand@elitjs/statedist so the baked-inELIT_VERSIONconstant matches.
v4.0.1-rc
Full Changelog: v4.0.1-beta...v4.0.1-rc
v4.0.1-beta
What's Changed
- chore: remove old publish workflow and add new automated publish work… by @nattbl49 in #4
- Fix website deploy workflow failures on Pages setup and clean-checkout builds by @nattbl49 with @Copilot in #5
- refactor: update import paths to use package structure and clean up u… by @nattbl49 in #6
- chore: update version to 4.0.1 and add new dependencies in package.js… by @nattbl49 in #7
- chore: update package.json to include @elitjs/server in dependencies … by @nattbl49 in #8
- feat: add @elitjs/fs dependency and update dev tools configuration by @nattbl49 in #9
New Contributors
- @nattbl49 made their first contribution in #4
- @nattbl49 with @Copilot made their first contribution in #5
Full Changelog: v4.0.0...v4.0.1-beta
v4.0.0
What's Changed
- feat: remove GitHub Actions workflow for publishing to NPM by @n-devs in #1
- chore: update package.json and package-lock.json across multiple pack… by @n-devs in #2
New Contributors
Full Changelog: https://github.com/elitjs/elit/commits/v4.0.0