@bitstillery/mithril 3.0.0-AA

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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +185 -0
  3. package/api/mount-redraw.ts +173 -0
  4. package/api/router-server.ts +88 -0
  5. package/api/router.ts +670 -0
  6. package/browser.ts +10 -0
  7. package/htm.ts +7 -0
  8. package/hyperscript.ts +8 -0
  9. package/index.ts +134 -0
  10. package/jsx.d.ts +16 -0
  11. package/mount.ts +10 -0
  12. package/package.json +66 -0
  13. package/pathname/build.ts +40 -0
  14. package/pathname/compileTemplate.ts +49 -0
  15. package/pathname/parse.ts +19 -0
  16. package/querystring/build.ts +22 -0
  17. package/querystring/parse.ts +50 -0
  18. package/redraw.ts +10 -0
  19. package/render/cachedAttrsIsStaticMap.ts +10 -0
  20. package/render/delayedRemoval.ts +1 -0
  21. package/render/domFor.ts +23 -0
  22. package/render/emptyAttrs.ts +2 -0
  23. package/render/fragment.ts +11 -0
  24. package/render/hyperscript.ts +125 -0
  25. package/render/hyperscriptVnode.ts +21 -0
  26. package/render/render.ts +1307 -0
  27. package/render/renderToString.ts +494 -0
  28. package/render/ssrState.ts +366 -0
  29. package/render/trust.ts +6 -0
  30. package/render/vnode.ts +131 -0
  31. package/route.ts +11 -0
  32. package/server/logger.ts +223 -0
  33. package/server/session.ts +87 -0
  34. package/server/ssr.ts +205 -0
  35. package/server/ssrLogger.ts +13 -0
  36. package/server.ts +101 -0
  37. package/signal.ts +320 -0
  38. package/ssrContext.ts +87 -0
  39. package/state.ts +1026 -0
  40. package/store.ts +457 -0
  41. package/util/censor.ts +46 -0
  42. package/util/decodeURIComponentSafe.ts +34 -0
  43. package/util/hasOwn.ts +2 -0
  44. package/util/next_tick.ts +45 -0
  45. package/util/ssr.ts +385 -0
  46. package/util/uri.ts +126 -0
  47. package/version.ts +3 -0
