@bitstillery/mithril 3.7.0 → 3.7.1

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": "@bitstillery/mithril",
3
- "version": "3.7.0",
3
+ "version": "3.7.1",
4
4
  "description": "Mithril + Signals, Store and SSR",
5
5
  "license": "MIT",
6
6
  "author": "Bitstillery",
package/render/render.ts CHANGED
@@ -691,7 +691,11 @@ export default function renderFactory() {
691
691
  const originalNextSibling = nextSibling
692
692
  let pos = 2147483647,
693
693
  matched = 0
694
- const oldIndices = Array.from({length: end - start + 1}, () => -1)
694
+ // See the note in vnode.ts's normalizeChildren: `Array.from({length: n}, () => -1)`
695
+ // is ~14x slower than this on V8 (~146ms vs ~10ms for 200k calls at 24 entries),
696
+ // since it adds a per-element mapper call on top of the slower allocation path.
697
+ // oxlint-disable-next-line no-new-array
698
+ const oldIndices = new Array(end - start + 1).fill(-1)
695
699
  const map: Record<string, number> = Object.create(null)
696
700
  for (let i = start; i <= end; i++) {
697
701
  if (vnodes[i] != null) map[vnodes[i]!.key!] = i
package/render/vnode.ts CHANGED
@@ -102,7 +102,13 @@ const normalize = function (node: any): Vnode | null {
102
102
  const normalizeChildren = function (input: any[]): (Vnode | null)[] {
103
103
  // Preallocate the array length (initially holey) and fill every index immediately in order.
104
104
  // Benchmarking shows better performance on V8.
105
- const children = Array.from({length: input.length}) as (Vnode | null)[]
105
+ //
106
+ // Do NOT let a linter rewrite this to `Array.from({length: n})`: that is not holey, it walks the
107
+ // array-like/iterator path, and on V8 it is ~15x slower here (measured on node 22: ~98ms vs ~6.4ms
108
+ // for 200k calls at 12 children). This is the hottest function in a portal render profile, so the
109
+ // difference is not academic — it was silently lost once already to an oxlint autofix.
110
+ // oxlint-disable-next-line no-new-array
111
+ const children = new Array(input.length) as (Vnode | null)[]
106
112
  // Count the number of keyed normalized vnodes for consistency check.
107
113
  // Note: this is a perf-sensitive check.
108
114
  // Fun fact: merging the loop like this is somehow faster than splitting
package/server.ts CHANGED
@@ -14,7 +14,10 @@ import censor from './util/censor'
14
14
  import type {MithrilStatic, Hyperscript} from './index'
15
15
  import type {Redraw} from './api/mount-redraw'
16
16
 
17
- logger.debug(`mithril ssr v${version}`)
17
+ // `process.env.MSI_BUILD_COMMIT` is inlined by the SSR bundle's `define` (see frontend/cli/tasks.ts).
18
+ // It is undefined when running unbundled (e.g. mithril's own tests), hence the fallback.
19
+ const buildCommit = process.env.MSI_BUILD_COMMIT || 'dev'
20
+ logger.debug(`mithril ssr v${version} (commit ${buildCommit})`)
18
21
 
19
22
  // Create server-side renderer
20
23
  const {renderToString, renderToStringSync} = renderToStringFactory()
package/state.ts CHANGED
@@ -619,12 +619,22 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
619
619
  }
620
620
 
621
621
  const ensurePropertySignal = (target: any, prop: string | symbol, key: string) => {
622
- if (!nestedSignalMap.has(key)) {
623
- const originalValue = Reflect.get(target, prop)
624
- if (originalValue === undefined) return undefined
625
- nestedSignalMap.set(key, createPropertySignal(originalValue))
622
+ // One Map lookup, not two. This runs on EVERY property read of every state object — the
623
+ // hottest path in the framework — and after the first access the signal always exists, so the
624
+ // old `has()` + `get()` pair doubled the cost of the common case.
625
+ //
626
+ // `get()` returning undefined is an exact substitute for `!has()`: createPropertySignal never
627
+ // returns undefined (it always yields a Signal, falling back to `signal(undefined)`), and the
628
+ // early return below stores nothing.
629
+ const existing = nestedSignalMap.get(key)
630
+ if (existing !== undefined) {
631
+ return existing
626
632
  }
627
- return nestedSignalMap.get(key)
633
+ const originalValue = Reflect.get(target, prop)
634
+ if (originalValue === undefined) return undefined
635
+ const created = createPropertySignal(originalValue)
636
+ nestedSignalMap.set(key, created)
637
+ return created
628
638
  }
629
639
  wrapped = new Proxy(obj, {
630
640
  get(target, prop) {
@@ -647,7 +657,9 @@ export function state<T extends Record<string, any>>(initial: T, name?: string,
647
657
  }
648
658
  }
649
659
 
650
- const propStr = String(prop)
660
+ // Property keys are already strings in virtually every access; `String()` on a symbol is
661
+ // the rare path, so don't make the common one pay for the conversion.
662
+ const propStr = typeof prop === 'string' ? prop : String(prop)
651
663
 
652
664
  // Check for $ prefix convention (deepsignal-style: returns raw signal)
653
665
  if (propStr.startsWith('$') && propStr.length > 1) {
package/store.ts CHANGED
@@ -557,7 +557,9 @@ export class Store<T extends Record<string, any> = Record<string, any>> {
557
557
  )
558
558
  return
559
559
  }
560
- document.cookie = `${key}=${value}; Path=/; SameSite=Lax; Max-Age=${this.cookieMaxAge}`
560
+ // Conditional so plain-http dev hosts can still write the cookie at all.
561
+ const secure = location.protocol === 'https:' ? '; Secure' : ''
562
+ document.cookie = `${key}=${value}; Path=/; SameSite=Lax${secure}; Max-Age=${this.cookieMaxAge}`
561
563
  } catch (err) {
562
564
  console.error('Cannot write cookie; continue without.', err)
563
565
  }