refactor: Update BuildContext components to use boolean values instead of strings

This commit is contained in:
Max 2024-07-12 08:39:57 +08:00
parent a5e1b569be
commit 5b19ef3d19
5 changed files with 241 additions and 169 deletions

View file

@ -12,6 +12,9 @@ import (
var slotRe = regexp.MustCompile(`\[\{([^\}]+)\}\]`) var slotRe = regexp.MustCompile(`\[\{([^\}]+)\}\]`)
var cssRe = regexp.MustCompile(`([\.a-z0-9A-Z-:# ]+)\{`) var cssRe = regexp.MustCompile(`([\.a-z0-9A-Z-:# ]+)\{`)
var transStmtReSingle = regexp.MustCompile(`'::([^:']+)'`)
var transStmtReDouble = regexp.MustCompile(`"::([^:"]+)"`)
var transFuncRe = regexp.MustCompile(`__m\s*\(\s*["'](.*?)["']\s*\)`)
// Build build the page // Build build the page
func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Document, []string, error) { func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Document, []string, error) {
@ -73,6 +76,27 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume
ctx.scripts = append(ctx.scripts, scripts...) ctx.scripts = append(ctx.scripts, scripts...)
ctx.styles = append(ctx.styles, styles...) ctx.styles = append(ctx.styles, styles...)
// Add the translation marks
sequence := 0
err = page.TranslateMarks(ctx, doc, &sequence)
if err != nil {
return nil, warnings, err
}
// Translate the scripts
if (scripts != nil) && len(scripts) > 0 {
for _, script := range scripts {
if script.Source == "" {
continue
}
trans, _, err := page.translateScript(script.Source, &sequence)
if err != nil {
return nil, ctx.warnings, err
}
ctx.translations = append(ctx.translations, trans...)
}
}
return doc, ctx.warnings, err return doc, ctx.warnings, err
} }
@ -83,6 +107,10 @@ func (page *Page) BuildAsComponent(sel *goquery.Selection, ctx *BuildContext, op
return "", fmt.Errorf("The parent page is not set") return "", fmt.Errorf("The parent page is not set")
} }
if ctx == nil {
ctx = NewBuildContext(nil)
}
// Push the current page onto the stack and increment the visit counter // Push the current page onto the stack and increment the visit counter
ctx.stack = append(ctx.stack, page.Route) ctx.stack = append(ctx.stack, page.Route)
ctx.visited[page.Route]++ ctx.visited[page.Route]++
@ -151,11 +179,34 @@ func (page *Page) BuildAsComponent(sel *goquery.Selection, ctx *BuildContext, op
page.buildComponents(doc, ctx, &opt) page.buildComponents(doc, ctx, &opt)
data := Data{"$props": page.Attrs} data := Data{"$props": page.Attrs}
data.ReplaceSelectionUse(slotRe, first) data.ReplaceSelectionUse(slotRe, first)
// Add the translation marks
sequence := 0
err = page.TranslateMarks(ctx, doc, &sequence)
if err != nil {
return "", err
}
// Translate the scripts
if (scripts != nil) && len(scripts) > 0 {
for _, script := range scripts {
if script.Source == "" {
continue
}
trans, _, err := page.translateScript(script.Source, &sequence)
if err != nil {
return "", err
}
ctx.translations = append(ctx.translations, trans...)
}
}
html, err = body.Html() html, err = body.Html()
if err != nil { if err != nil {
return "", err return "", err
} }
sel.ReplaceWithHtml(html) sel.ReplaceWithHtml(html)
ctx.components[page.Route] = true
return html, nil return html, nil
} }
@ -496,3 +547,162 @@ func addTabToEachLine(input string, prefix ...string) string {
return strings.Join(lines, "\n") return strings.Join(lines, "\n")
} }
// TranslateMarks add the translation marks to the document
func (page *Page) TranslateMarks(ctx *BuildContext, doc *goquery.Document, sequence *int) error {
if doc.Length() == 0 {
return nil
}
if ctx == nil {
ctx = NewBuildContext(nil)
}
if ctx.translations == nil {
ctx.translations = []Translation{}
}
root := doc.First()
translations, err := page.translateNode(root.Nodes[0], sequence)
if err != nil {
return err
}
if translations != nil {
ctx.translations = append(ctx.translations, translations...)
}
return nil
}
func (page *Page) translateNode(node *html.Node, sequence *int) ([]Translation, error) {
translations := []Translation{}
*sequence = *sequence + 1
switch node.Type {
case html.DocumentNode:
for child := node.FirstChild; child != nil; child = child.NextSibling {
trans, err := page.translateNode(child, sequence)
if err != nil {
return nil, err
}
translations = append(translations, trans...)
}
break
case html.ElementNode:
// Script
if node.Data == "script" {
code := goquery.NewDocumentFromNode(node).Text()
trans, _, err := page.translateScript(code, sequence)
if err != nil {
return nil, err
}
translations = append(translations, trans...)
break
}
sel := goquery.NewDocumentFromNode(node)
for _, attr := range node.Attr {
trans, keys, err := page.translateText(attr.Val, sequence, "attr")
if err != nil {
return nil, err
}
if len(keys) > 0 {
raw := strings.Join(keys, ",")
sel.SetAttr("s:trans-attr-"+attr.Key, raw)
translations = append(translations, trans...)
}
}
// Node Attributes
for child := node.FirstChild; child != nil; child = child.NextSibling {
trans, err := page.translateNode(child, sequence)
if err != nil {
return nil, err
}
translations = append(translations, trans...)
}
break
case html.TextNode:
parentSel := goquery.NewDocumentFromNode(node.Parent)
if _, has := parentSel.Attr("s:trans"); has {
key := TranslationKey(page.Route, *sequence)
message := strings.TrimSpace(node.Data)
if message != "" {
translations = append(translations, Translation{
Key: key,
Message: message,
Type: "text",
})
parentSel.SetAttr("s:trans-node", key)
*sequence = *sequence + 1
}
parentSel.RemoveAttr("s:trans")
}
trans, keys, err := page.translateText(node.Data, sequence, "text")
if err != nil {
return nil, err
}
if len(keys) > 0 {
raw := strings.Join(keys, ",")
parentSel.SetAttr("s:trans-text", raw)
parentSel.RemoveAttr("s:trans")
translations = append(translations, trans...)
}
break
}
return translations, nil
}
func (page *Page) translateText(text string, sequence *int, transType string) ([]Translation, []string, error) {
translations := []Translation{}
matches := stmtRe.FindAllStringSubmatch(text, -1)
keys := []string{}
for _, match := range matches {
text := strings.TrimSpace(match[1])
transMatches := transStmtReSingle.FindAllStringSubmatch(text, -1)
if len(transMatches) == 0 {
transMatches = transStmtReDouble.FindAllStringSubmatch(text, -1)
}
for _, transMatch := range transMatches {
message := strings.TrimSpace(transMatch[1])
key := TranslationKey(page.Route, *sequence)
keys = append(keys, key)
translations = append(translations, Translation{
Key: key,
Message: message,
Type: transType,
})
*sequence = *sequence + 1
}
}
return translations, keys, nil
}
func (page *Page) translateScript(code string, sequence *int) ([]Translation, []string, error) {
translations := []Translation{}
keys := []string{}
if code == "" {
return translations, keys, nil
}
matches := transFuncRe.FindAllStringSubmatch(code, -1)
for _, match := range matches {
key := TranslationKey(page.Route, *sequence)
translations = append(translations, Translation{
Key: key,
Message: match[1],
Type: "script",
})
*sequence = *sequence + 1
}
return translations, keys, nil
}

