import { structureToString, parseHTMLStructure, HTMLTag, HTMLText, ElementStructure } from "./htmlStructure" import * as Helper from "./helper" import * as CoreLogic from "./coreLogic" import * as Leaf from "../../dist/leaf.module" import * as fs from "fs" import * as pathModule from "path" import * as ts from "typescript" //https://github.com/vvakame/typescript-formatter/blob/master/lib/formatter.ts import * as formatTypescript from "typescript-formatter/lib/formatter" // Context for template type WidgetTree = { [index: string]: WidgetTree | WidgetTemplate } interface DebugContext { file: string path: string name: string } export class Context { public widgets: WidgetTemplate[] = [] constructor(public readonly path: string, public debug: boolean = false) { } public parseInterface(widget: WidgetTemplate) { let vms: string[] = [] for (let vm of widget.parser.VMs) { if (vm.indexOf("i18n") == 0) { vms.push(`${vm}:string`) } } let widgetI18nEntry = "" let vmInterface = "" if (vms.length > 0) { vmInterface += vms.join("\n") vmInterface += "\n" } let childrenInterface = "" if (widget.children.length > 0) { childrenInterface += widget.children.map((item) => { return this.parseInterface(item) }).join("") } if (vmInterface || childrenInterface) { widgetI18nEntry += `\n${widget.name}:{\n` + vmInterface + childrenInterface + `}\n` } return widgetI18nEntry } public buildI18n(option: { export: boolean, tree: WidgetTree }) { let root = ` ${option.export && "export" || "declare"} namespace R{ interface I18n{ ${this.recursiveGenerateI18nDefinition(option.tree)} } }` return root } merge(tree: WidgetTree) { for (let prop in tree) { let sub = tree[prop] } } public recursiveGenerateI18nDefinition(tree: WidgetTree) { let result = "" let nameIndex = [] for (let prop in tree) { let sub = tree[prop] let name = prop name = Leaf.Util.capitalize(name) name = `${name.charAt(0).toLowerCase() + name.slice(1)}` if (sub instanceof WidgetTemplate) { result += this.parseInterface(sub) nameIndex.push(name) } else { let resultabc = Helper.indentForward(this.recursiveGenerateI18nDefinition(sub)) if (resultabc) { if (nameIndex.indexOf(name) >= 0 && result.indexOf(`${name}:{`) >= 0) { let slicePoint = result.indexOf(`${name}:{`) + `${name}:{`.length result = result.slice(0, slicePoint) + "\n" + resultabc + result.slice(slicePoint) } else { result += `\n${name}:{\n` result += resultabc result += `}` } } } } return result } public rootName: string = "R" includeWidgetByRelativeFilePath(path: string) { let content = fs.readFileSync(pathModule.join(this.path, path), "utf8") let tags = parseHTMLStructure(content) let ext = pathModule.extname(path) let base = pathModule.basename(path, ext) let dir = pathModule.dirname(path) let fullBase = pathModule.join(dir, base) if (fullBase[0] != "/") fullBase = "/" + fullBase this.widgets.push(new WidgetTemplate(this, tags, fullBase)) } compileTypescript(): string { // 1. build widgets into tree // 2. recursively generate widget from tree let tree = this.buildTree(this.widgets) let resultTypescript = this.recursiveGenerateTypescript(tree) let widgetPathes = this.getAllWidgetPath(this.widgets) let extra = `let compiledAt = "${new Date().toString()}"` && "" return (formatTypescript["default"] as any)("R.ts", `import * as Leaf from "leaf-ts" namespace ${this.rootName}{\n` + Helper.indentForward(resultTypescript) + ` ${extra} export const widgets = ${JSON.stringify(widgetPathes)} \n}` + ` window["${this.rootName}"] = ${this.rootName} module.exports = module.exports || {} module.exports.${this.rootName} = ${this.rootName} `) } compileJavascript(): string { let tree = this.buildTree(this.widgets) let resultJavascript = this.recursiveGenerateJavascript(tree) let widgetPathes = this.getAllWidgetPath(this.widgets) let extra = "" return ` var Leaf = require("leaf-ts") var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var ${this.rootName} = (function(){ var _R_Scope = ${Helper.indentForward(resultJavascript).trim()} ${extra} _R_Scope.widgets = ${JSON.stringify(widgetPathes)} return _R_Scope })(); window.${this.rootName} = ${this.rootName} module.exports = module.exports || {} module.exports.${this.rootName} = ${this.rootName} ` } compileDefinition(option: { export: boolean }): string { let tree = this.buildTree(this.widgets) let resultTypescriptDefinition = this.recursiveGenerateTypescriptDefinition(tree) let widgetPathes = this.getAllWidgetPath(this.widgets) let content = `${option.export && "export" || "declare"} namespace ${this.rootName}{\n` + Helper.indentForward(resultTypescriptDefinition) + `\n}` if (option.export) { content = `import * as Leaf from "leaf-ts" ` + content } if (this.shouldFormat) { content = (formatTypescript["default"] as any)("R.ts", content) } let i18nDefinition = this.buildI18n({ export: option.export, tree }) content = content + i18nDefinition return content } shouldFormat: boolean = false compileStyle(): string { return this.widgets.map(widget => widget.generateStyle()).filter(item => !!item).join("\n") } private getAllWidgetPath(widgets: WidgetTemplate[]): string[] { let result = [] for (let child of widgets) { result.push(...child.getAllWidgetPath()) } return result } private buildTree(widgets: WidgetTemplate[]): WidgetTree { let root: WidgetTree = {} for (let widget of widgets) { let names = widget.path.split("/").filter(item => !!item) let name = names.pop() let cur = root for (let folder of names) { folder = Leaf.Util.capitalize(Leaf.Util.slugToCamel(folder)) if (cur[folder]) { cur = cur[folder] as WidgetTree } else { cur = cur[folder] = {} } if (cur instanceof WidgetTemplate) { throw new Error("Widget shouldn't have name confliction the the same level folder:" + folder) } } cur[name] = widget } return root } private recursiveGenerateJavascript(tree: WidgetTree, valueName?: string): string { let result = "" for (let prop in tree) { let sub = tree[prop] if (sub instanceof WidgetTemplate) { result += `_R_Scope.${Leaf.Util.capitalize(sub.name)} = ` + sub.compileJavascript({ debug: this.debug }) } else { let name = prop name = Leaf.Util.capitalize(name) result += `_R_Scope.${name} = ` result += Helper.indentForward(this.recursiveGenerateJavascript(sub, `_R_Scope.${name}`)).trim() } } return `(function(v){ var _R_Scope = v || {} ${Helper.indentForward(result)} return _R_Scope })(${valueName || "null"});` } private recursiveGenerateTypescriptDefinition(tree: WidgetTree): string { let result = "" for (let prop in tree) { let sub = tree[prop] if (sub instanceof WidgetTemplate) { result += sub.compileTypescriptDefinition({ debug: this.debug }) } else { let name = prop name = Leaf.Util.capitalize(name) result += `export namespace ${name}{` result += Helper.indentForward(this.recursiveGenerateTypescriptDefinition(sub)) result += "}" } } return result } private recursiveGenerateTypescript(tree: WidgetTree): string { let result = "" for (let prop in tree) { let sub = tree[prop] if (sub instanceof WidgetTemplate) { result += sub.compileTypescript({ debug: this.debug }) } else { let name = prop name = Leaf.Util.capitalize(name) result += `export namespace ${name}{` result += Helper.indentForward(this.recursiveGenerateTypescript(sub)) result += "}" } } return result } generateNamespace(t: WidgetTemplate, holder: string = "WidgetContent"): string { // nested namespace let parts = pathModule.normalize(pathModule.dirname(t.path)).split("/").map(item => item.trim()).filter(item => !!item) let n: string let result = `` while (n = parts.pop()) { result = `export namespace ${n}{ ${Helper.indentForward(result)} }` } return result } } interface WidgetTemplateData { name: string templateNodes: ElementStructure[] children: WidgetTemplateData[] } export class WidgetTemplate implements WidgetTemplateData { children: WidgetTemplate[] = [] name: string parent: WidgetTemplate = null parser: WidgetStructureParserDesign protected error(arg: any, ...args: any[]) { console.error(arg, ...args) } public id: string = null public debugContext: DebugContext = null constructor(public readonly context: Context, public readonly templateNodes: ElementStructure[], public readonly path: string) { this.name = Leaf.Util.slugToCamel(pathModule.basename(path)) this.debugContext = { name: this.name, file: this.path, path: this.path } this.parser = new WidgetStructureParser(templateNodes, path, this.debugContext) this.id = CoreLogic.pathToId(this.path) for (let template of this.parser.templates) { if (!template.name) { this.error("template should have a name") continue } this.addChild(template.tag.children, template.name) } } getAllWidgetPath(): string[] { let path = [this.path] for (let child of this.children) { path.push(...child.getAllWidgetPath()) } return path } addChild(tags: ElementStructure[], name: string) { let w = new WidgetTemplate(this.context, tags, pathModule.join(this.path, name)) w.parent = this this.children.push(w) } private getSlotInterfacesFromSlots(slots: { name: string tag: HTMLTag index: number }[]) { let result: string[] = [] for (let slot of slots) { result.push(`${slot.name}: Leaf.Widget`) } if (result.length > 0) { return Helper.indentForward(`public readonly asSlot: Leaf.WidgetSlotBehavior<{ ${Helper.indentForward(result.join("\n"))} }>`) } return "" } private getVMInterfacesFromVMs(vms: string[]): string { // 1. merge roots // 2. generate interfaces let maps = {} for (let vm of vms) { let parts = vm.split(".") let key = parts.shift() if (!maps[key]) maps[key] = {} let parent = maps[key] while (key = parts.shift()) { if (!parent[key]) { parent[key] = {} } parent = parent[key] } } let gen = (maps) => { let result = [] for (let key in maps) { let child = maps[key] if (Object.keys(child).length === 0) { result.push(`${key}?: any`) } else { result.push(`${key}?: ` + gen(maps[key])) } } return `{ ${Helper.indentForward(result.join("\n"))} }` } return gen(maps) } private eventInterfacesFromLists(lists: { name: string type: "inline" | "reference" | "void" reference: string }[], indent = true) { let infs = [] for (let list of lists) { // Not that useful to deduce the child type, so just don't do it for now // Deduce child type to R's type will cause confusion to newbie. // Newbies may expect the // type of it to be the type of actully extended class, which is not possible to // be deduced by now. infs.push("onChildAdd" + Leaf.Util.capitalize(list.name) + "(child:any)") infs.push("onChildRemove" + Leaf.Util.capitalize(list.name) + "(child:any)") } if (indent) return Helper.indentForward(infs.join("\n")) return infs.join("\n") } private eventInterfacesFromUIs(uis: { name: string tagName: string tag: HTMLTag }[], indent = true) { let infs = [] for (let ui of uis) { let events = CoreLogic.getPossibleEventsByTagName(ui.tagName) for (let event of events) { let eventType = CoreLogic.getEventTypeStringByEventName(event) let inf = "on" + Leaf.Util.capitalize(event) + Leaf.Util.capitalize(Leaf.Util.slugToCamel(ui.name)) + `(e?:${eventType})` infs.push(inf) } } let events = CoreLogic.getPossibleEventsByTagName("div") for (let event of events) { let eventType = CoreLogic.getEventTypeStringByEventName(event) let inf = "on" + Leaf.Util.capitalize(event) + Leaf.Util.capitalize("node") + `(e?:${eventType})` infs.push(inf) } if (indent) return Helper.indentForward(infs.join("\n")) return infs.join("\n") } public generateStyle() { let childStyles: string = this.children.map(item => item.generateStyle()).filter(item => !!item).join("\n") if (this.parser.styles.length === 0 && childStyles.length === 0) return "" let widgetStyle: string = "" if (this.parser.styles.length > 0) { widgetStyle = structureToString(this.parser.styles[0]) } // child style should be at same level with parent, since every class are generated by a hash, so it's unlikely // to conflict. And if we don't nested we can preview the child alone return `.${this.id}{ ${Helper.indentForward(widgetStyle)} } ${childStyles} ` } public compileJavascript(option?: { debug?: boolean }): string { // 1. compile myself. let WidgetName = Leaf.Util.capitalize(Leaf.Util.slugToCamel(this.name)) let UIInterfaces = "{" + this.parser.UIs.map(item => `${Leaf.Util.slugToCamel(item.name)}:${CoreLogic.getElementTypeStringByTagName(item.tagName)}`).join(",") + "}" let VMInterfaces = "{" + this.parser.VMs.map(item => `${item}:any`).join(",") + "}" let VMStringLiterals = this.parser.VMs.map(item => `"${item}"`).join("|") || "null" let Template = this.parser.getTemplateFunctionString() let VMStringArray = "[" + this.parser.VMs.map(item => `"${item}"`).join(",") + "]" let ListsDeclarations = this.parser.lists.map((list) => { if (list.reference) return `${list.name}:Leaf.List<${this.context.rootName}.${CoreLogic.pathToResourceResolution(list.reference)}>` return `${list.name}:Leaf.List` }).join("\n") let WidgetsDeclarations = this.parser.widgets.filter(widget => !!widget.name).map((widget) => { if (widget.path) { return `${widget.name}:${this.context.rootName}.${CoreLogic.pathToResourceResolution(widget.path)}` } return `${widget.name}:Leaf.WidgetAny` }).join("\n") let Path = `"${this.path}"` let TestDatas = JSON.stringify(this.parser.testDatas) let InitialData = JSON.stringify(this.parser.initialData) let BindedLists = JSON.stringify(this.parser.lists) let BindedWidgets = JSON.stringify(this.parser.widgets) let RootName = this.context.rootName let self = Leaf.render(this.widgetJavascriptCompileTemplate, { WidgetName, UIInterfaces, VMInterfaces, VMStringLiterals, Template, VMStringArray, ListsDeclarations, WidgetsDeclarations, Path, TestDatas, BindedLists, BindedWidgets, InitialData, RootName }) let childrenCode = "" if (this.children.length > 0) { childrenCode = Helper.indentForward(this.children.map(item => `${WidgetName}.${Leaf.Util.capitalize(item.name)} = ` + item.compileJavascript()).join("\n")) } let scope = `(function(){ ${Helper.indentForward(self).trim()} ${childrenCode} return ${WidgetName} })(); ` return scope } public compileTypescriptDefinition(option?: { debug?: boolean }): string { // 1. compile myself. let WidgetName = Leaf.Util.capitalize(Leaf.Util.slugToCamel(this.name)) let UIInterfaces = "{" + this.parser.UIs.map(item => `${Leaf.Util.slugToCamel(item.name)}:${CoreLogic.getElementTypeStringByTagName(item.tagName)}`).join(",") + "}" let VMInterfaces = this.getVMInterfacesFromVMs(this.parser.VMs);//"{" + this.parser.VMs.map(item => `"${item}":any`).join(",") + "}" let SlotInterfaces = this.getSlotInterfacesFromSlots(this.parser.slots); let VMStringLiterals = this.parser.VMs.map(item => `"${item}"`).join("|") || "null" let Template = this.parser.getTemplateFunctionString() let VMStringArray = "[" + this.parser.VMs.map(item => `"${item}"`).join(",") + "]" let ListsDeclarations = this.parser.lists.map((list) => { if (list.reference) return `${list.name}:Leaf.List<${this.context.rootName}.${CoreLogic.pathToResourceResolution(list.reference)}>` return `${list.name}:Leaf.List` }).join("\n") let WidgetsDeclarations = this.parser.widgets.filter(widget => !!widget.name).map((widget) => { if (widget.path) { return `${widget.name}:${this.context.rootName}.${CoreLogic.pathToResourceResolution(widget.path)}` } return `${widget.name}:Leaf.WidgetAny` }).join("\n") let Path = `"${this.path}"` let TestDatas = JSON.stringify(this.parser.testDatas) let InitialData = JSON.stringify(this.parser.initialData) let BindedLists = JSON.stringify(this.parser.lists) let BindedWidgets = JSON.stringify(this.parser.widgets) let RootName = this.context.rootName let UIEventInterfaces = [ this.eventInterfacesFromUIs(this.parser.UIs), this.eventInterfacesFromLists(this.parser.lists) ].join("\n") let self = Leaf.render(this.widgetTypescriptDefinitionCompileTemplate, { WidgetName, UIInterfaces, VMInterfaces, VMStringLiterals, Template, VMStringArray, ListsDeclarations, WidgetsDeclarations, Path, TestDatas, BindedLists, BindedWidgets, InitialData, RootName, UIEventInterfaces, SlotInterfaces }) if (this.children.length > 0) { self = self + `export namespace ${WidgetName}{` + Helper.indentForward(this.children.map(item => item.compileTypescriptDefinition()).join("\n")) + "}" } return self } public compileTypescript(option?: { debug?: boolean }): string { // 1. compile myself. let WidgetName = Leaf.Util.capitalize(Leaf.Util.slugToCamel(this.name)) let UIInterfaces = "{" + this.parser.UIs.map(item => `${Leaf.Util.slugToCamel(item.name)}:${CoreLogic.getElementTypeStringByTagName(item.tagName)}`).join(",") + "}" //let VMInterfaces = "{" + this.parser.VMs.map(item => `${item}:any`).join(",") + "}" let VMInterfaces = this.getVMInterfacesFromVMs(this.parser.VMs);//"{" + this.parser.VMs.map(item => `"${item}":any`).join(",") + "}" let SlotInterfaces = this.getSlotInterfacesFromSlots(this.parser.slots); let VMStringLiterals = this.parser.VMs.map(item => `"${item}"`).join("|") || "null" let Template = this.parser.getTemplateFunctionString() let UIEventInterfaces = [ this.eventInterfacesFromUIs(this.parser.UIs), this.eventInterfacesFromLists(this.parser.lists) ].join("\n") let VMStringArray = "[" + this.parser.VMs.map(item => `"${item}"`).join(",") + "]" let ListsDeclarations = this.parser.lists.map((list) => { if (list.reference) return `${list.name}:Leaf.List<${this.context.rootName}.${CoreLogic.pathToResourceResolution(list.reference)}>` return `${list.name}:Leaf.List` }).join("\n") let WidgetsDeclarations = this.parser.widgets.filter(widget => !!widget.name).map((widget) => { if (widget.path) { return `${widget.name}:${this.context.rootName}.${CoreLogic.pathToResourceResolution(widget.path)}` } return `${widget.name}:Leaf.WidgetAny` }).join("\n") let Path = `"${this.path}"` let TestDatas = JSON.stringify(this.parser.testDatas) let InitialData = JSON.stringify(this.parser.initialData) let BindedLists = JSON.stringify(this.parser.lists) let BindedWidgets = JSON.stringify(this.parser.widgets) let RootName = this.context.rootName let self = Leaf.render(this.widgetTypescriptCompileTemplate, { WidgetName, UIInterfaces, VMInterfaces, VMStringLiterals, Template, VMStringArray, ListsDeclarations, WidgetsDeclarations, Path, TestDatas, BindedLists, BindedWidgets, InitialData, RootName, UIEventInterfaces, SlotInterfaces }) if (this.children.length > 0) { self = self + `export namespace ${WidgetName}{` + Helper.indentForward(this.children.map(item => item.compileTypescript()).join("\n")) + "}" } return self } widgetJavascriptCompileTemplate: string = `function {{WidgetName}}(){ Leaf.GeneratedWidget.call(this,{{Template}},"{{WidgetName}}",{{VMStringArray}}) this.TestDatas = {{WidgetName}}.TestDatas this.BindedLists = {{WidgetName}}.BindedLists this.BindedWidgets = {{WidgetName}}.BindedWidgets this.InitialData = {{WidgetName}}.InitialData this.path = {{WidgetName}}.path this.GeneratedConstructor = {{WidgetName}} this.R = {{RootName}} if({{WidgetName}}.InitialData){ this.renderRecursive({{WidgetName}}.InitialData) } } __extends({{WidgetName}},Leaf.GeneratedWidget) {{WidgetName}}.widgetName = "{{WidgetName}}" {{WidgetName}}.TestDatas = {{TestDatas}} {{WidgetName}}.BindedLists = {{BindedLists}} {{WidgetName}}.BindedWidgets = {{BindedWidgets}} {{WidgetName}}.path={{Path}} {{WidgetName}}.prototype.path = {{Path}} {{WidgetName}}.InitialData = {{InitialData}} {{WidgetName}}.prototype.widgetName = "{{WidgetName}}" ` widgetTypescriptDefinitionCompileTemplate: string = ` export class {{WidgetName}} extends Leaf.GeneratedWidget<{{UIInterfaces}},{{VMInterfaces}},{ {{UIEventInterfaces}} }>{ constructor(); {{ListsDeclarations}} {{WidgetsDeclarations}} public widgetName:"{{WidgetName}}" public path:{{Path}} public GeneratedConstructor:{new():{{WidgetName}}} static widgetName: "{{WidgetName}}" {{SlotInterfaces}} {{UIEventInterfaces}} } ` widgetTypescriptCompileTemplate: string = ` export class {{WidgetName}} extends Leaf.GeneratedWidget<{{UIInterfaces}},{{VMInterfaces}},{ {{UIEventInterfaces}} }>{ constructor() { super({{Template}}, "{{WidgetName}}",{{VMStringArray}} as any) if({{WidgetName}}.InitialData){ this.renderRecursive({{WidgetName}}.InitialData) } } {{ListsDeclarations}} {{WidgetsDeclarations}} static TestDatas = {{TestDatas}} static BindedLists = {{BindedLists}} static BindedWidgets = {{BindedWidgets}} static path = {{Path}} static widgetName = "{{WidgetName}}" static InitialData = {{InitialData}} public TestDatas = {{WidgetName}}.TestDatas public BindedLists = {{WidgetName}}.BindedLists public BindedWidgets = {{WidgetName}}.BindedWidgets public InitialData = {{WidgetName}}.InitialData public path = {{WidgetName}}.path public GeneratedConstructor = {{WidgetName}} public R = {{RootName}} {{SlotInterfaces}} {{UIEventInterfaces}} } ({{WidgetName}}["prototype"] as any).widgetName = "{{WidgetName}}" ` } abstract class WidgetStructureParserDesign { buildFunctionString: string public templates: { name: string tag: HTMLTag }[] = [] public widgets: { path: string name: string }[] = [] public UIs: { name: string tag: HTMLTag tagName: string }[] = [] public VMs: string[] = [] public slots: { name: string tag: HTMLTag index: number }[] = [] public lists: { name: string type: "inline" | "reference" | "void" reference: string }[] = [] public testDatas: { name: string content: string json: any }[] = [] public initialData: any = null public scripts: HTMLTag[] = [] public styles: HTMLTag[] = [] public rootWidgetNode: HTMLTag = null constructor(public readonly tags: ElementStructure[], public readonly path: string, public readonly debugContext: DebugContext) { this.parse(tags) } abstract parse(rootElements: ElementStructure[]) abstract parseStructure(s: ElementStructure) abstract parseElement(tag: HTMLTag); abstract parseText(tag: HTMLText); abstract extractTemplates(tag: ElementStructure): { name: string, tag: HTMLTag }[]; abstract getTemplateFunctionString(): string; protected abstract getDynamicProp(tag: HTMLTag): Leaf.ElementConfigProperties protected abstract addUI(name: string, tag: HTMLTag); protected abstract addVM(name: string); protected abstract addVMByDynamicValueConfigs(configs: Leaf.DynamicValueConfig[]) protected abstract addTemplate(tag: HTMLTag, name?: string); } export class WidgetStructureParser extends WidgetStructureParserDesign { protected error(arg: any, ...args: any[]) { console.error(this.debugContext.file, ":", arg, ...args) } getTemplateFunctionString() { return `function(){ var _e = this._e.bind(this) var _t = this._t.bind(this) return ${this.buildFunctionString} } ` } parse(rootElements: ElementStructure[]) { let scripts = [] let styles = [] let templates = [] for (let item of rootElements) { if (item.type === "element") { let el = item as HTMLTag if (el.name === "script") { scripts.push(el) if (el.attrs["data-role"] == "test") { let content = structureToString(el) try { this.testDatas.push({ name: el.attrs["data-name"] || `test-${this.testDatas.length + 1}` , content: structureToString(content) , json: Helper.parseLooseJSON(content) }) } catch (e) { this.error(content) this.error("Fail to parse test data", e) } } else if (el.attrs["data-role"] == "init") { let content = structureToString(el) try { this.initialData = Helper.parseLooseJSON(content) } catch (e) { this.error(content) this.error("Fail to parse init data", e) } } } else if (el.name === "style") { styles.push(el) } else if (el.name === "template") { this.addTemplate(el, el.attrs["data-name"]) templates.push(el) } else { if (this.rootWidgetNode) { throw new Error(`Multiple root element not allowed ${this.path}`) } this.rootWidgetNode = el } } } this.styles = styles this.scripts = scripts if (this.rootWidgetNode) { let className = this.rootWidgetNode.attrs["class"] || "" let classes = className.split(" ").filter(item => !!item) classes.unshift(CoreLogic.pathToId(this.path)) this.rootWidgetNode.attrs["class"] = classes.join(" ") this.buildFunctionString = this.parseStructure(this.rootWidgetNode) || `document.createElement("div")` } } parseStructure(s: ElementStructure) { if (s.type === "text") { return this.parseText(s as HTMLText) } else if (s.type === "element") { return this.parseElement(s as HTMLTag) } } parseText(text: HTMLText) { let fnName = "_t" return `${fnName}(${JSON.stringify(this.getDynamicStringConfig(text.content))})` } parseElement(tag: HTMLTag) { if (tag.name === "template") { this.addTemplate(tag) return null } if (tag.attrs["data-list"]) { let listName = tag.attrs["data-list"] let childName = tag.attrs["data-list-item"] let type: string, reference: string if (childName) { type = "reference" // /folder/parent ~ ./some/child => /folder/some/child reference = pathModule.join(this.path, "..", childName) } else { let templates = this.extractTemplates(tag) if (templates[0]) { let child = templates[0] if (!child.name) child.name = listName + "Item" type = "inline" // resolve to /parent/child reference = pathModule.join(this.path, "/" + child.name) } else { type = "void" reference = null } this.templates.push(...templates) } this.lists.push({ name: listName, type, reference } as any) } if (tag.attrs["data-id"]) { this.addUI(tag.attrs["data-id"], tag) } if (tag.name === "slot") { this.slots.push({ name: tag.attrs["data-slot"], tag: tag, index: this.slots.length }) if (tag.attrs["data-slot"]) { this.addUI(tag.attrs["data-slot"], tag) } } if (tag.name === "widget") { let path: string if (tag.attrs["data-widget"]) { path = pathModule.resolve(pathModule.dirname(this.path), tag.attrs["data-widget"]) } else { path = null } let widget = { path: path, name: tag.attrs["data-name"] || CoreLogic.pathToName(tag.attrs["data-widget"] || "") } if (!widget.name) { this.error("widget requires a name") } this.widgets.push(widget) } let children = tag.children || [] let childFunctionStrings = children.map(tag => this.parseStructure(tag)).filter(item => !!item) let fnName = "_e" return `${fnName}("${tag.name}", ${JSON.stringify(this.getDynamicProp(tag))},[${childFunctionStrings.join(",")}])` } protected getDynamicStringConfig(content: string) { let result = { templates: Leaf.CoreLogic.createDynamicValueConfigs(content) } this.addVMByDynamicValueConfigs(result.templates) return result } protected getDynamicProp(tag: HTMLTag): Leaf.ElementConfigProperties { let tagName = tag.name let attr = tag.attrs let result: { [index: string]: Leaf.DynamicPropertyConfig | string } = {} let temp: Leaf.DynamicPropertyConfig = null for (let name in attr) { let value = attr[name] result[name] = temp = { name, templates: Leaf.CoreLogic.createDynamicValueConfigs(value) } this.addVMByDynamicValueConfigs(temp.templates) } return result } protected addUI(name: string, tag: HTMLTag) { for (let ui of this.UIs) { if (ui.name === name) { this.error("duplicate data-id", ui.name) return } } this.UIs.push({ name, tag, tagName: tag.name }) } protected addVM(name: string) { if (this.VMs.indexOf(name) >= 0) return this.VMs.push(name) } protected addVMByDynamicValueConfigs(configs: Leaf.DynamicValueConfig[]) { for (let config of configs) { if (config.type === "value") { this.addVM(config.content) } } } protected addTemplate(tag: HTMLTag, name?: string) { name = tag.attrs["data-name"] || name if (!name) { this.error("template need a name") return } for (let template of this.templates) { if (template.name == name) { this.error(`duplicate template name ${name} `) return } } this.templates.push({ name, tag }) } extractTemplates(e: ElementStructure): { name: string, tag: HTMLTag }[] { if (e.type === "text") return [] let tag = e as HTMLTag let result = [] if (!tag.children) tag.children = [] tag.children = tag.children.filter((child: HTMLTag) => { if (child.name === "template") { result.push({ name: child.attrs["data-name"], tag: child }) return false } result = result.concat(this.extractTemplates(child)) return true }) return result } }