diff --git a/.travis.yml b/.travis.yml index dfe189b..d06ca51 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,5 +11,4 @@ deploy: secure: "imp1U7Pl6pYcMxwg6boBQlRWf08hKl/9SyXcKIeG1HuL27DDsuvlsYZfdVBLPvHEwgGH89fFhdPv5AKpTmt8bBES/t3/LLTfCO4Wx0inXeUI+558yDB+RpWN8dtiKfOWGDqeDFL01zYnPfVTclzLR+mDVgAcQxqRov7YpANpD0E1WIAzXpxCYyO3SgEkJlZ0s0R4zfG2Ibm/nJ7caaHbJJqwjxZsDI2IRdSLdl/WucaoSnRbFW8hTLP5IaTNkahFIeqhP2jLl0S1YIxrV4B3Bt7f0PCqfhELAQNkJ/hcu4whtJ0EMzmQO/brHPC3mKSPDDThaQLJf7pnFbZhL355BhvMwpai8SlHTIxxG6hpqjyf0KZp/7tA9lxyGEz9JYOQwe/msPnGtYXlM4tkp5k3xELxU4BVI4kPE9DMPy10wg/FJw+Jxg5CelDvXhKHWF6/AkZpzwV3CgxDib2iopZkouevy1illJ+n555Q2yaF6b0ZDf6Ymf4feqhICvNYOI01ATC+c0wzctyFAV546UCQjJGv5HRsuOzwoOBnOzhnTBhHC+OljW+jsrAQpqDvUbmacvlIzR2g0dgZJOK/A3NkuCJ2wEb8z9USnVx8TRoRETqFS8mswW34sgLtXcF3TYfdGK+x1acNlNS4E+rxYL8gcJzWsOKnIKwBJ8BHBKnd4YQ=" on: tags: true - branch: master repo: tlaziuk/mithril-express-middleware diff --git a/mock.spec.ts b/mock.spec.ts index e89737e..ced4ce5 100644 --- a/mock.spec.ts +++ b/mock.spec.ts @@ -2,9 +2,7 @@ import { expect, } from "chai"; -import { - mock, -} from "./mock"; +import mock from "./mock"; describe(mock.name, () => { it("should mock window", () => { diff --git a/mock.ts b/mock.ts index 82e2afd..adef19e 100644 --- a/mock.ts +++ b/mock.ts @@ -3,7 +3,7 @@ import * as browserMock from "mithril/test-utils/browserMock"; -export function mock(env: object) { +export function mock(env: object = global) { browserMock(env); } diff --git a/package.json b/package.json index f895327..73b85d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mithril-express-middleware", - "version": "1.1.3", + "version": "1.1.4", "description": "express middleware for mithril.js projects", "main": "index.js", "scripts": { @@ -43,6 +43,7 @@ "dependencies": { "@types/express": "^4.0.0", "@types/mithril": "~1.1.0", + "mithril-render": "~1.1.0", "tslib": "~1.7.0" } } diff --git a/render.spec.ts b/render.spec.ts index 62a43d2..568056e 100644 --- a/render.spec.ts +++ b/render.spec.ts @@ -6,25 +6,10 @@ import { mock, } from "./mock"; -import { - isComponent, - render, -} from "./render"; +import render from "./render"; mock(global); -describe(isComponent.name, () => { - it("should be a function", () => { - expect(isComponent).to.be.a("function"); - }); - it("should return boolean", () => { - expect(isComponent(() => undefined)).to.be.a("boolean"); - expect(isComponent(null)).to.be.a("boolean"); - expect(isComponent(undefined)).to.be.a("boolean"); - expect(isComponent({})).to.be.a("boolean"); - }); -}); - describe(render.name, () => { it("should return Promise", () => { expect(render({ view: () => "div" })).to.be.instanceOf(Promise); diff --git a/render.ts b/render.ts index b98bd3d..2d3db9b 100644 --- a/render.ts +++ b/render.ts @@ -1,33 +1,35 @@ // tslint:disable-next-line:no-reference -/// +/// import { Attributes, Children, + ClassComponent, + Component, ComponentTypes, + CVnode, + FactoryComponent, Lifecycle, RouteResolver, Vnode, } from "mithril"; -import mithrilRender from "./renderer"; +import mithrilRender, { + isComponentType, +} from "mithril-render"; export type Defs = ComponentTypes | RouteResolver; -export function isComponent(thing: any): thing is ComponentTypes> { - return !!(thing && (thing.view || typeof thing === "function")); -} - export async function render( payload: Defs, params: { [_: string]: any } = {}, path: string = "", ): Promise { - if (isComponent(payload)) { - return await mithrilRender(payload, params); + // tslint:disable-next-line:whitespace + const m = await import("mithril/render/hyperscript"); + if (isComponentType(payload)) { + return await mithrilRender(m(payload, params)); } else { - // tslint:disable-next-line:whitespace - const m = await import("mithril"); let component: Vnode; if (payload.onmatch) { component = m(await payload.onmatch(params, path), params); @@ -35,7 +37,7 @@ export async function render( component = m("div"); } if (payload.render) { - return await mithrilRender(payload.render(component), params); + return await mithrilRender(payload.render(component)); } } return ""; diff --git a/renderer.ts b/renderer.ts deleted file mode 100644 index 8b4a764..0000000 --- a/renderer.ts +++ /dev/null @@ -1,238 +0,0 @@ -// tslint:disable:ban-types - -// code stolen from "mithril-node-renderer" and rewritten in TypeScript -// it would be great if this code could be merged into main package - -import { - Attributes, - Children, - ComponentTypes, - Lifecycle, - RouteResolver, - Vnode, -} from "mithril"; - -import { - isComponent, -} from "./render"; - -const VOID_TAGS = [ - "area", - "base", - "br", - "col", - "command", - "embed", - "hr", - "img", - "input", - "keygen", - "link", - "meta", - "param", - "source", - "track", - "wbr", - "!doctype", -]; - -const COMPONENT_PROPS = [ - "oninit", - "view", - "oncreate", - "onbeforeupdate", - "onupdate", - "onbeforeremove", - "onremove", -]; - -const isArray = Array.isArray; - -function isObject(thing: any): thing is object { - return typeof thing === "object"; -} - -function isFunction(thing: any): thing is Function { - return typeof thing === "function"; -} - -function camelToDash(str: string) { - return str - .replace(/\W+/g, "-") - .replace(/([a-z\d])([A-Z])/g, "$1-$2"); -} - -function removeEmpties(n: any) { - return n !== ""; -} - -function omit(source: { [_: string]: any }, keys: string[] = []) { - const res: typeof source = {}; - for (const k in source) { - if (keys.indexOf(k) < 0) { - res[k] = source[k]; - } - } - return res; -} - -const copy = omit; - -// shameless stolen from https://github.com/punkave/sanitize-html -function escapeHtml(s: any, replaceDoubleQuote?: boolean) { - if (s === "undefined") { - s = ""; - } - if (typeof s !== "string") { - s = s + ""; - } - s = s.replace(/&/g, "&").replace(//g, ">"); - if (replaceDoubleQuote) { - return s.replace(/"/g, """); - } - return s; -} - -async function setHooks(component: Lifecycle, vnode: Vnode, hooks: Function[]) { - if (component.oninit) { - await component.oninit(vnode); - } - if (component.onremove) { - hooks.push(component.onremove.bind(vnode.state, vnode)); - } -} - -function createAttrString(view: Vnode, escapeAttributeValue: typeof escapeHtml = escapeHtml) { - const attrs = view.attrs; - - if (!attrs || !Object.keys(attrs).length) { - return ""; - } - - return Object.keys(attrs).map((name) => { - const value = attrs[name]; - if (typeof value === "undefined" || value === null || typeof value === "function") { - return; - } - if (typeof value === "boolean") { - return value ? ` ${name}` : ""; - } - if (name === "style") { - if (!value) { - return; - } - let styles = attrs.style; - if (isObject(styles)) { - styles = Object.keys(styles).map((property) => styles[property] !== "" ? - [camelToDash(property).toLowerCase(), styles[property]].join(":") : - "", - ).filter(removeEmpties).join(";"); - } - return styles !== "" ? ` style="${escapeAttributeValue(styles, true)}"` : ""; - } - - // Handle SVG tags specially - if (name === "href" && view.tag === "use") { - return ` xlink:href="${escapeAttributeValue(value, true)}"`; - } - - return ` ${(name === "className" ? "class" : name)}="${escapeAttributeValue(value, true)}"`; - }).join(""); -} - -async function createChildrenContent(view: Vnode, options: { [_: string]: any }, hooks: Function[]) { - if (view.text != null) { - return options.escapeString(view.text); - } - if (isArray(view.children) && !view.children.length) { - return ""; - } - return await _render(view.children, options, hooks); -} - -export async function mithrilRender( - view: Children | ComponentTypes, - attrs: Attributes = {}, - options: { [_: string]: any } = attrs, -) { - if (isComponent(view)) { - // tslint:disable-next-line:whitespace - view = await import("mithril").then((m) => m(view as any, attrs)); - } else { - options = attrs || {}; - } - const hooks: Function[] = []; - - options = { - escapeAttributeValue: escapeHtml, - escapeString: escapeHtml, - strict: false, - ...options, - }; - - const result = await _render(view, options, hooks); - - hooks.forEach((hook) => hook()); - - return result; -} - -async function _render( - view: Children | ComponentTypes, - options: { [_: string]: any }, - hooks: Function[], -): Promise { - if (!view) { - return ""; - } - - if (typeof view === "string") { - return options.escapeString(view); - } - - if (typeof view === "number" || typeof view === "boolean") { - return view + ""; - } - - if (isArray(view)) { - const res: Array> = []; - for (const v of view) { - res.push(_render(v, options, hooks)); - } - return (await Promise.all(res)).join(""); - } - - if (isComponent(view)) { - // tslint:disable-next-line:whitespace - view = await import("mithril").then((m) => m(view as any, options)); - } - - if (view.attrs) { - await setHooks(view.attrs, view, hooks); - } - - if (view.tag === "<") { - return view.children + ""; - } - const children = await _render(view.children, options, hooks); - if (view.tag === "#") { - return options.escapeString(children); - } - if (view.tag === "[") { - return children; - } - if (typeof view.tag === "string") { - if ((!children || !children.length) && (options.strict || VOID_TAGS.indexOf(view.tag.toLowerCase()) >= 0)) { - // tslint:disable-next-line:max-line-length - return `<${view.tag} ${createAttrString(view, options.escapeAttributeValue)} ${(options.strict ? "/" : "")}>`; - } - return [ - `<${view.tag} ${createAttrString(view, options.escapeAttributeValue)}>`, - children, - ``, - ].join(""); - } - return ""; -} - -export default mithrilRender; diff --git a/types/mithril-node-render.d.ts b/types/mithril-node-render.d.ts deleted file mode 100644 index 659fa4b..0000000 --- a/types/mithril-node-render.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "mithril-node-render"; diff --git a/types/mithril.d.ts b/types/mithril.d.ts index e6d6552..837423a 100644 --- a/types/mithril.d.ts +++ b/types/mithril.d.ts @@ -1,2 +1,3 @@ declare module "mithril/querystring/parse"; declare module "mithril/test-utils/browserMock"; +declare module "mithril/render/hyperscript";