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
}
}