Skip to content

Releases: elitjs/elit

v4.0.4

Choose a tag to compare

@github-actions github-actions released this 15 Aug 19:07

Added

  • HTTPS dev/preview server support - dev.https / preview.https (previously declared but ignored) now serves the dev/preview server over HTTPS. https: true generates a self-signed dev certificate; https: { cert, key } uses real certs (PEM content or file paths). CLI flags --https, --cert <path>, --key <path> added to dev and preview. The WebSocket server stays attached to the same HTTPS server, so WSS works on the same origin.
    • @elitjs/https Server now proxies the upgrade event (previously only error/close), enabling WebSocket-over-HTTPS on Node and fixing the standalone @elitjs/wss package.
    • WebSocket-over-HTTPS also works on Bun (@elitjs/https Bun 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, keeps createDevServer synchronous and avoids a breaking public-API change).

Changed

  • Preview mode minifies browser modules with esbuild instead of obfuscating them - elit preview previously ran transpiled .ts/.tsx modules through javascript-obfuscator (string-array obfuscation); preview output is now minified (identifiers + whitespace) by the same esbuild transform that transpiles the module. Removes the javascript-obfuscator dependency (~11.9 MB unpacked, 20 transitive deps) from @elitjs/server, @elitjs/dev-build, @elitjs/preview-build, and the root elit package. Trade-off: minification mangles code structure but, unlike string-array obfuscation, leaves string literals readable. Dev mode is unchanged.
  • Client package browser exports now point at the ES module build - @elitjs/el, @elitjs/dom, @elitjs/style, @elitjs/state, @elitjs/router, and @elitjs/render-context mapped their browser condition to dist/index.iife.js, which has no exports; every bundler build with browser platform resolution got undefined for all named imports (e.g. import { div } from '@elitjs/el'). The browser condition now resolves to dist/index.mjs.

Fixed

  • Client WebSocket behind TLS-terminating proxies (e.g. Cloudflare) - the @elitjs/hmr client 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 every wss:// connection failed. Both now use window.location.host (same origin); the hmr client also targets the /__elit_ws endpoint it previously omitted.
  • SharedState reconnect 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 after destroy() even if a close event arrives late.
  • reactive() sometimes didn't update - the DOM renderer fires ref asynchronously (via setTimeout), so the first state change could land before reactive()'s element reference was set; the update was silently dropped and the element stayed on its initial snapshot. reactive() and reactiveAs() now track the missed update and replay it once the element exists. Also, scheduleRAFUpdate falls back to setTimeout when the tab is hidden, so state-driven updates keep flowing in background tabs where requestAnimationFrame is paused.
  • @elitjs/server bundled a broken inlined copy of esbuild - the server's tsup config did not mark esbuild as external, so the runtime import('esbuild') in transpileNodeBrowserModule resolved to an inlined chunk that fails to load ("Dynamic require of fs is not supported"), silently disabling every esbuild-backed transpile path (.tsx files, the .ts esbuild fallback, and now preview minification). esbuild is now external and resolved from node_modules at runtime, matching @elitjs/dev-build and @elitjs/preview-build.
  • elit test could not load test files importing Node builtins or packages - the test runner's nodeRequire probe (typeof require !== 'undefined') was rewritten by esbuild's require shim into a tautology, so it always used the throwing shim instead of createRequire(import.meta.url). The same pattern in @elitjs/ws, @elitjs/chokidar, @elitjs/fs, @elitjs/http, and @elitjs/https crashed those packages whenever the test runner evaluated their source (createRequire(undefined)). All sites now try __filename first (present when the runner evals transpiled CJS) and fall back to import.meta.url (real ESM builds).
  • elit test leaked mocked browser globals across test files - example tests that mock document, fetch, window, EventSource, timers, etc. at module scope left the mocks installed for every later file in the same process, which broke tsup's import.meta.url CJS shim ("Invalid URL" loading cli.test.ts), broke dom.test.ts with a partial document, and hung the e2e-* suites on a never-firing mocked setTimeout. 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/afterAll registered inside one describe ran 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/afterEach compose down the suite ancestry (outer→inner / inner→outer) and beforeAll/afterAll run once per suite.
  • PM record writes crashed the process - writePmRecord wrote apps/<id>.json without 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.requestPaused dispatch discarded the handlePausedRequest promise, so a paused request that failed to resume (mocked session, command timeout) escaped as an unhandled rejection and crashed the whole elit test process 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.ts now targets packages/function-store (renamed from packages/database); html-entrypoint.test.ts points at the restructured create-elit templates (basic-example); dom.test.ts reads the expected elit-version from packages/dom/package.json instead of a hardcoded version; desktop-cli.test.ts compares resolved paths via path.resolve instead of hardcoded POSIX strings (the functions intentionally return native separators); and the full-db client test expects the current /main.js output layout.

