Skip to content

Instantly share code, notes, and snippets.

@StephanHoyer
Last active November 18, 2024 00:05
Show Gist options
  • Save StephanHoyer/41988a6038ac430975990281d08b0d71 to your computer and use it in GitHub Desktop.
Save StephanHoyer/41988a6038ac430975990281d08b0d71 to your computer and use it in GitHub Desktop.
Sin mithril bridge (WIP)
/* 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