From 6542c6e46be1c11d1fbe9f8089dd0be72c24d343 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 6 Jul 2024 21:24:36 +0800 Subject: [PATCH] refactor: optimize the SUI page compiler --- sui/api/sui_test.go | 2 +- sui/core/build.go | 525 ++++++++++++++----------------- sui/core/compile.go | 71 ++++- sui/core/context.go | 7 +- sui/core/types.go | 28 +- sui/storages/local/build_test.go | 14 +- 6 files changed, 336 insertions(+), 311 deletions(-) diff --git a/sui/api/sui_test.go b/sui/api/sui_test.go index 66b804677..aba20f8b6 100644 --- a/sui/api/sui_test.go +++ b/sui/api/sui_test.go @@ -37,7 +37,7 @@ func prepare(t *testing.T) { t.Fatal(err) } - err = advanced.Build(&core.BuildOption{SSR: true}) + err = advanced.Build(&core.BuildOption{SSR: true, AssetRoot: "/unit-test/assets"}) if err != nil { t.Fatal(err) } diff --git a/sui/core/build.go b/sui/core/build.go index ec8523c3d..92c4c53e4 100644 --- a/sui/core/build.go +++ b/sui/core/build.go @@ -3,7 +3,6 @@ package core import ( "bufio" "fmt" - "path/filepath" "regexp" "strings" @@ -19,324 +18,228 @@ var cssRe = regexp.MustCompile(`([\.a-z0-9A-Z-:# ]+)\{`) var langFuncRe = regexp.MustCompile(`L\s*\(\s*["'](.*?)["']\s*\)`) var langAttrRe = regexp.MustCompile(`'::(.*?)'`) -// Build is the struct for the public +// Build build the page func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Document, []string, error) { - // Create the context if not exists if ctx == nil { ctx = NewBuildContext(nil) } - warnings := []string{} + ctx.sequence++ html, err := page.BuildHTML(option) if err != nil { - warnings = append(warnings, err.Error()) + ctx.warnings = append(ctx.warnings, err.Error()) } - // Add Style & Script & Warning doc, err := NewDocumentString(html) if err != nil { - warnings = append(warnings, err.Error()) + return nil, ctx.warnings, err } - // Append the nested html - err = page.parse(ctx, doc, option, warnings) + err = page.buildComponents(doc, ctx, option) if err != nil { - warnings = append(warnings, err.Error()) + return nil, ctx.warnings, err } - // Add Style - style, err := page.BuildStyle(ctx, option) + // Scripts + namespace := Namespace(page.Name, ctx.sequence) + scripts, err := page.BuildScripts(ctx, option, "__page", namespace) if err != nil { - warnings = append(warnings, err.Error()) + return nil, ctx.warnings, err } - doc.Selection.Find("head").AppendHtml(fmt.Sprintf("\n"+``+"\n%s\n", strings.Join(ctx.styles, "\n"), style)) - // Add Script - code, scripts, err := page.BuildScript(ctx, option, option.Namespace) + // Styles + styles, err := page.BuildStyles(ctx, option, "__page", namespace) if err != nil { - warnings = append(warnings, err.Error()) - } - if scripts != nil { - for _, script := range scripts { - doc.Selection.Find("body").AppendHtml("\n" + `` + "\n") - } + return nil, ctx.warnings, err } - componentScripts := "" - if len(ctx.scripts) > 0 { - componentScripts = fmt.Sprintf("\n"+``+"\n", strings.Join(ctx.scripts, "\n")) - } - doc.Selection.Find("body").AppendHtml(fmt.Sprintf("\n%s\n%s\n", componentScripts, code)) - return doc, warnings, nil -} -// BuildForImport build the page for import -func (page *Page) BuildForImport(ctx *BuildContext, option *BuildOption, slots map[string]interface{}, attrs map[string]string) (string, string, string, []string, error) { + // Append the scripts and styles + ctx.scripts = append(ctx.scripts, scripts...) + ctx.styles = append(ctx.styles, styles...) - defer func() { - if option.ComponentName != "" { - ctx.components[option.ComponentName] = true - } - }() + return doc, ctx.warnings, err +} - warnings := []string{} - html, err := page.BuildHTML(option) - if err != nil { - warnings = append(warnings, err.Error()) +// BuildAsComponent build the page as component +func (page *Page) BuildAsComponent(sel *goquery.Selection, ctx *BuildContext, option *BuildOption) (string, error) { + if page.parent == nil { + return "", fmt.Errorf("The parent page is not set") } - data := map[string]interface{}{} - if slots != nil { - slotvars := map[string]interface{}{} - for k, v := range slots { - slotvars[k] = v - } - data["$slot"] = slotvars // Will be deprecated use $slots instead - data["$slots"] = slotvars + name, exists := sel.Attr("is") + if !exists { + return "", fmt.Errorf("The component tag must have an is attribute") } - if attrs != nil { - data["$prop"] = attrs // Will be deprecated use $props instead - data["$props"] = attrs - page.Attrs = attrs + namespace := Namespace(name, ctx.sequence) + component := ComponentName(name) + attrs := []html.Attribute{ + {Key: "s:ns", Val: namespace}, + {Key: "s:cn", Val: component}, + {Key: "s:ready", Val: component + "()"}, } - // Add Style & Script & Warning - doc, err := NewDocumentStringWithWrapper(html) + ctx.sequence++ + var opt = *option + opt.IgnoreDocument = true + html, err := page.BuildHTML(&opt) if err != nil { - warnings = append(warnings, err.Error()) + return "", err } - // Append the nested html - err = page.parse(ctx, doc, option, warnings) + doc, err := NewDocumentStringWithWrapper(html) if err != nil { - warnings = append(warnings, err.Error()) + return "", err } - // Add Style - style, err := page.BuildStyle(ctx, option) - if err != nil { - warnings = append(warnings, err.Error()) + body := doc.Selection.Find("body") + if body.Length() > 1 { + body.SetHtml("
" + html + "
") } - code, _, err := page.BuildScript(ctx, option, option.Namespace) + // Scripts + scripts, err := page.BuildScripts(ctx, &opt, component, namespace) if err != nil { - warnings = append(warnings, err.Error()) + return "", err } - body := doc.Selection.Find("body") - if body.Length() > 1 { - body.SetHtml("
" + html + "
") - } + // Append the scripts + ctx.scripts = append(ctx.scripts, scripts...) + + // Pass the component props + first := body.Children().First() - body.Children().First().SetAttr("s:cn", option.ComponentName) - body.Children().First().SetAttr("s:ns", option.Namespace) - body.Children().First().SetAttr("s:ready", option.Namespace+"()") + page.copyProps(ctx, sel, first, attrs...) + page.buildComponents(doc, ctx, &opt) html, err = body.Html() if err != nil { - return "", "", "", warnings, err + return "", err } - // Replace the slots - html, _ = Data(data).ReplaceUse(slotRe, html) - return html, style, code, warnings, nil + // Update the component + data := Data{"$props": page.Attrs} + html, _ = data.ReplaceUse(slotRe, html) + sel.ReplaceWithHtml(html) + return html, nil } -func (page *Page) parse(ctx *BuildContext, doc *goquery.Document, option *BuildOption, warnings []string) error { +func (page *Page) copyProps(ctx *BuildContext, from *goquery.Selection, to *goquery.Selection, extra ...html.Attribute) error { + attrs := from.Get(0).Attr + prefix := "s:prop" + if page.Attrs == nil { + page.Attrs = map[string]string{} + } + for _, attr := range attrs { + if strings.HasPrefix(attr.Key, "s:") || attr.Key == "is" || attr.Key == "parsed" { + continue + } + + if strings.HasPrefix(attr.Key, "...[{") { + data := Data{"$props": page.parent.Attrs} + + val, err := data.Exec(attr.Key[3:]) + if err != nil { + ctx.warnings = append(ctx.warnings, err.Error()) + continue + } + + switch value := val.(type) { + case map[string]string: + for key, value := range value { + page.Attrs[key] = value + key = fmt.Sprintf("%s:%s", prefix, key) + to.SetAttr(key, value) + + } + } + continue + } + + val := attr.Val + if strings.HasPrefix(attr.Key, "...") { + val = attr.Key[3:] + } + page.Attrs[attr.Key] = val + key := fmt.Sprintf("%s:%s", prefix, attr.Key) + to.SetAttr(key, val) + } + + if len(extra) > 0 { + for _, attr := range extra { + to.SetAttr(attr.Key, attr.Val) + } + } + + return nil +} +func (page *Page) buildComponents(doc *goquery.Document, ctx *BuildContext, option *BuildOption) error { sui := SUIs[page.SuiID] if sui == nil { return fmt.Errorf("SUI %s not found", page.SuiID) } + public := sui.GetPublic() tmpl, err := sui.GetTemplate(page.TemplateID) if err != nil { return err } - public := sui.GetPublic() - - // Find the import pages - pages := doc.Find("*").FilterFunction(func(i int, sel *goquery.Selection) bool { - + doc.Find("*").Each(func(i int, sel *goquery.Selection) { // Get the translation - if translations := getNodeTranslation(sel, i, option.Namespace); len(translations) > 0 { - page.Translations = append(page.Translations, translations...) - } name, has := sel.Attr("is") - if has { - // Check if Just-In-Time Component ( "is" has variable ) - if ctx.isJitComponent(name) { - sel.SetAttr("s:jit", "true") - sel.SetAttr("s:root", public.Root) - ctx.addJitComponent(name) - return false - } - return true - } - - tagName := sel.Get(0).Data - if tagName == "page" { - return true - } - - if tagName == "slot" { - return false + if !has { + return } - return has - }) + sel.SetAttr("parsed", "true") - for _, node := range pages.Nodes { - sel := goquery.NewDocumentFromNode(node) - name, has := sel.Attr("is") - if !has { - msg := fmt.Sprintf("Page %s/%s/%s: page tag must have an is attribute", page.SuiID, page.TemplateID, page.Route) - sel.ReplaceWith(fmt.Sprintf("", msg)) - log.Warn(msg) - continue + // Check if Just-In-Time Component ( "is" has variable ) + if ctx.isJitComponent(name) { + sel.SetAttr("s:jit", "true") + sel.SetAttr("s:root", public.Root) + ctx.addJitComponent(name) + return } - sel.SetAttr("parsed", "true") ipage, err := tmpl.Page(name) if err != nil { sel.ReplaceWith(fmt.Sprintf("", err.Error())) log.Warn("Page %s/%s/%s: %s", page.SuiID, page.TemplateID, page.Route, err.Error()) - continue + return } err = ipage.Load() if err != nil { sel.ReplaceWith(fmt.Sprintf("", err.Error())) log.Warn("Page %s/%s/%s: %s", page.SuiID, page.TemplateID, page.Route, err.Error()) - continue - } - - // Set the parent - slots := map[string]interface{}{} - for _, slot := range sel.Find("slot").Nodes { - slotSel := goquery.NewDocumentFromNode(slot) - slotName, has := slotSel.Attr("is") - if !has { - continue - } - slotHTML, err := slotSel.Html() - if err != nil { - continue - } - slots[slotName] = strings.TrimSpace(slotHTML) - } - - // Set Attrs - attrs := map[string]string{} - if sel.Length() > 0 { - for _, attr := range sel.Nodes[0].Attr { - if attr.Key == "is" || attr.Key == "parsed" { - continue - } - val := attr.Val - if page.Attrs != nil { - parentProps := Data{ - "$prop": page.Attrs, // Will be deprecated use $props instead - "$props": page.Attrs, - } - val, _ = parentProps.ReplaceUse(slotRe, val) - } - attrs[attr.Key] = val - } - } - - p := ipage.Get() - namespace := Namespace(name, ctx.sequence+1) - componentName := ComponentName(name) - html, _, _, warns, err := p.BuildForImport(ctx, &BuildOption{ - SSR: option.SSR, - AssetRoot: option.AssetRoot, - IgnoreAssetRoot: option.IgnoreAssetRoot, - KeepPageTag: option.KeepPageTag, - IgnoreDocument: true, - Namespace: namespace, - ComponentName: componentName, - ScriptMinify: true, - StyleMinify: true, - }, slots, attrs) - - // append translations - page.Translations = append(page.Translations, p.Translations...) - - if err != nil { - sel.ReplaceWith(fmt.Sprintf("", err.Error())) - log.Warn("Page %s/%s/%s: %s", page.SuiID, page.TemplateID, page.Route, err.Error()) - continue + return } - if warns != nil { - warnings = append(warnings, warns...) - } - - sel.SetAttr("s:ns", namespace) - sel.SetAttr("s:ready", namespace+"()") - if option.KeepPageTag { - sel.SetHtml(fmt.Sprintf("\n%s\n", addTabToEachLine(html))) - - // Set Slot HTML - slotsAttr, err := jsoniter.MarshalToString(slots) - if err != nil { - warns = append(warns, err.Error()) - continue - } - - sel.SetAttr("s:slots", slotsAttr) + component := ipage.Get() + component.parent = page + component.BuildAsComponent(sel, ctx, option) + return + }) - // Set Attrs - for k, v := range attrs { - sel.SetAttr(k, v) - } - continue - } - sel.ReplaceWithHtml(fmt.Sprintf("\n%s\n", addTabToEachLine(html))) - ctx.sequence++ - } - return nil + return err } -// BuildHTML build the html -func (page *Page) BuildHTML(option *BuildOption) (string, error) { - - html := string(page.Codes.HTML.Code) - - if option.WithWrapper { - html = fmt.Sprintf("%s", html) - } - - if !option.IgnoreDocument { - html = string(page.Document) - if page.Codes.HTML.Code != "" { - html = strings.Replace(html, "{{ __page }}", page.Codes.HTML.Code, 1) - } - } - - if !option.IgnoreAssetRoot { - html = strings.ReplaceAll(html, "@assets", option.AssetRoot) - } - - res, err := page.CompileHTML([]byte(html), false) - if err != nil { - return "", err +// BuildStyles build the styles for the page +func (page *Page) BuildStyles(ctx *BuildContext, option *BuildOption, component string, namespace string) ([]StyleNode, error) { + styles := []StyleNode{} + if page.Codes.CSS.Code == "" { + return styles, nil } - return string(res), nil -} - -// BuildStyle build the style -func (page *Page) BuildStyle(ctx *BuildContext, option *BuildOption) (string, error) { - if page.Codes.CSS.Code == "" { - return "", nil + if _, has := ctx.styleUnique[component]; has { + return styles, nil } + ctx.styleUnique[component] = true code := page.Codes.CSS.Code - // Replace the assets if !option.IgnoreAssetRoot { code = AssetsRe.ReplaceAllStringFunc(code, func(match string) string { @@ -350,106 +253,136 @@ func (page *Page) BuildStyle(ctx *BuildContext, option *BuildOption) (string, er }) res, err := page.CompileCSS([]byte(code), option.StyleMinify) if err != nil { - return "", err + return styles, err } - - ctx.styles = append(ctx.styles, string(res)) - return fmt.Sprintf("\n", res), nil + styles = append(styles, StyleNode{ + Namespace: namespace, + Component: component, + Source: string(res), + Parent: "head", + Attrs: []html.Attribute{ + {Key: "rel", Val: "stylesheet"}, + {Key: "type", Val: "text/css"}, + }, + }) + return styles, nil } res, err := page.CompileCSS([]byte(code), option.StyleMinify) if err != nil { - return "", err - } + return styles, err + } + styles = append(styles, StyleNode{ + Namespace: namespace, + Component: component, + Parent: "head", + Source: string(res), + Attrs: []html.Attribute{ + {Key: "rel", Val: "stylesheet"}, + {Key: "type", Val: "text/css"}, + }, + }) - return fmt.Sprintf("\n", res), nil + return styles, nil } -// BuildScript build the script -func (page *Page) BuildScript(ctx *BuildContext, option *BuildOption, namespace string) (string, []string, error) { +// BuildScripts build the scripts for the page +func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component string, namespace string) ([]ScriptNode, error) { + scripts := []ScriptNode{} if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" { - return "", nil, nil + return scripts, nil } - - instanceCode := fmt.Sprintf("function %s(){ %s(...arguments);}", option.Namespace, option.ComponentName) - - // if the script is a component and not the first import - if option.ComponentName != "" && ctx.components[option.ComponentName] { - ctx.scripts = append(ctx.scripts, instanceCode) - return fmt.Sprintf("\n", option.ComponentName, instanceCode), []string{}, nil + if _, has := ctx.scriptUnique[component]; has { + return scripts, nil } - // TypeScript + ctx.scriptUnique[component] = true + var err error = nil + var imports []string = nil + var source []byte = nil if page.Codes.TS.Code != "" { - code, scripts, err := page.CompileTS([]byte(page.Codes.TS.Code), option.ScriptMinify) + source, imports, err = page.CompileTS([]byte(page.Codes.TS.Code), option.ScriptMinify) if err != nil { - return "", nil, err + return nil, err } - // Get the translation - if translations := getScriptTranslation(string(code), namespace); len(translations) > 0 { - page.Translations = append(page.Translations, translations...) + } else if page.Codes.JS.Code != "" { + source, imports, err = page.CompileJS([]byte(page.Codes.JS.Code), option.ScriptMinify) + if err != nil { + return nil, err } - // Replace the assets - if !option.IgnoreAssetRoot { - code = AssetsRe.ReplaceAllFunc(code, func(match []byte) []byte { - return []byte(strings.ReplaceAll(string(match), "@assets", option.AssetRoot)) - }) + } - if scripts != nil { - for i, script := range scripts { - scripts[i] = filepath.Join(option.AssetRoot, script) - } - } + // Add the script + if imports != nil { + for _, src := range imports { + scripts = append(scripts, ScriptNode{ + Namespace: namespace, + Component: component, + Parent: "head", + Attrs: []html.Attribute{ + {Key: "src", Val: fmt.Sprintf("%s/%s", option.AssetRoot, src)}, + {Key: "type", Val: "text/javascript"}, + }}, + ) } + } - if option.Namespace == "" { - if strings.TrimSpace(string(code)) == "" { - return "", scripts, nil - } - return fmt.Sprintf("\n", code), scripts, nil + // Replace the assets + if !option.IgnoreAssetRoot && source != nil { + source = AssetsRe.ReplaceAllFunc(source, func(match []byte) []byte { + return []byte(strings.ReplaceAll(string(match), "@assets", option.AssetRoot)) + }) + + code := string(source) + parent := "body" + if component != "__page" { + parent = "head" + code = fmt.Sprintf("function %s(){\n%s\n}\n", component, addTabToEachLine(code)) } - componentCode := fmt.Sprintf("function %s(){\n%s\n}\n%s\n", option.ComponentName, addTabToEachLine(string(code)), instanceCode) - ctx.scripts = append(ctx.scripts, componentCode) - return fmt.Sprintf("\n", option.ComponentName, componentCode), scripts, nil + scripts = append(scripts, ScriptNode{ + Namespace: namespace, + Component: component, + Source: code, + Parent: parent, + Attrs: []html.Attribute{ + {Key: "type", Val: "text/javascript"}, + }, + }) } - // JavaScript - code, scripts, err := page.CompileJS([]byte(page.Codes.JS.Code), option.ScriptMinify) - if err != nil { - return "", nil, err + return scripts, nil +} + +// BuildHTML build the html +func (page *Page) BuildHTML(option *BuildOption) (string, error) { + + html := string(page.Codes.HTML.Code) + + if option.WithWrapper { + html = fmt.Sprintf("%s", html) } - // Get the translation - if translations := getScriptTranslation(string(code), namespace); len(translations) > 0 { - page.Translations = append(page.Translations, translations...) + if !option.IgnoreDocument { + html = string(page.Document) + if page.Codes.HTML.Code != "" { + html = strings.Replace(html, "{{ __page }}", page.Codes.HTML.Code, 1) + } } - // Replace the assets if !option.IgnoreAssetRoot { - code = AssetsRe.ReplaceAllFunc(code, func(match []byte) []byte { - return []byte(strings.ReplaceAll(string(match), "@assets", option.AssetRoot)) - }) - if scripts != nil { - for i, script := range scripts { - scripts[i] = filepath.Join(option.AssetRoot, script) - } - } + html = strings.ReplaceAll(html, "@assets", option.AssetRoot) } - if option.Namespace == "" { - if strings.TrimSpace(string(code)) == "" { - return "", scripts, nil - } - return fmt.Sprintf("\n", code), scripts, nil + res, err := page.CompileHTML([]byte(html), false) + if err != nil { + return "", err } - componentCode := fmt.Sprintf("function %s(){\n%s\n}\n%s\n", option.ComponentName, addTabToEachLine(string(code)), instanceCode) - ctx.scripts = append(ctx.scripts, componentCode) - return fmt.Sprintf("\n", option.ComponentName, componentCode), scripts, nil + return string(res), nil } func addTabToEachLine(input string, prefix ...string) string { diff --git a/sui/core/compile.go b/sui/core/compile.go index 460e8097c..40c201178 100644 --- a/sui/core/compile.go +++ b/sui/core/compile.go @@ -2,6 +2,7 @@ package core import ( "regexp" + "strings" "github.com/evanw/esbuild/pkg/api" "github.com/yaoapp/gou/runtime/transform" @@ -29,12 +30,38 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, error } } + body := doc.Find("body") + head := doc.Find("head") + + // Scripts + if ctx != nil && ctx.scripts != nil { + for _, script := range ctx.scripts { + if script.Parent == "head" { + head.AppendHtml(script.HTML() + "\n") + continue + } + body.AppendHtml(script.HTML() + "\n") + } + } + + // Styles + if ctx != nil && ctx.styles != nil { + for _, style := range ctx.styles { + if style.Parent == "head" { + head.AppendHtml(style.HTML() + "\n") + continue + } + body.AppendHtml(style.HTML() + "\n") + } + + } + // Page Config page.Config = page.GetConfig() // Config Data if page.Config != nil { - doc.Find("body").AppendHtml("\n\n" + `\n\n", ) @@ -42,7 +69,7 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, error // Page Data if page.Codes.DATA.Code != "" { - doc.Find("body").AppendHtml("\n\n" + `\n\n", ) @@ -50,7 +77,7 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, error // Page Global Data if page.GlobalData != nil && len(page.GlobalData) > 0 { - doc.Find("body").AppendHtml("\n\n" + `\n\n", ) @@ -66,6 +93,44 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, error return html, nil } +// HTML return the html of the script +func (script ScriptNode) HTML() string { + + attrs := []string{ + "s:ns=\"" + script.Namespace + "\"", + "s:cn=\"" + script.Component + "\"", + } + if script.Attrs != nil { + for _, attr := range script.Attrs { + attrs = append(attrs, attr.Key+"=\""+attr.Val+"\"") + } + } + // Inline Script + if script.Source == "" { + return "" + } + return "" +} + +// HTML return the html of the style node +func (style StyleNode) HTML() string { + attrs := []string{ + "s:ns=\"" + style.Namespace + "\"", + "s:cn=\"" + style.Component + "\"", + } + if style.Attrs != nil { + for _, attr := range style.Attrs { + attrs = append(attrs, attr.Key+"=\""+attr.Val+"\"") + } + } + // Inline Style + if style.Source == "" { + return "" + } + return "" + +} + // CompileAsComponent compile the page as component func (page *Page) CompileAsComponent(ctx *BuildContext, option *BuildOption) (string, error) { diff --git a/sui/core/context.go b/sui/core/context.go index eaa74c483..8c4b8414f 100644 --- a/sui/core/context.go +++ b/sui/core/context.go @@ -5,10 +5,13 @@ func NewBuildContext(global *GlobalBuildContext) *BuildContext { return &BuildContext{ components: map[string]bool{}, sequence: 1, - scripts: []string{}, - styles: []string{}, + scripts: []ScriptNode{}, + scriptUnique: map[string]bool{}, + styles: []StyleNode{}, + styleUnique: map[string]bool{}, jitComponents: map[string]bool{}, global: global, + warnings: []string{}, } } diff --git a/sui/core/types.go b/sui/core/types.go index 1eeabeb3e..ebeecae5b 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -5,6 +5,7 @@ import ( "regexp" "github.com/PuerkitoBio/goquery" + "golang.org/x/net/html" ) // DSL the struct for the DSL @@ -39,7 +40,9 @@ type Page struct { Document []byte `json:"-"` GlobalData []byte `json:"-"` Attrs map[string]string `json:"-"` + Attributes []html.Attribute `json:"-"` Translations []Translation `json:"-"` // will be deprecated + parent *Page `json:"-"` } // BuildContext is the struct for the build context @@ -48,10 +51,31 @@ type BuildContext struct { jitComponents map[string]bool sequence int doc *goquery.Document - scripts []string - styles []string + scripts []ScriptNode + scriptUnique map[string]bool + styles []StyleNode + styleUnique map[string]bool global *GlobalBuildContext translations []Translation + warnings []string +} + +// ScriptNode is the struct for the script node +type ScriptNode struct { + Source string `json:"source"` + Attrs []html.Attribute `json:"attrs"` + Parent string `json:"parent"` + Namespace string `json:"namespace"` + Component string `json:"component"` +} + +// StyleNode is the struct for the style node +type StyleNode struct { + Source string `json:"source"` + Attrs []html.Attribute `json:"attrs"` + Parent string `json:"parent"` + Namespace string `json:"namespace"` + Component string `json:"component"` } // GlobalBuildContext is the struct for the global build context diff --git a/sui/storages/local/build_test.go b/sui/storages/local/build_test.go index b623f856c..df89023dd 100644 --- a/sui/storages/local/build_test.go +++ b/sui/storages/local/build_test.go @@ -44,7 +44,7 @@ func TestTemplateBuild(t *testing.T) { } assert.Contains(t, string(content), "body") - assert.Contains(t, string(content), ``) + assert.Contains(t, string(content), `src="/unit-test/assets/js/import.js"`) assert.Contains(t, string(content), ``) + assert.Contains(t, string(content), `src="/unit-test/assets/js/import.js"`) assert.Contains(t, string(content), `