package/htm.ts ADDED
@@ -0,0 +1,7 @@
1
+ import htm from 'htm'
2
+ import m from './index.ts'
3
+
4
+ const html = htm.bind(m)
5
+
6
+ export {html}
7
+ export {m}
package/hyperscript.ts ADDED
@@ -0,0 +1,8 @@
1
+ import hyperscript from './render/hyperscript'
2
+ import trust from './render/trust'
3
+ import fragment from './render/fragment'
4
+
5
+ hyperscript.trust = trust
6
+ hyperscript.fragment = fragment
7
+
8
+ export default hyperscript
package/index.ts ADDED
@@ -0,0 +1,134 @@
1
+ import hyperscript from './render/hyperscript'
2
+ import mountRedrawFactory from './api/mount-redraw'
3
+ import routerFactory from './api/router'
4
+ import renderFactory from './render/render'
5
+ import parseQueryString from './querystring/parse'
6
+ import buildQueryString from './querystring/build'
7
+ import parsePathname from './pathname/parse'
8
+ import buildPathname from './pathname/build'
9
+ import VnodeFactory, {MithrilComponent} from './render/vnode'
10
+ import censor from './util/censor'
11
+ import next_tick from './util/next_tick'
12
+ import domFor from './render/domFor'
13
+ import {signal, computed, effect, Signal, ComputedSignal, setSignalRedrawCallback, getSignalComponents} from './signal'
14
+ import {state, watch, registerState, getRegisteredStates, copyGlobalStatesToContext} from './state'
15
+
16
+ import type {Vnode, Children, ComponentType} from './render/vnode'
17
+ import type {Hyperscript} from './render/hyperscript'
18
+ import type {Route, RouteResolver, RedirectObject} from './api/router'
19
+ import type {Render, Redraw, Mount} from './api/mount-redraw'
20
+
21
+ export interface MithrilStatic {
22
+ m: Hyperscript
23
+ trust: (html: string) => Vnode
24
+ fragment: (attrs: Record<string, any> | null, ...children: Children[]) => Vnode
25
+ Fragment: string
26
+ mount: Mount
27
+ route: Route &
28
+ ((root: Element, defaultRoute: string, routes: Record<string, ComponentType | RouteResolver>) => void) & {
29
+ redirect: (path: string) => RedirectObject
30
+ }
31
+ render: Render
32
+ redraw: Redraw
33
+ parseQueryString: (queryString: string) => Record<string, any>
34
+ buildQueryString: (values: Record<string, any>) => string
35
+ parsePathname: (pathname: string) => {path: string; params: Record<string, any>}
36
+ buildPathname: (template: string, params: Record<string, any>) => string
37
+ vnode: typeof VnodeFactory
38
+ censor: (attrs: Record<string, any>, extras?: string[]) => Record<string, any>
39
+ next_tick: () => Promise<void>
40
+ domFor: (vnode: Vnode) => Generator<Node, void, unknown>
41
+ }
42
+
43
+ const mountRedrawInstance = mountRedrawFactory(
44
+ renderFactory(),
45
+ typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame.bind(window) : setTimeout,
46
+ console,
47
+ )
48
+
49
+ const router = routerFactory(typeof window !== 'undefined' ? window : null, mountRedrawInstance)
50
+
51
+ const m: MithrilStatic & Hyperscript = function m(this: any) {
52
+ return hyperscript.apply(this, arguments as any)
53
+ } as unknown as MithrilStatic & Hyperscript
54
+
55
+ m.m = hyperscript as Hyperscript
56
+ m.trust = hyperscript.trust
57
+ m.fragment = hyperscript.fragment
58
+ m.Fragment = '['
59
+ m.mount = mountRedrawInstance.mount
60
+ m.route = router as Route & typeof router & {redirect: (path: string) => RedirectObject}
61
+ m.render = renderFactory()
62
+ m.redraw = mountRedrawInstance.redraw
63
+ m.parseQueryString = parseQueryString
64
+ m.buildQueryString = buildQueryString
65
+ m.parsePathname = parsePathname
66
+ m.buildPathname = buildPathname
67
+ m.vnode = VnodeFactory
68
+ m.censor = censor
69
+ m.next_tick = next_tick
70
+ m.domFor = domFor
71
+
72
+ // Set up signal-to-component redraw integration
73
+ setSignalRedrawCallback((sig: Signal<any>) => {
74
+ const components = getSignalComponents(sig)
75
+ if (components) {
76
+ components.forEach((component) => {
77
+ // Use the component-level redraw
78
+ m.redraw(component as any)
79
+ })
80
+ }
81
+ })
82
+
83
+ // Export signals API
84
+ export {signal, computed, effect, Signal, ComputedSignal, state, watch, registerState, getRegisteredStates}
85
+ export type {State, StateOptions} from './state'
86
+
87
+ // Export Store class
88
+ export {Store} from './store'
89
+
90
+ // Export SSR utilities
91
+ export {serializeStore, deserializeStore, serializeAllStates, deserializeAllStates} from './render/ssrState'
92
+
93
+ // Export SSR request context (for per-request store and state registry)
94
+ export {getSSRContext, runWithContext, runWithContextAsync, cleanupWatchers} from './ssrContext'
95
+ export type {SSRAccessContext} from './ssrContext'
96
+
97
+ // Export isomorphic logger
98
+ export {logger, Logger} from './server/logger'
99
+ export type {LogContext} from './server/logger'
100
+
101
+ // Export next_tick utility
102
+ export {next_tick} from './util/next_tick'
103
+
104
+ // Export URI utilities
105
+ export {getCurrentUrl, getPathname, getSearch, getHash, getLocation} from './util/uri'
106
+ export type {IsomorphicLocation} from './util/uri'
107
+
108
+ // Export component and vnode types
109
+ export type {
110
+ Vnode,
111
+ ComponentVnode,
112
+ Children,
113
+ Child,
114
+ VnodeDOM,
115
+ Component,
116
+ ComponentFactory,
117
+ ComponentType,
118
+ VnodeOf,
119
+ } from './render/vnode'
120
+ export {MithrilComponent}
121
+ export type {Hyperscript} from './render/hyperscript'
122
+ export type {Route, RouteResolver, RedirectObject} from './api/router'
123
+ export type {Render, Redraw, Mount} from './api/mount-redraw'
124
+
125
+ // Namespace merge: enables m.Vnode<Attrs> and m.Children when using import m from '@bitstillery/mithril'
126
+ // m.Vnode uses ComponentVnode so vnode.attrs is always defined in component lifecycle methods
127
+ declare namespace m {
128
+ type Vnode<Attrs = Record<string, any>, State = any> = import('./render/vnode').ComponentVnode<Attrs, State>
129
+ type VnodeDOM<Attrs = Record<string, any>, State = any> = import('./render/vnode').VnodeDOM<Attrs, State>
130
+ type Children = import('./render/vnode').Children
131
+ type ChildArray = import('./render/vnode').Child[]
132
+ }
133
+
134
+ export default m
package/jsx.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type {Vnode} from './render/vnode'
2
+
3
+ declare global {
4
+ namespace JSX {
5
+ /** JSX elements from hyperscript always have attrs set (at least {}). */
6
+ interface Element extends Omit<Vnode, 'attrs'> {
7
+ attrs: Record<string, any>
8
+ }
9
+ interface IntrinsicElements {
10
+ [elemName: string]: any
11
+ }
12
+ interface ElementAttributesProperty {
13
+ __tsx_attrs: any
14
+ }
15
+ }
16
+ }
package/mount.ts ADDED
@@ -0,0 +1,10 @@
1
+ import mountRedrawFactory from './api/mount-redraw'
2
+ import renderFactory from './render/render'
3
+
4
+ const mountRedraw = mountRedrawFactory(
5
+ renderFactory(),
6
+ typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame.bind(window) : setTimeout,
7
+ console,
8
+ )
9
+
10
+ export default mountRedraw.mount
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@bitstillery/mithril",
3
+ "version": "3.0.0-AA",
4
+ "description": "Mithril AA (AI Augmented) - Mithril + Signals, Store and SSR",
5
+ "license": "MIT",
6
+ "author": "Bitstillery",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/bitstillery/mithril.git"
10
+ },
11
+ "files": [
12
+ "*.ts",
13
+ "*.d.ts",
14
+ "ssrContext.ts",
15
+ "signal.ts",
16
+ "store.ts",
17
+ "api/*.ts",
18
+ "pathname/*.ts",
19
+ "querystring/*.ts",
20
+ "render/*.ts",
21
+ "server/*.ts",
22
+ "util/*.ts",
23
+ "LICENSE",
24
+ "README.md"
25
+ ],
26
+ "type": "module",
27
+ "main": "./index.ts",
28
+ "module": "./index.ts",
29
+ "exports": {
30
+ ".": {
31
+ "import": "./index.ts",
32
+ "default": "./index.ts"
33
+ },
34
+ "./browser": {
35
+ "import": "./browser.ts",
36
+ "default": "./browser.ts"
37
+ },
38
+ "./server": {
39
+ "import": "./server.ts",
40
+ "default": "./server.ts"
41
+ },
42
+ "./htm": {
43
+ "import": "./htm.ts",
44
+ "default": "./htm.ts"
45
+ }
46
+ },
47
+ "scripts": {
48
+ "lint": "bun run lint:ts",
49
+ "lint:ts": "bun run lint:ts-format && bun run lint:ts-syntax && bun run lint:ts-types",
50
+ "lint:ts-format": "oxfmt",
51
+ "lint:ts-syntax": "oxlint -c .oxlintrc.json",
52
+ "lint:ts-types": "tsgo --noEmit",
53
+ "test": "bun test",
54
+ "prepublishOnly": "bun run lint:ts",
55
+ "pack:dry-run": "npm pack --dry-run"
56
+ },
57
+ "dependencies": {
58
+ "htm": "^3.1.1"
59
+ },
60
+ "devDependencies": {
61
+ "@types/bun": "^1.3.9",
62
+ "@typescript/native-preview": "^7.0.0-dev.20260120.1",
63
+ "oxfmt": "^0.32.0",
64
+ "oxlint": "^1.30.0"
65
+ }
66
+ }
@@ -0,0 +1,40 @@
1
+ import buildQueryString from '../querystring/build'
2
+
3
+ // Returns `path` from `template` + `params`
4
+ export default function buildPathname(template: string, params: Record<string, any>): string {
5
+ if (/:([^/.-]+)(\.{3})?:/.test(template)) {
6
+ throw new SyntaxError("Template parameter names must be separated by either a '/', '-', or '.'.")
7
+ }
8
+ if (params == null) return template
9
+ const queryIndex = template.indexOf('?')
10
+ const hashIndex = template.indexOf('#')
11
+ const queryEnd = hashIndex < 0 ? template.length : hashIndex
12
+ const pathEnd = queryIndex < 0 ? queryEnd : queryIndex
13
+ const path = template.slice(0, pathEnd)
14
+ const query: Record<string, any> = {}
15
+
16
+ Object.assign(query, params)
17
+
18
+ const resolved = path.replace(/:([^/.-]+)(\.{3})?/g, function (m, key, variadic) {
19
+ delete query[key]
20
+ // If no such parameter exists, don't interpolate it.
21
+ if (params[key] == null) return m
22
+ // Escape normal parameters, but not variadic ones.
23
+ return variadic ? params[key] : encodeURIComponent(String(params[key]))
24
+ })
25
+
26
+ // In case the template substitution adds new query/hash parameters.
27
+ const newQueryIndex = resolved.indexOf('?')
28
+ const newHashIndex = resolved.indexOf('#')
29
+ const newQueryEnd = newHashIndex < 0 ? resolved.length : newHashIndex
30
+ const newPathEnd = newQueryIndex < 0 ? newQueryEnd : newQueryIndex
31
+ let result = resolved.slice(0, newPathEnd)
32
+
33
+ if (queryIndex >= 0) result += template.slice(queryIndex, queryEnd)
34
+ if (newQueryIndex >= 0) result += (queryIndex < 0 ? '?' : '&') + resolved.slice(newQueryIndex, newQueryEnd)
35
+ const querystring = buildQueryString(query)
36
+ if (querystring) result += (queryIndex < 0 && newQueryIndex < 0 ? '?' : '&') + querystring
37
+ if (hashIndex >= 0) result += template.slice(hashIndex)
38
+ if (newHashIndex >= 0) result += (hashIndex < 0 ? '' : '&') + resolved.slice(newHashIndex)
39
+ return result
40
+ }
@@ -0,0 +1,49 @@
1
+ import parsePathname from './parse'
2
+
3
+ interface CompiledTemplate {
4
+ (data: {path: string; params: Record<string, any>}): boolean
5
+ }
6
+
7
+ // Compiles a template into a function that takes a resolved path (without query
8
+ // strings) and returns an object containing the template parameters with their
9
+ // parsed values. This expects the input of the compiled template to be the
10
+ // output of `parsePathname`. Note that it does *not* remove query parameters
11
+ // specified in the template.
12
+ export default function compileTemplate(template: string): CompiledTemplate {
13
+ const templateData = parsePathname(template)
14
+ const templateKeys = Object.keys(templateData.params)
15
+ const keys: Array<{k: string; r: boolean}> = []
16
+ const regexp = new RegExp(
17
+ '^' +
18
+ templateData.path.replace(
19
+ // I escape literal text so people can use things like `:file.:ext` or
20
+ // `:lang-:locale` in routes. This is all merged into one pass so I
21
+ // don't also accidentally escape `-` and make it harder to detect it to
22
+ // ban it from template parameters.
23
+ /:([^/.-]+)(\.{3}|\.(?!\.)|-)?|[\\^$*+.()|[\]{}]/g,
24
+ function (m, key, extra) {
25
+ if (key == null) return '\\' + m
26
+ keys.push({k: key, r: extra === '...'})
27
+ if (extra === '...') return '(.*)'
28
+ if (extra === '.') return '([^/]+)\\.'
29
+ return '([^/]+)' + (extra || '')
30
+ },
31
+ ) +
32
+ '\\/?$',
33
+ )
34
+ return function (data: {path: string; params: Record<string, any>}): boolean {
35
+ // First, check the params. Usually, there isn't any, and it's just
36
+ // checking a static set.
37
+ for (let i = 0; i < templateKeys.length; i++) {
38
+ if (templateData.params[templateKeys[i]] !== data.params[templateKeys[i]]) return false
39
+ }
40
+ // If no interpolations exist, let's skip all the ceremony
41
+ if (!keys.length) return regexp.test(data.path)
42
+ const values = regexp.exec(data.path)
43
+ if (values == null) return false
44
+ for (let i = 0; i < keys.length; i++) {
45
+ data.params[keys[i].k] = keys[i].r ? values[i + 1] : decodeURIComponent(values[i + 1])
46
+ }
47
+ return true
48
+ }
49
+ }
@@ -0,0 +1,19 @@
1
+ import parseQueryString from '../querystring/parse'
2
+
3
+ // Returns `{path, params}` from `url`
4
+ export default function parsePathname(url: string): {path: string; params: Record<string, any>} {
5
+ const queryIndex = url.indexOf('?')
6
+ const hashIndex = url.indexOf('#')
7
+ const queryEnd = hashIndex < 0 ? url.length : hashIndex
8
+ const pathEnd = queryIndex < 0 ? queryEnd : queryIndex
9
+ let path = url.slice(0, pathEnd).replace(/\/{2,}/g, '/')
10
+
11
+ if (!path) path = '/'
12
+ else {
13
+ if (path[0] !== '/') path = '/' + path
14
+ }
15
+ return {
16
+ path: path,
17
+ params: queryIndex < 0 ? {} : parseQueryString(url.slice(queryIndex + 1, queryEnd)),
18
+ }
19
+ }
@@ -0,0 +1,22 @@
1
+ export default function buildQueryString(object: Record<string, any>): string {
2
+ if (Object.prototype.toString.call(object) !== '[object Object]') return ''
3
+
4
+ const args: string[] = []
5
+ function destructure(key: string, value: any) {
6
+ if (Array.isArray(value)) {
7
+ for (let i = 0; i < value.length; i++) {
8
+ destructure(key + '[' + i + ']', value[i])
9
+ }
10
+ } else if (Object.prototype.toString.call(value) === '[object Object]') {
11
+ for (const i in value) {
12
+ destructure(key + '[' + i + ']', value[i])
13
+ }
14
+ } else args.push(encodeURIComponent(key) + (value != null && value !== '' ? '=' + encodeURIComponent(value) : ''))
15
+ }
16
+
17
+ for (const key in object) {
18
+ destructure(key, object[key])
19
+ }
20
+
21
+ return args.join('&')
22
+ }
@@ -0,0 +1,50 @@
1
+ import decodeURIComponentSafe from '../util/decodeURIComponentSafe'
2
+
3
+ export default function parseQueryString(string: string | null | undefined): Record<string, any> {
4
+ if (string === '' || string == null) return {}
5
+ if (string.charAt(0) === '?') string = string.slice(1)
6
+
7
+ const entries = string.split('&')
8
+ const counters: Record<string, number> = {}
9
+ const data: Record<string, any> = {}
10
+ for (let i = 0; i < entries.length; i++) {
11
+ const entry = entries[i].split('=')
12
+ const key = decodeURIComponentSafe(entry[0])
13
+ let value: any = entry.length === 2 ? decodeURIComponentSafe(entry[1]) : ''
14
+
15
+ if (value === 'true') value = true
16
+ else if (value === 'false') value = false
17
+
18
+ const levels = key.split(/\]\[?|\[/)
19
+ let cursor: any = data
20
+ if (key.indexOf('[') > -1) levels.pop()
21
+ for (let j = 0; j < levels.length; j++) {
22
+ const level = levels[j]
23
+ const nextLevel = levels[j + 1]
24
+ const isNumber = nextLevel == '' || !isNaN(parseInt(nextLevel, 10))
25
+ let finalLevel: string | number
26
+ if (level === '') {
27
+ const key = levels.slice(0, j).join()
28
+ if (counters[key] == null) {
29
+ counters[key] = Array.isArray(cursor) ? cursor.length : 0
30
+ }
31
+ finalLevel = counters[key]++
32
+ }
33
+ // Disallow direct prototype pollution
34
+ else if (level === '__proto__') break
35
+ else {
36
+ finalLevel = level
37
+ }
38
+ if (j === levels.length - 1) cursor[finalLevel] = value
39
+ else {
40
+ // Read own properties exclusively to disallow indirect
41
+ // prototype pollution
42
+ const desc = Object.getOwnPropertyDescriptor(cursor, finalLevel)
43
+ let descValue = desc != null ? desc.value : undefined
44
+ if (descValue == null) cursor[finalLevel] = descValue = isNumber ? [] : {}
45
+ cursor = descValue
46
+ }
47
+ }
48
+ }
49
+ return data
50
+ }
package/redraw.ts ADDED
@@ -0,0 +1,10 @@
1
+ import mountRedrawFactory from './api/mount-redraw'
2
+ import renderFactory from './render/render'
3
+
4
+ const mountRedraw = mountRedrawFactory(
5
+ renderFactory(),
6
+ typeof requestAnimationFrame !== 'undefined' ? requestAnimationFrame.bind(window) : setTimeout,
7
+ console,
8
+ )
9
+
10
+ export default mountRedraw.redraw
@@ -0,0 +1,10 @@
1
+ import emptyAttrs from './emptyAttrs'
2
+
3
+ // This Map manages the following:
4
+ // - Whether an attrs is cached attrs generated by compileSelector().
5
+ // - Whether the cached attrs is "static", i.e., does not contain any form attributes.
6
+ // These information will be useful to skip updating attrs in render().
7
+ //
8
+ // Since the attrs used as keys in this map are not released from the selectorCache object,
9
+ // there is no risk of memory leaks. Therefore, Map is used here instead of WeakMap.
10
+ export default new Map([[emptyAttrs, true]])
@@ -0,0 +1 @@
1
+ export default new WeakMap<Node, number>()
@@ -0,0 +1,23 @@
1
+ import delayedRemoval from './delayedRemoval'
2
+
3
+ import type {Vnode} from './vnode'
4
+
5
+ function* domFor(vnode: Vnode): Generator<Node, void, unknown> {
6
+ // To avoid unintended mangling of the internal bundler,
7
+ // parameter destructuring is not used here.
8
+ let dom = vnode.dom
9
+ let domSize = vnode.domSize
10
+ const generation = delayedRemoval.get(dom!)
11
+ do {
12
+ const nextSibling = dom!.nextSibling
13
+
14
+ if (delayedRemoval.get(dom!) === generation) {
15
+ yield dom!
16
+ domSize!--
17
+ }
18
+
19
+ dom = nextSibling as Node | null
20
+ } while (domSize)
21
+ }
22
+
23
+ export default domFor
@@ -0,0 +1,2 @@
1
+ // This is an attrs object that is used by default when attrs is undefined or null.
2
+ export default {}
@@ -0,0 +1,11 @@
1
+ import Vnode from './vnode'
2
+ import hyperscriptVnode from './hyperscriptVnode'
3
+
4
+ export default function fragment(attrs: any, ...children: any[]): any {
5
+ const vnode = hyperscriptVnode(attrs, children)
6
+
7
+ if (vnode.attrs == null) vnode.attrs = {}
8
+ vnode.tag = '['
9
+ vnode.children = Vnode.normalizeChildren(vnode.children)
10
+ return vnode
11
+ }
@@ -0,0 +1,125 @@
1
+ import hasOwn from '../util/hasOwn'
2
+
3
+ import Vnode from './vnode'
4
+ import hyperscriptVnode from './hyperscriptVnode'
5
+ import emptyAttrs from './emptyAttrs'
6
+ import cachedAttrsIsStaticMap from './cachedAttrsIsStaticMap'
7
+ import trust from './trust'
8
+ import fragment from './fragment'
9
+
10
+ import type {ComponentType, Children, Vnode as VnodeType} from './vnode'
11
+
12
+ export interface Hyperscript {
13
+ (selector: string, ...children: Children[]): VnodeType
14
+ (selector: string, attrs: Record<string, any>, ...children: Children[]): VnodeType
15
+ <Attrs, State>(component: ComponentType<Attrs, State>, ...children: Children[]): VnodeType<Attrs, State>
16
+ <Attrs, State>(component: ComponentType<Attrs, State>, attrs: Attrs, ...children: Children[]): VnodeType<Attrs, State>
17
+ trust(html: string): VnodeType
18
+ fragment(attrs: Record<string, any> | null, ...children: Children[]): VnodeType
19
+ Fragment: string
20
+ }
21
+
22
+ const selectorParser = /(?:(^|#|\.)([^#.[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
23
+ const selectorCache: Record<string, {tag: string; attrs: Record<string, any>; is?: string}> = Object.create(null)
24
+
25
+ function isEmpty(object: Record<string, any>): boolean {
26
+ for (const key in object) if (hasOwn.call(object, key)) return false
27
+ return true
28
+ }
29
+
30
+ function isFormAttributeKey(key: string): boolean {
31
+ return key === 'value' || key === 'checked' || key === 'selectedIndex' || key === 'selected'
32
+ }
33
+
34
+ function compileSelector(selector: string): {tag: string; attrs: Record<string, any>; is?: string} {
35
+ let match: RegExpExecArray | null
36
+ let tag = 'div'
37
+ const classes: string[] = []
38
+ let attrs: Record<string, any> = {}
39
+ let isStatic = true
40
+ while ((match = selectorParser.exec(selector)) !== null) {
41
+ const type = match[1]
42
+ const value = match[2]
43
+ if (type === '' && value !== '') tag = value
44
+ else if (type === '#') attrs.id = value
45
+ else if (type === '.') classes.push(value)
46
+ else if (match[3][0] === '[') {
47
+ let attrValue = match[6]
48
+ if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, '$1').replace(/\\\\/g, '\\')
49
+ if (match[4] === 'class') classes.push(attrValue)
50
+ else {
51
+ attrs[match[4]] = attrValue === '' ? attrValue : attrValue || true
52
+ if (isFormAttributeKey(match[4])) isStatic = false
53
+ }
54
+ }
55
+ }
56
+ if (classes.length > 0) attrs.className = classes.join(' ')
57
+ if (isEmpty(attrs)) attrs = emptyAttrs
58
+ else cachedAttrsIsStaticMap.set(attrs, isStatic)
59
+ return (selectorCache[selector] = {tag: tag, attrs: attrs, is: attrs.is})
60
+ }
61
+
62
+ function execSelector(state: {tag: string; attrs: Record<string, any>; is?: string}, vnode: any): any {
63
+ vnode.tag = state.tag
64
+
65
+ let attrs = vnode.attrs
66
+ if (attrs == null) {
67
+ vnode.attrs = state.attrs
68
+ vnode.is = state.is
69
+ return vnode
70
+ }
71
+
72
+ if (hasOwn.call(attrs, 'class')) {
73
+ if (attrs.class != null) attrs.className = attrs.class
74
+ attrs.class = null
75
+ }
76
+
77
+ if (state.attrs !== emptyAttrs) {
78
+ const className = attrs.className
79
+ attrs = Object.assign({}, state.attrs, attrs)
80
+
81
+ if (state.attrs.className != null)
82
+ attrs.className = className != null ? String(state.attrs.className) + ' ' + String(className) : state.attrs.className
83
+ }
84
+
85
+ // workaround for #2622 (reorder keys in attrs to set "type" first)
86
+ // The DOM does things to inputs based on the "type", so it needs set first.
87
+ // See: https://github.com/MithrilJS/mithril.js/issues/2622
88
+ if (state.tag === 'input' && hasOwn.call(attrs, 'type')) {
89
+ attrs = Object.assign({type: attrs.type}, attrs)
90
+ }
91
+
92
+ // This reduces the complexity of the evaluation of "is" within the render function.
93
+ vnode.is = attrs.is
94
+
95
+ vnode.attrs = attrs
96
+
97
+ return vnode
98
+ }
99
+
100
+ function hyperscript(selector: string | ComponentType, attrs?: Record<string, any> | null, ...children: Children[]): any {
101
+ if (
102
+ selector == null ||
103
+ (typeof selector !== 'string' && typeof selector !== 'function' && typeof (selector as any).view !== 'function')
104
+ ) {
105
+ throw Error('The selector must be either a string or a component.')
106
+ }
107
+
108
+ const vnode = hyperscriptVnode(attrs, children)
109
+
110
+ if (typeof selector === 'string') {
111
+ vnode.children = Vnode.normalizeChildren(vnode.children)
112
+ if (selector !== '[') return execSelector(selectorCache[selector] || compileSelector(selector), vnode)
113
+ }
114
+
115
+ if (vnode.attrs == null) vnode.attrs = {}
116
+ vnode.tag = selector
117
+ return vnode
118
+ }
119
+
120
+ hyperscript.trust = trust
121
+
122
+ hyperscript.fragment = fragment
123
+ hyperscript.Fragment = '['
124
+
125
+ export default hyperscript
@@ -0,0 +1,21 @@
1
+ import Vnode from './vnode'
2
+
3
+ // Note: the processing of variadic parameters is perf-sensitive.
4
+ //
5
+ // In native ES6, it might be preferable to define hyperscript and fragment
6
+ // factories with a final ...args parameter and call hyperscriptVnode(...args),
7
+ // since modern engines can optimize spread calls.
8
+ //
9
+ // However, benchmarks showed this was not faster. As a result, spread is used
10
+ // only in the parameter lists of hyperscript and fragment, while an array is
11
+ // passed to hyperscriptVnode.
12
+ export default function hyperscriptVnode(attrs: any, children: any[]): any {
13
+ if (attrs == null || (typeof attrs === 'object' && attrs.tag == null && !Array.isArray(attrs))) {
14
+ if (children.length === 1 && Array.isArray(children[0])) children = children[0]
15
+ } else {
16
+ children = children.length === 0 && Array.isArray(attrs) ? attrs : [attrs, ...children]
17
+ attrs = undefined
18
+ }
19
+
20
+ return Vnode('', attrs && attrs.key, attrs, children, null, null)
21
+ }