View file

@ -5,20 +5,15 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/PuerkitoBio/goquery"
"github.com/evanw/esbuild/pkg/api" "github.com/evanw/esbuild/pkg/api"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/yaoapp/gou/runtime/transform" "github.com/yaoapp/gou/runtime/transform"
"github.com/yaoapp/kun/log" "github.com/yaoapp/kun/log"
"golang.org/x/net/html"
) )
var quoteRe = "'\"`" var quoteRe = "'\"`"
var importRe = regexp.MustCompile(`import\s*\t*\n*[^;]*;`) // import { foo, bar } from 'hello'; ... var importRe = regexp.MustCompile(`import\s*\t*\n*[^;]*;`) // import { foo, bar } from 'hello'; ...
var importAssetsRe = regexp.MustCompile(`import\s*\t*\n*\s*['"]@assets\/([^'"]+)['"];`) // import '@assets/foo.js'; or import "@assets/foo.js"; var importAssetsRe = regexp.MustCompile(`import\s*\t*\n*\s*['"]@assets\/([^'"]+)['"];`) // import '@assets/foo.js'; or import "@assets/foo.js";
var transStmtReSingle = regexp.MustCompile(`'::([^:']+)'`)
var transStmtReDouble = regexp.MustCompile(`"::([^:"]+)"`)
var transFuncRe = regexp.MustCompile(`__m\s*\(\s*["'](.*?)["']\s*\)`)
// AssetsRe is the regexp for assets // AssetsRe is the regexp for assets
var AssetsRe = regexp.MustCompile(`[` + quoteRe + `]@assets\/([^` + quoteRe + `]+)[` + quoteRe + `]`) // '@assets/foo.js' or "@assets/foo.js" or `@assets/foo` var AssetsRe = regexp.MustCompile(`[` + quoteRe + `]@assets\/([^` + quoteRe + `]+)[` + quoteRe + `]`) // '@assets/foo.js' or "@assets/foo.js" or `@assets/foo`
@ -90,12 +85,15 @@ func (page *Page) Compile(ctx *BuildContext, option *BuildOption) (string, []str
) )
} }
// Add the translation marks // Page Components
sequence := 0 components := []string{}
err = page.TranslateMarks(ctx, option, doc, &sequence) if ctx != nil && ctx.components != nil && len(ctx.components) > 0 {
if err != nil { for route := range ctx.components {
return "", warnings, err components = append(components, route)
} }
}
rawComponents, _ := jsoniter.MarshalToString(components)
body.AppendHtml("\n\n" + `<script name="imports" type="json">` + "\n" + rawComponents + "\n</script>\n\n")
page.ReplaceDocument(doc) page.ReplaceDocument(doc)
html, err := doc.Html() html, err := doc.Html()
@ -140,6 +138,15 @@ func (page *Page) CompileAsComponent(ctx *BuildContext, option *BuildOption) (st
return "", warnings, err return "", warnings, err
} }
// Page Components
components := []string{}
if ctx != nil && ctx.components != nil && len(ctx.components) > 0 {
for route := range ctx.components {
components = append(components, route)
}
}
rawComponents, _ := jsoniter.MarshalToString(components)
if body.Children().Length() == 0 { if body.Children().Length() == 0 {
return "", warnings, fmt.Errorf("page %s as component should have one root element", page.Route) return "", warnings, fmt.Errorf("page %s as component should have one root element", page.Route)
} }
@ -151,13 +158,7 @@ func (page *Page) CompileAsComponent(ctx *BuildContext, option *BuildOption) (st
body.Children().First().AppendHtml(fmt.Sprintf(`<script name="scripts" type="json">%s</script>`+"\n", rawScripts)) body.Children().First().AppendHtml(fmt.Sprintf(`<script name="scripts" type="json">%s</script>`+"\n", rawScripts))
body.Children().First().AppendHtml(fmt.Sprintf(`<script name="styles" type="json">%s</script>`+"\n", rawStyles)) body.Children().First().AppendHtml(fmt.Sprintf(`<script name="styles" type="json">%s</script>`+"\n", rawStyles))
body.Children().First().AppendHtml(fmt.Sprintf(`<script name="option" type="json">%s</script>`+"\n", rawOption)) body.Children().First().AppendHtml(fmt.Sprintf(`<script name="option" type="json">%s</script>`+"\n", rawOption))
body.Children().First().AppendHtml(fmt.Sprintf(`<script name="imports" type="json">%s</script>`+"\n", rawComponents))
// Add the translation marks
sequence := 0
err = page.TranslateMarks(ctx, option, doc, &sequence)
if err != nil {
return "", warnings, err
}
html, err := body.Html() html, err := body.Html()
return html, warnings, err return html, warnings, err
@ -282,150 +283,3 @@ func (style StyleNode) HTML() string {
return "<style " + strings.Join(attrs, " ") + ">\n" + style.Source + "\n</style>" return "<style " + strings.Join(attrs, " ") + ">\n" + style.Source + "\n</style>"
} }
// TranslateMarks add the translation marks to the document
func (page *Page) TranslateMarks(ctx *BuildContext, option *BuildOption, doc *goquery.Document, sequence *int) error {
if doc.Length() == 0 {
return nil
}
if ctx == nil {
ctx = NewBuildContext(nil)
}
if ctx.translations == nil {
ctx.translations = []Translation{}
}
root := doc.First()
translations, err := page.translateNode(root.Nodes[0], sequence)
if err != nil {
return err
}
if translations != nil {
ctx.translations = append(ctx.translations, translations...)
}
return nil
}
func (page *Page) translateNode(node *html.Node, sequence *int) ([]Translation, error) {
translations := []Translation{}
*sequence = *sequence + 1
switch node.Type {
case html.DocumentNode:
for child := node.FirstChild; child != nil; child = child.NextSibling {
trans, err := page.translateNode(child, sequence)
if err != nil {
return nil, err
}
translations = append(translations, trans...)
}
break
case html.ElementNode:
// Script
if node.Data == "script" {
code := goquery.NewDocumentFromNode(node).Text()
if code != "" {
translations := []Translation{}
matches := transFuncRe.FindAllStringSubmatch(code, -1)
for _, match := range matches {
key := Namespace(page.Route, *sequence)
translations = append(translations, Translation{
Key: key,
Message: match[1],
Type: "script",
})
*sequence = *sequence + 1
}
}
break
}
sel := goquery.NewDocumentFromNode(node)
for _, attr := range node.Attr {
trans, keys, err := page.translateText(attr.Val, sequence, "attr")
if err != nil {
return nil, err
}
if len(keys) > 0 {
raw := strings.Join(keys, ",")
sel.SetAttr("s:trans-attr-"+attr.Key, raw)
translations = append(translations, trans...)
}
}
// Node Attributes
for child := node.FirstChild; child != nil; child = child.NextSibling {
trans, err := page.translateNode(child, sequence)
if err != nil {
return nil, err
}
translations = append(translations, trans...)
}
break
case html.TextNode:
parentSel := goquery.NewDocumentFromNode(node.Parent)
if _, has := parentSel.Attr("s:trans"); has {
key := Namespace(page.Route, *sequence)
message := strings.TrimSpace(node.Data)
if message != "" {
translations = append(translations, Translation{
Key: key,
Message: message,
Type: "text",
})
parentSel.SetAttr("s:trans-node", key)
*sequence = *sequence + 1
}
parentSel.RemoveAttr("s:trans")
}
trans, keys, err := page.translateText(node.Data, sequence, "text")
if err != nil {
return nil, err
}
if len(keys) > 0 {
raw := strings.Join(keys, ",")
parentSel.SetAttr("s:trans-text", raw)
parentSel.RemoveAttr("s:trans")
translations = append(translations, trans...)
}
break
}
return translations, nil
}
func (page *Page) translateText(text string, sequence *int, typ string) ([]Translation, []string, error) {
translations := []Translation{}
matches := stmtRe.FindAllStringSubmatch(text, -1)
keys := []string{}
for _, match := range matches {
text := strings.TrimSpace(match[1])
transMatches := transStmtReSingle.FindAllStringSubmatch(text, -1)
if len(transMatches) == 0 {
transMatches = transStmtReDouble.FindAllStringSubmatch(text, -1)
}
for _, transMatch := range transMatches {
message := strings.TrimSpace(transMatch[1])
key := Namespace(page.Route, *sequence)
keys = append(keys, key)
translations = append(translations, Translation{
Key: key,
Message: message,
Type: typ,
})
*sequence = *sequence + 1
}
}
return translations, keys, nil
}

