Last active
November 18, 2024 00:05
-
-
Save StephanHoyer/41988a6038ac430975990281d08b0d71 to your computer and use it in GitHub Desktop.
Sin mithril bridge (WIP)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* eslint-disable id-denylist */ | |
'use strict' | |
const isString = require('lodash/isString') | |
const omit = require('lodash/omit') | |
function flattenObj(obj, prefix = '') { | |
if (!obj) return {} | |
const res = {} | |
Object.keys(obj).map(key => { | |
if (typeof obj[key] === 'object') { | |
Object.assign( | |
res, | |
flattenObj(obj[key], prefix ? `${prefix}[${key}]` : key) | |
) | |
} else { | |
res[prefix ? `${prefix}[${key}]` : key] = obj[key] | |
} | |
}) | |
return res | |
} | |
const s = require('sin').default | |
function parseTag(tag, attrs) { | |
// .foo[bar=baz][foz=biz][active].someClass => { tag: '.foo.someClass', attrs: { bar: 'baz', foz: biz, active: true } } | |
tag = tag.replace(/\[.*?\]/g, x => { | |
const [key, value] = x.slice(1, -1).split('=') | |
attrs[key] = value || true | |
return '' | |
}) | |
return [tag, attrs] | |
} | |
const m = (tag, ...children) => { | |
let attrs = {} | |
if (children[0] && typeof children[0] === 'object') { | |
;[attrs, ...children] = children | |
} | |
if (isString(tag) && tag.includes('[')) { | |
;[tag, attrs] = parseTag(tag, attrs) | |
} | |
// convert closure component to pojo component | |
if (typeof tag === 'function') { | |
const oninit = tag | |
tag = { | |
oninit: vnode => Object.assign(tag, oninit(vnode)), | |
view: () => {}, | |
} | |
} | |
if (tag.view) { | |
return s(() => { | |
const initialState = omit(tag, ['view', 'oninit', 'oncreate', 'onupdate']) | |
const vnode = { state: { ...initialState }, attrs, children } | |
tag.oninit?.(vnode) | |
let onupdate = () => { | |
attrs.oncreate?.(vnode) | |
tag.oncreate?.(vnode) | |
onupdate = () => { | |
attrs.onupdate?.(vnode) | |
tag.onupdate?.(vnode) | |
} | |
} | |
return () => { | |
const res = tag.view(vnode) | |
vnode.dom = res | |
onupdate() | |
return res | |
} | |
}) | |
} | |
if ( | |
attrs.oncreate || | |
attrs.onupdate || | |
attrs.onbeforeupdate || | |
attrs.onbeforeremove | |
) { | |
return s((_, __, { ignore }) => { | |
const vnode = { state: {}, attrs, children } | |
return (vAttrs, vChildren) => { | |
attrs.onbeforeupdate && | |
vnode.dom && | |
ignore(!attrs.onbeforeupdate({ ...vnode, attrs: vAttrs }, vnode)) | |
vnode.children = vChildren | |
vnode.attrs = vAttrs | |
return s( | |
tag, | |
{ | |
...vnode.attrs, | |
dom: dom => { | |
vnode.dom = dom | |
vnode.attrs.oncreate?.(vnode) | |
return () => vnode.attrs.onbeforeremove?.(vnode) | |
}, | |
}, | |
vnode.dom && (() => (vnode.attrs.onupdate?.(vnode), null)), | |
vnode.children | |
) | |
} | |
})(attrs, children) | |
} | |
return s(tag, attrs, ...children) | |
} | |
m.route = (dom, defaultRoute, routes) => { | |
if (dom && dom !== document.body) | |
console.warn('mounting to other than document.body is not supported atm') | |
const sRoutes = { '*': () => s.route(defaultRoute) } | |
Object.keys(routes).map(path => { | |
sRoutes[path.replace(':key', ':KEY')] = params => { | |
if (params.KEY) params.key = params.KEY | |
return s(async () => { | |
const { oninit, view, onmatch, render, ...initialState } = routes[path] | |
const vnode = { state: { ...initialState }, attrs: params } | |
if (onmatch && render) { | |
const matchResult = await onmatch?.(params) | |
const component = { | |
oninit: () => matchResult?.oninit(vnode), | |
view: () => render(vnode), | |
} | |
return () => m(component, params) | |
} else { | |
const component = { | |
oninit: () => oninit?.(vnode), | |
view: () => view(vnode), | |
} | |
return () => m(component, params) | |
} | |
}) | |
} | |
}) | |
return s.mount((_, __, { route, redraw }) => route(sRoutes)) | |
} | |
m.mount = (dom, comp) => { | |
if (dom && dom !== document.body) | |
console.warn('mounting to other than document.body is not supported atm') | |
s.mount(() => m(comp)) | |
} | |
m.route.get = () => location.pathname | |
m.route.set = s.route | |
m.route.param = () => {} | |
m.redraw = s.redraw | |
m.fragment = ({ key }, children) => s(() => children)({ key }) | |
m.trust = s.trust | |
m.parseQueryString = qs => Object.fromEntries(new URLSearchParams(qs)) | |
m.buildQueryString = obj => new URLSearchParams(obj).toString() | |
m.request = async (urlOrOpts, opts = {}) => { | |
const [url, options] = | |
typeof urlOrOpts === 'string' | |
? [urlOrOpts, opts] | |
: [urlOrOpts.url, { ...urlOrOpts, ...opts }] | |
delete options.url | |
options.query = flattenObj(options.params) | |
if (options.extract) { | |
const res = await s.http(url, options).xhr | |
const result = options.extract(res) | |
s.redraw() | |
return result | |
} | |
return s.http(url, options) | |
} | |
module.exports = m |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment