Create composable (x)html components with pure javascript functions
npm install somatic
/**
* Main API function; returns pure js composable html
* component wrapper function based on a markup renderer function.
* @param baseRenderer: specifies the function that returns the raw markup for the component.
* @param options: options used to pre-configure wrapper function
*/
createComponent(baseRenderer: Renderer, options: Partial>): Component;
/**
* create module with default options applied when options not supplied or fails
*/
setOptions(defaultOptions: Partial): Module
/**
* resolve the component argument to a component function,
* using the supplied resolution argument
*/
resolveComponent(name: string, options?: Options): AnyRenderer;
/**
* Converts ast to markup consisting solely of primitive components (html elements)
*/
evalAst(ast: AbstractSyntaxTree, options?: Options): string;
/**
* generate JSON AST from xml markup
*/
generateAST(markup: string): AbstractSyntaxTree;
/**
* Helper method to stringify object argument passed to template function
*/
stringifyProps(props: T, encode?: boolean): string;
/**
* Helper method to stringify data-{x} arguments passed to template function
*/
stringifyDataProps(props: AnyObject): string;
/**
* Helper method to stringify CSS object or passthrough CSS string
*/
stringifyCSS(style: CssStyle | string): string;
/**
* Parse CSS string into JSON-style object
*/
parseCSS(style: string): CssStyle;
/**
* Generate HTMl markup
*/
generateHTML(tag: string, props?: ObjectDictionary, children?: any): string;
/**
* Formats html
*/
formatHTML(htmlOrAst: string | AbstractSyntaxTree, options?: AnyObject): string;
`
Types
`
interface AbstractSyntaxTree {
name: string,
type: string,
value: string,
children: any[],
attributes: AnyObject
}
interface Options {
readonly failAsDiv: boolean, // render tags that cannot be resolved as html divs?
readonly resolution: Partial<{
readonly dict: ObjectDictionary // resolution dictionary (first priority)
readonly func: (string) => AnyComponent // resolution function (second priority)
}>
readonly formatMarkup: boolean // format the output as xml/html?
readonly debugMode: boolean
}
interface ComponentOptions extends Options {
readonly name: string, // component name, for documentation and debugging purposes
readonly defaultProps: Partial,
readonly isContainer: boolean // whether component have can children elements
}
type Renderer = (props?: T, children?: any) => string;
type AnyRenderer = Renderer;
type Component = Renderer & {
readonly componentOptions: ComponentOptions, // for documentation and inspection purposes
setOptions: (opts: ComponentOptions) => Component // override initial options
}
type AnyComponent = Component;
interface ObjectDictionary { [key: string]: T}
type AnyObject = ObjectDictionary;
`
Example
Note that we are able to compose components from both base html elements
and other custom higher-order components.
Any component references in the markup are resolved using
the resolution member of the options passed to the createComponent function.
`
let components = () => _components;
const somatic: Module = (require("./index") as Module).setOptions({
resolution: {
func: (name) => _components[name]
},
debugMode: false
})
const _ = somatic.stringifyProps;
let _components = {
Banner: somatic.createComponent(function (props: { logo, title }, children) {
return
;
}, { name: "Banner", resolution: { func: (ref) => components()[ref] } }),
StackPanelHorizontal: somatic.createComponent(function (props, children) {
return
${child}).join("")}
;
}, { name: "StackPanelHorizontal", resolution: { func: (ref) => components()[ref] } }),
StackPanelVertical: somatic.createComponent(function (props, children) {
var stringifiedProps = _({
style: {
display: "flex",
flexDirection: "column",
justifyContent: "flex-start"
}
}, false);
return
${child}).join("")
;
}, { name: "StackPanelVertical", resolution: { func: (ref) => components()[ref] } }),
Footer: somatic.createComponent(function (props, children) {
return
;
}, { name: "Footer", resolution: { func: (ref) => components()[ref] } }),
Page: somatic.createComponent(function (props, children) {
return
;
}, { name: "Page", resolution: { func: (ref) => components()[ref] } })
};
try {
let markup = components().Page();
console.log("Page markup: " + markup);
}
catch (e) {
console.log(e.toString());
}
`
Output is
`
Page markup:
Eureka
YCombinator
col1,row1
col1,row2
col2,row1
col2,row2
``