View file

@ -3,7 +3,7 @@ package core
// NewBuildContext create a new build context // NewBuildContext create a new build context
func NewBuildContext(global *GlobalBuildContext) *BuildContext { func NewBuildContext(global *GlobalBuildContext) *BuildContext {
return &BuildContext{ return &BuildContext{
components: map[string]string{}, components: map[string]bool{},
sequence: 1, sequence: 1,
scripts: []ScriptNode{}, scripts: []ScriptNode{},
scriptUnique: map[string]bool{}, scriptUnique: map[string]bool{},

View file

@ -47,7 +47,7 @@ type Page struct {
// BuildContext is the struct for the build context // BuildContext is the struct for the build context
type BuildContext struct { type BuildContext struct {
components map[string]string components map[string]bool
jitComponents map[string]bool jitComponents map[string]bool
sequence int sequence int
doc *goquery.Document doc *goquery.Document

View file

@ -55,13 +55,13 @@ func Namespace(name string, idx int, hash ...bool) string {
name = strings.ReplaceAll(name, "/", "_") name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, "[", "_") name = strings.ReplaceAll(name, "[", "_")
name = strings.ReplaceAll(name, "]", "_") name = strings.ReplaceAll(name, "]", "_")
ns := fmt.Sprintf("__namespace_%s", name) ns := fmt.Sprintf("page_%s_%d", name, idx)
if len(hash) > 0 && hash[0] { if len(hash) > 0 && hash[0] {
h := fnv.New64a() h := fnv.New64a()
h.Write([]byte(ns)) h.Write([]byte(ns))
return fmt.Sprintf("ns_%x", h.Sum64()) return fmt.Sprintf("ns_%x", h.Sum64())
} }
return fmt.Sprintf("__page_%s_%d", name, idx) return ns
} }
// ComponentName convert the name to component name // ComponentName convert the name to component name
@ -69,7 +69,7 @@ func ComponentName(name string, hash ...bool) string {
name = strings.ReplaceAll(name, "/", "_") name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, "[", "_") name = strings.ReplaceAll(name, "[", "_")
name = strings.ReplaceAll(name, "]", "_") name = strings.ReplaceAll(name, "]", "_")
cn := fmt.Sprintf("__component_%s", name) cn := fmt.Sprintf("comp_%s", name)
if len(hash) > 0 && hash[0] { if len(hash) > 0 && hash[0] {
h := fnv.New64a() h := fnv.New64a()
h.Write([]byte(cn)) h.Write([]byte(cn))
@ -77,3 +77,11 @@ func ComponentName(name string, hash ...bool) string {
} }
return cn return cn
} }
// TranslationKey convert the name to translation key
func TranslationKey(name string, sequence int) string {
name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, "[", "_")
name = strings.ReplaceAll(name, "]", "_")
return fmt.Sprintf("trans_%s_%d", name, sequence)
}