@bitstillery/mithril 3.7.0 → 3.8.0
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 +1 -1
- package/querystring/build.ts +8 -3
- package/render/render.ts +5 -1
- package/render/vnode.ts +7 -1
- package/server.ts +4 -1
- package/state.ts +18 -6
- package/store.ts +3 -1
package/package.json
CHANGED
package/querystring/build.ts
CHANGED
|
@@ -4,9 +4,14 @@ export default function buildQueryString(object: Record<string, any>): string {
|
|
|
4
4
|
const args: string[] = []
|
|
5
5
|
function destructure(key: string, value: any) {
|
|
6
6
|
if (Array.isArray(value)) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
// A comma list, not `key[0]=&key[1]=`: it is a third of the length, it survives a CDN
|
|
8
|
+
// cache-key allowlist as a single parameter name, and RFC 3986 permits a bare comma in
|
|
9
|
+
// a query. Each element is encoded on its own, so a comma *inside* a value stays `%2C`
|
|
10
|
+
// and the list still splits unambiguously.
|
|
11
|
+
if (value.length === 0) return
|
|
12
|
+
args.push(
|
|
13
|
+
encodeURIComponent(key) + '=' + value.map((item) => (item == null ? '' : encodeURIComponent(item))).join(','),
|
|
14
|
+
)
|
|
10
15
|
} else if (Object.prototype.toString.call(value) === '[object Object]') {
|
|
11
16
|
for (const i in value) {
|
|
12
17
|
destructure(key + '[' + i + ']', value[i])
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|