Full Changelog: v4.0.3...v4.0.4

v4.0.2

Choose a tag to compare

@n-devs n-devs released this 24 Jul 02:20
cd04442

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

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 a reactive() 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() and reactiveAs() update paths.
    • Verified by simulating four consecutive input events on an input nested inside reactive(): focus and the underlying <input> DOM node are preserved across all keystrokes, while sibling reactive content (a mirror span) stays in sync.
  • Stale props on reused elements - updateElementProps now diffs the new props against the previous render's props (tracked via __elitPrevProps on 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 removed onclick would keep firing (memory leak + wrong behavior), a removed class would linger, and a removed value/checked on a form control would keep the old input state. Initial-render applyProps (@elitjs/dom) also seeds __elitPrevProps so the first reactive update can diff against it.
  • Form-control props during reactive updates - updateElementProps now assigns value on <input>/<textarea>/<select> and checked on <input> via the DOM property (with an equality check) instead of setAttribute. The previous setAttribute('value', …) path was semantically wrong for live form values and could cause cursor jumps when the input was the root of a reactive() block.
  • State-as-prop regression - updateElementProps now skips State values (they already have their own subscription from the initial applyProps pass) instead of coercing them to "[object Object]" via String(), which broke form controls that received value: 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 __elitPrevProps expando property on the element). Removes DOM pollution and as any casts; entries are GC'd with the element. The accompanying snapshotProps helper shallow-clones nested object/array values (notably style, arrays passed to class/className, and dangerouslySetInnerHTML), so diffing detects in-place mutation of reused prop objects — e.g. a caller doing delete sharedStyle.fontSize between renders now correctly clears font-size on 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 textContent and createTextNode (not parsed as HTML), element props use setAttribute and direct property assignment (also not HTML-parsed). The only innerHTML write is for the explicit dangerouslySetInnerHTML prop (user opt-in, same contract as React); updateElementProps now 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 track v4.0.2. Rebuilt @elitjs/dom and @elitjs/state dist so the baked-in ELIT_VERSION constant matches.

v4.0.2-rc

Choose a tag to compare

@n-devs n-devs released this 23 Jul 17:02
2b53914

What's Changed

Full Changelog: v4.0.2-beta...v4.0.2-rc

v4.0.2-beta

Choose a tag to compare

@n-devs n-devs released this 23 Jul 14:59
9568631

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 a reactive() 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() and reactiveAs() update paths.
    • Verified by simulating four consecutive input events on an input nested inside reactive(): focus and the underlying <input> DOM node are preserved across all keystrokes, while sibling reactive content (a mirror span) stays in sync.
  • Stale props on reused elements - updateElementProps now diffs the new props against the previous render's props (tracked via __elitPrevProps on 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 removed onclick would keep firing (memory leak + wrong behavior), a removed class would linger, and a removed value/checked on a form control would keep the old input state. Initial-render applyProps (@elitjs/dom) also seeds __elitPrevProps so the first reactive update can diff against it.
  • Form-control props during reactive updates - updateElementProps now assigns value on <input>/<textarea>/<select> and checked on <input> via the DOM property (with an equality check) instead of setAttribute. The previous setAttribute('value', …) path was semantically wrong for live form values and could cause cursor jumps when the input was the root of a reactive() block.
  • State-as-prop regression - updateElementProps now skips State values (they already have their own subscription from the initial applyProps pass) instead of coercing them to "[object Object]" via String(), which broke form controls that received value: 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 __elitPrevProps expando property on the element). Removes DOM pollution and as any casts; entries are GC'd with the element. The accompanying snapshotProps helper shallow-clones nested object/array values (notably style, arrays passed to class/className, and dangerouslySetInnerHTML), so diffing detects in-place mutation of reused prop objects — e.g. a caller doing delete sharedStyle.fontSize between renders now correctly clears font-size on 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 textContent and createTextNode (not parsed as HTML), element props use setAttribute and direct property assignment (also not HTML-parsed). The only innerHTML write is for the explicit dangerouslySetInnerHTML prop (user opt-in, same contract as React); updateElementProps now 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 track v4.0.2. Rebuilt @elitjs/dom and @elitjs/state dist so the baked-in ELIT_VERSION constant matches.

v4.0.1-rc

Choose a tag to compare

@nattbl49 nattbl49 released this 08 Jul 21:29

Full Changelog: v4.0.1-beta...v4.0.1-rc

v4.0.1-beta

Choose a tag to compare

@nattbl49 nattbl49 released this 08 Jul 21:21

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

Full Changelog: v4.0.0...v4.0.1-beta

v4.0.0

Choose a tag to compare

@n-devs n-devs released this 08 Jul 16:57

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

  • @n-devs made their first contribution in #1

Full Changelog: https://github.com/elitjs/elit/commits/v4.0.0