refactor: Update parsing logic for JIT components
This commit is contained in:
parent
d188f967d0
commit
ffb858b76f
7 changed files with 251 additions and 60 deletions
|
|
@ -40,7 +40,7 @@ func (page *Page) Build(ctx *BuildContext, option *BuildOption) (*goquery.Docume
|
|||
|
||||
ctx.sequence++
|
||||
|
||||
namespace := Namespace(page.Name, ctx.sequence, option.ScriptMinify)
|
||||
namespace := Namespace(page.Route, ctx.sequence, option.ScriptMinify)
|
||||
page.namespace = namespace
|
||||
|
||||
html, err := page.BuildHTML(option)
|
||||
|
|
@ -367,6 +367,11 @@ func (page *Page) BuildStyles(ctx *BuildContext, option *BuildOption, component
|
|||
// BuildScripts build the scripts for the page
|
||||
func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component string, namespace string) ([]ScriptNode, error) {
|
||||
|
||||
ispage := component == "__page"
|
||||
if ispage {
|
||||
component = ComponentName(page.Route, option.ScriptMinify)
|
||||
}
|
||||
|
||||
scripts := []ScriptNode{}
|
||||
if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" {
|
||||
return scripts, nil
|
||||
|
|
@ -416,7 +421,7 @@ func (page *Page) BuildScripts(ctx *BuildContext, option *BuildOption, component
|
|||
|
||||
code := string(source)
|
||||
parent := "body"
|
||||
if component != "__page" {
|
||||
if !ispage {
|
||||
parent = "head"
|
||||
code = fmt.Sprintf("function %s(){\n%s\n}\n", component, addTabToEachLine(code))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/evanw/esbuild/pkg/api"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou/runtime/transform"
|
||||
"github.com/yaoapp/kun/log"
|
||||
)
|
||||
|
|
@ -93,44 +95,6 @@ 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 "<script " + strings.Join(attrs, " ") + "></script>"
|
||||
}
|
||||
return "<script " + strings.Join(attrs, " ") + ">\n" + script.Source + "\n</script>"
|
||||
}
|
||||
|
||||
// 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 "<link " + strings.Join(attrs, " ") + "></link>"
|
||||
}
|
||||
return "<style " + strings.Join(attrs, " ") + ">\n" + style.Source + "\n</style>"
|
||||
|
||||
}
|
||||
|
||||
// CompileAsComponent compile the page as component
|
||||
func (page *Page) CompileAsComponent(ctx *BuildContext, option *BuildOption) (string, error) {
|
||||
|
||||
|
|
@ -148,8 +112,34 @@ func (page *Page) CompileAsComponent(ctx *BuildContext, option *BuildOption) (st
|
|||
}
|
||||
}
|
||||
|
||||
page.ReplaceDocument(doc)
|
||||
return doc.Find("body").Html()
|
||||
body := doc.Find("body")
|
||||
rawScripts, err := jsoniter.MarshalToString(ctx.scripts)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rawStyles, err := jsoniter.MarshalToString(ctx.styles)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
rawOption, err := jsoniter.MarshalToString(option)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if body.Children().Length() == 0 {
|
||||
return "", fmt.Errorf("page %s as component should have one root element", page.Route)
|
||||
}
|
||||
|
||||
if body.Children().Length() > 1 {
|
||||
return "", fmt.Errorf("page %s as component should have only one root element", page.Route)
|
||||
}
|
||||
|
||||
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="option" type="json">%s</script>`+"\n", rawOption))
|
||||
return body.Html()
|
||||
}
|
||||
|
||||
// CompileJS compile the javascript
|
||||
|
|
@ -212,3 +202,62 @@ func (page *Page) CompileCSS(source []byte, minify bool) ([]byte, error) {
|
|||
func (page *Page) CompileHTML(source []byte, minify bool) ([]byte, error) {
|
||||
return source, 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 "<script " + strings.Join(attrs, " ") + "></script>"
|
||||
}
|
||||
return "<script " + strings.Join(attrs, " ") + ">\n" + script.Source + "\n</script>"
|
||||
}
|
||||
|
||||
// ComponentHTML return the html of the script
|
||||
func (script ScriptNode) ComponentHTML(ns string) string {
|
||||
|
||||
attrs := []string{
|
||||
"s:ns=\"" + ns + "\"",
|
||||
"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 "<script " + strings.Join(attrs, " ") + "></script>"
|
||||
}
|
||||
|
||||
source := fmt.Sprintf(`function %s(){%s};`, script.Component, script.Source)
|
||||
return "<script " + strings.Join(attrs, " ") + ">\n" + source + "\n</script>"
|
||||
}
|
||||
|
||||
// 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 "<link " + strings.Join(attrs, " ") + "></link>"
|
||||
}
|
||||
return "<style " + strings.Join(attrs, " ") + ">\n" + style.Source + "\n</style>"
|
||||
|
||||
}
|
||||
|
|
|
|||
144
sui/core/jit.go
144
sui/core/jit.go
|
|
@ -4,18 +4,23 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou/application"
|
||||
)
|
||||
|
||||
// JitComponent component
|
||||
type JitComponent struct {
|
||||
html string
|
||||
scripts []ScriptNode
|
||||
styles []StyleNode
|
||||
file string
|
||||
route string
|
||||
html string
|
||||
scripts []ScriptNode
|
||||
styles []StyleNode
|
||||
buildOption *BuildOption
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
@ -32,6 +37,9 @@ type componentData struct {
|
|||
// Components loaded JIT components
|
||||
var Components = map[string]*JitComponent{}
|
||||
var chComp = make(chan *componentData, 1)
|
||||
var reScripts = regexp.MustCompile(`<script[^>]*name="scripts"[^>]*>(.*?)</script>`)
|
||||
var reStyles = regexp.MustCompile(`<script[^>]*name="styles"[^>]*>(.*?)</script>`)
|
||||
var reOption = regexp.MustCompile(`<script[^>]*name="option"[^>]*>(.*?)</script>`)
|
||||
|
||||
func init() {
|
||||
go componentWriter()
|
||||
|
|
@ -47,17 +55,18 @@ func (parser *TemplateParser) parseComponent(sel *goquery.Selection) {
|
|||
return
|
||||
}
|
||||
|
||||
html, err := parser.RenderComponent(comp, props, slots)
|
||||
html, _, err := parser.RenderComponent(comp, props, slots)
|
||||
if err != nil {
|
||||
parser.errors = append(parser.errors, err)
|
||||
setError(sel, err)
|
||||
return
|
||||
}
|
||||
|
||||
sel.SetHtml(html)
|
||||
}
|
||||
|
||||
// RenderComponent render the component
|
||||
func (parser *TemplateParser) RenderComponent(comp *JitComponent, props map[string]interface{}, slots *goquery.Selection) (string, error) {
|
||||
func (parser *TemplateParser) RenderComponent(comp *JitComponent, props map[string]interface{}, slots *goquery.Selection) (string, string, error) {
|
||||
html := comp.html
|
||||
randvar := fmt.Sprintf("__%s_$props", time.Now().Format("20060102150405"))
|
||||
html = slotRe.ReplaceAllStringFunc(html, func(exp string) string {
|
||||
|
|
@ -77,7 +86,7 @@ func (parser *TemplateParser) RenderComponent(comp *JitComponent, props map[stri
|
|||
|
||||
sel, err := NewDocumentString(`<body>` + html + `</body>`)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
root := sel.Find("body")
|
||||
|
|
@ -103,15 +112,65 @@ func (parser *TemplateParser) RenderComponent(comp *JitComponent, props map[stri
|
|||
compParser := NewTemplateParser(data, &option)
|
||||
|
||||
// Parse the node
|
||||
ns := Namespace(comp.route, compParser.sequence, comp.buildOption.ScriptMinify)
|
||||
cn := ComponentName(comp.route, comp.buildOption.ScriptMinify)
|
||||
compParser.parseNode(root.Get(0))
|
||||
for sel, nodes := range compParser.replace {
|
||||
sel.ReplaceWithNodes(nodes...)
|
||||
delete(parser.replace, sel)
|
||||
}
|
||||
|
||||
if comp.scripts != nil {
|
||||
for _, script := range comp.scripts {
|
||||
script.Namespace = ns
|
||||
parser.scripts = append(parser.scripts, script)
|
||||
}
|
||||
}
|
||||
|
||||
if comp.styles != nil {
|
||||
for _, style := range comp.styles {
|
||||
style.Namespace = ns
|
||||
parser.styles = append(parser.styles, style)
|
||||
}
|
||||
}
|
||||
|
||||
// Update sequence
|
||||
parser.sequence = compParser.sequence + parser.sequence
|
||||
return root.Html()
|
||||
first := root.Children().First()
|
||||
first.SetAttr("s:ns", ns)
|
||||
first.SetAttr("s:cn", cn)
|
||||
first.SetAttr("s:ready", cn+"()")
|
||||
html, err = root.Html()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return html, ns, nil
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) addScripts(sel *goquery.Selection, scripts []ScriptNode) {
|
||||
if scripts == nil {
|
||||
return
|
||||
}
|
||||
for _, script := range scripts {
|
||||
query := fmt.Sprintf(`script[s\:cn="%s"]`, script.Component)
|
||||
if sel.Find(query).Length() > 0 {
|
||||
continue
|
||||
}
|
||||
sel.AppendHtml(script.ComponentHTML(script.Namespace))
|
||||
}
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) addStyles(sel *goquery.Selection, styles []StyleNode) {
|
||||
if styles == nil {
|
||||
return
|
||||
}
|
||||
for _, style := range styles {
|
||||
query := fmt.Sprintf(`style[s\:cn="%s"]`, style.Component)
|
||||
if sel.Find(query).Length() > 0 {
|
||||
continue
|
||||
}
|
||||
sel.Append(style.HTML())
|
||||
}
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) getComponent(sel *goquery.Selection) (*JitComponent, map[string]interface{}, *goquery.Selection, error) {
|
||||
|
|
@ -123,12 +182,12 @@ func (parser *TemplateParser) getComponent(sel *goquery.Selection) (*JitComponen
|
|||
return nil, nil, slots, err
|
||||
}
|
||||
|
||||
file, err := parser.componentFile(sel, props)
|
||||
file, route, err := parser.componentFile(sel, props)
|
||||
if err != nil {
|
||||
return nil, props, slots, err
|
||||
}
|
||||
|
||||
comp, err := getComponent(file, parser.disableCache())
|
||||
comp, err := getComponent(route, file, parser.disableCache())
|
||||
if err != nil {
|
||||
return nil, props, slots, err
|
||||
}
|
||||
|
|
@ -225,10 +284,10 @@ func (parser *TemplateParser) parseComponentProps(props map[string]string) (map[
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (parser *TemplateParser) componentFile(sel *goquery.Selection, props map[string]interface{}) (string, error) {
|
||||
func (parser *TemplateParser) componentFile(sel *goquery.Selection, props map[string]interface{}) (string, string, error) {
|
||||
route := sel.AttrOr("is", "")
|
||||
if route == "" {
|
||||
return "", fmt.Errorf("Component route is required")
|
||||
return "", "", fmt.Errorf("Component route is required")
|
||||
}
|
||||
|
||||
data := Data{"$props": props}
|
||||
|
|
@ -236,7 +295,7 @@ func (parser *TemplateParser) componentFile(sel *goquery.Selection, props map[st
|
|||
route, _ = parser.data.Replace(route)
|
||||
root := sel.AttrOr("s:root", "/")
|
||||
file := filepath.Join(string(os.PathSeparator), "public", root, route+".jit")
|
||||
return file, nil
|
||||
return file, route, nil
|
||||
}
|
||||
|
||||
func componentWriter() {
|
||||
|
|
@ -253,7 +312,7 @@ func componentWriter() {
|
|||
}
|
||||
}
|
||||
|
||||
func getComponent(file string, disableCache ...bool) (*JitComponent, error) {
|
||||
func getComponent(route string, file string, disableCache ...bool) (*JitComponent, error) {
|
||||
|
||||
noCache := false
|
||||
if len(disableCache) > 0 && disableCache[0] {
|
||||
|
|
@ -264,7 +323,7 @@ func getComponent(file string, disableCache ...bool) (*JitComponent, error) {
|
|||
return comp, nil
|
||||
}
|
||||
|
||||
comp, err := readComponent(file)
|
||||
comp, err := readComponent(route, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -275,10 +334,63 @@ func getComponent(file string, disableCache ...bool) (*JitComponent, error) {
|
|||
}
|
||||
|
||||
// readComponent read the JIT component
|
||||
func readComponent(file string) (*JitComponent, error) {
|
||||
func readComponent(route string, file string) (*JitComponent, error) {
|
||||
content, err := application.App.Read(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &JitComponent{html: string(content)}, nil
|
||||
|
||||
// Get the scripts
|
||||
html := string(content)
|
||||
rawScripts := ""
|
||||
rawStyles := ""
|
||||
rawOption := ""
|
||||
html = reScripts.ReplaceAllStringFunc(html, func(exp string) string {
|
||||
rawScripts = reScripts.ReplaceAllString(exp, "$1")
|
||||
return ""
|
||||
})
|
||||
|
||||
html = reStyles.ReplaceAllStringFunc(html, func(exp string) string {
|
||||
rawStyles = reStyles.ReplaceAllString(exp, "$1")
|
||||
return ""
|
||||
})
|
||||
|
||||
html = reOption.ReplaceAllStringFunc(html, func(exp string) string {
|
||||
rawOption = reOption.ReplaceAllString(exp, "$1")
|
||||
return ""
|
||||
})
|
||||
|
||||
scripts := []ScriptNode{}
|
||||
styles := []StyleNode{}
|
||||
buildOption := BuildOption{}
|
||||
|
||||
if rawScripts != "" {
|
||||
err := jsoniter.UnmarshalFromString(rawScripts, &scripts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if rawStyles != "" {
|
||||
err := jsoniter.UnmarshalFromString(rawStyles, &styles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if rawOption != "" {
|
||||
err := jsoniter.UnmarshalFromString(rawOption, &buildOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return &JitComponent{
|
||||
file: file,
|
||||
route: route,
|
||||
html: html,
|
||||
scripts: scripts,
|
||||
styles: styles,
|
||||
buildOption: &buildOption,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ type TemplateParser struct {
|
|||
option *ParserOption // parser option
|
||||
locale *Locale // locale
|
||||
context *ParserContext // parser context
|
||||
scripts []ScriptNode // scripts
|
||||
styles []StyleNode // styles
|
||||
}
|
||||
|
||||
// ParserContext parser context for the template
|
||||
|
|
@ -171,6 +173,8 @@ func NewTemplateParser(data Data, option *ParserOption) *TemplateParser {
|
|||
errors: []error{},
|
||||
replace: map[*goquery.Selection][]*html.Node{},
|
||||
option: option,
|
||||
scripts: []ScriptNode{},
|
||||
styles: []StyleNode{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,6 +206,8 @@ func (parser *TemplateParser) Render(html string) (string, error) {
|
|||
head := doc.Find("head")
|
||||
if head.Length() > 0 {
|
||||
head.AppendHtml(headInjectionScript())
|
||||
parser.addScripts(head, parser.scripts)
|
||||
parser.addStyles(head, parser.styles)
|
||||
}
|
||||
|
||||
// Append the data to the body
|
||||
|
|
|
|||
|
|
@ -191,8 +191,9 @@ type Template struct {
|
|||
|
||||
// TemplateScirpts is the struct for the template scripts
|
||||
type TemplateScirpts struct {
|
||||
BeforeBuild []*TemplateScript `json:"before:build,omitempty"` // Run before build
|
||||
AfterBuild []*TemplateScript `json:"after:build,omitempty"` // Run after build
|
||||
BeforeBuild []*TemplateScript `json:"before:build,omitempty"` // Run before build
|
||||
AfterBuild []*TemplateScript `json:"after:build,omitempty"` // Run after build
|
||||
BuildComplete []*TemplateScript `json:"build:complete,omitempty"` // Run build complete
|
||||
}
|
||||
|
||||
// TemplateScript is the struct for the template script
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ import (
|
|||
// Build the template
|
||||
func (tmpl *Template) Build(option *core.BuildOption) error {
|
||||
var err error
|
||||
defer func() {
|
||||
if option.ExecScripts {
|
||||
tmpl.ExecBuildCompleteScripts()
|
||||
}
|
||||
}()
|
||||
|
||||
// Execute the build before hook
|
||||
if option.ExecScripts {
|
||||
|
|
|
|||
|
|
@ -79,6 +79,14 @@ func (tmpl *Template) Reload() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ExecBuildCompleteScripts execute the build complete scripts
|
||||
func (tmpl *Template) ExecBuildCompleteScripts() []core.TemplateScirptResult {
|
||||
if tmpl.Scripts == nil || len(tmpl.Scripts.BuildComplete) == 0 {
|
||||
return nil
|
||||
}
|
||||
return tmpl.ExecScripts(tmpl.Scripts.BuildComplete)
|
||||
}
|
||||
|
||||
// ExecBeforeBuildScripts execute the before build scripts
|
||||
func (tmpl *Template) ExecBeforeBuildScripts() []core.TemplateScirptResult {
|
||||
if tmpl.Scripts == nil || len(tmpl.Scripts.BeforeBuild) == 0 {
|
||||
|
|
@ -97,7 +105,12 @@ func (tmpl *Template) ExecAfterBuildScripts() []core.TemplateScirptResult {
|
|||
|
||||
// ExecScripts execute the scripts
|
||||
func (tmpl *Template) ExecScripts(scripts []*core.TemplateScript) []core.TemplateScirptResult {
|
||||
|
||||
results := []core.TemplateScirptResult{}
|
||||
if scripts == nil {
|
||||
return results
|
||||
}
|
||||
|
||||
for _, script := range scripts {
|
||||
switch script.Type {
|
||||
case "command":
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue