commit
0a5ea2540f
13 changed files with 538 additions and 104 deletions
2
go.mod
2
go.mod
|
|
@ -37,6 +37,8 @@ require (
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/google/go-github/v30 v30.1.0 // indirect
|
github.com/google/go-github/v30 v30.1.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||||
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
|
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
|
|
|
||||||
3
go.sum
3
go.sum
|
|
@ -251,6 +251,7 @@ github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
|
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
|
||||||
|
|
@ -260,6 +261,8 @@ github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH
|
||||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||||
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ=
|
github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ=
|
||||||
github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
|
github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s=
|
||||||
github.com/hashicorp/go-plugin v1.5.1 h1:oGm7cWBaYIp3lJpx1RUEfLWophprE2EV/KUeqBYo+6k=
|
github.com/hashicorp/go-plugin v1.5.1 h1:oGm7cWBaYIp3lJpx1RUEfLWophprE2EV/KUeqBYo+6k=
|
||||||
|
|
|
||||||
|
|
@ -162,8 +162,22 @@ var dsl = []byte(`
|
||||||
"path": "/:id/preview/:template_id/*route",
|
"path": "/:id/preview/:template_id/*route",
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"process": "sui.Preview.Render",
|
"process": "sui.Preview.Render",
|
||||||
"in": ["$param.id", "$param.template_id", "$param.path", "$header.Referer", "$query.r", "$query.t"],
|
"in": ["$param.id", "$param.template_id", "$param.route", "$header.Referer", "$query.r", "$query.t"],
|
||||||
"out": {"status": 200, "type": "text/html; charset=utf-8"}
|
"out": {"status": 200, "type": "text/html; charset=utf-8"}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"path": "/:id/build/:template_id",
|
||||||
|
"method": "POST",
|
||||||
|
"process": "sui.Build.All",
|
||||||
|
"in": ["$param.id", "$param.template_id", ":payload"],
|
||||||
|
"out": {"status": 200}
|
||||||
|
},{
|
||||||
|
"path": "/:id/build/:template_id/*route",
|
||||||
|
"method": "POST",
|
||||||
|
"process": "sui.Build.Page",
|
||||||
|
"in": ["$param.id", "$param.template_id", "$param.route", ":payload"],
|
||||||
|
"out": {"status": 200}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,9 @@ func init() {
|
||||||
"editor.sourceaftersavetemp": EditorSourceAfterSaveTemp,
|
"editor.sourceaftersavetemp": EditorSourceAfterSaveTemp,
|
||||||
|
|
||||||
"preview.render": PreviewRender,
|
"preview.render": PreviewRender,
|
||||||
|
|
||||||
|
"build.all": BuildAll,
|
||||||
|
"build.page": BuildPage,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -571,6 +574,77 @@ func PreviewRender(process *process.Process) interface{} {
|
||||||
return html
|
return html
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildAll handle the render page request
|
||||||
|
func BuildAll(process *process.Process) interface{} {
|
||||||
|
|
||||||
|
process.ValidateArgNums(3)
|
||||||
|
sui := get(process)
|
||||||
|
templateID := process.ArgsString(1)
|
||||||
|
|
||||||
|
option := process.ArgsMap(2, map[string]interface{}{})
|
||||||
|
ssr := true
|
||||||
|
if v, ok := option["ssr"].(bool); ok {
|
||||||
|
ssr = v
|
||||||
|
}
|
||||||
|
|
||||||
|
assetRoot := ""
|
||||||
|
if v, ok := option["asset_root"].(string); ok {
|
||||||
|
assetRoot = v
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := sui.GetTemplate(templateID)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tmpl.Build(&core.BuildOption{SSR: ssr, AssetRoot: assetRoot})
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildPage handle the render page request
|
||||||
|
func BuildPage(process *process.Process) interface{} {
|
||||||
|
process.ValidateArgNums(4)
|
||||||
|
sui := get(process)
|
||||||
|
templateID := process.ArgsString(1)
|
||||||
|
route := route(process, 2)
|
||||||
|
option := process.ArgsMap(3, map[string]interface{}{})
|
||||||
|
ssr := true
|
||||||
|
if v, ok := option["ssr"].(bool); ok {
|
||||||
|
ssr = v
|
||||||
|
}
|
||||||
|
|
||||||
|
assetRoot := ""
|
||||||
|
if v, ok := option["asset_root"].(string); ok {
|
||||||
|
assetRoot = v
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := sui.GetTemplate(templateID)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
page, err := tmpl.Page(route)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = page.Load()
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = page.Build(&core.BuildOption{SSR: ssr, AssetRoot: assetRoot})
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// get the sui
|
// get the sui
|
||||||
func get(process *process.Process) core.SUI {
|
func get(process *process.Process) core.SUI {
|
||||||
sui, has := core.SUIs[process.ArgsString(0)]
|
sui, has := core.SUIs[process.ArgsString(0)]
|
||||||
|
|
|
||||||
|
|
@ -547,6 +547,27 @@ func TestEditorRender(t *testing.T) {
|
||||||
assert.NotEmpty(t, res.(*core.ResponseEditorRender).Config)
|
assert.NotEmpty(t, res.(*core.ResponseEditorRender).Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEditorPageSource(t *testing.T) {
|
||||||
|
load(t)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
sources := []string{"page", "script", "style", "data"}
|
||||||
|
for _, source := range sources {
|
||||||
|
p, err := process.Of("sui.editor.source", "demo", "tech-blue", "/index", source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := p.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.IsType(t, core.SourceData{}, res)
|
||||||
|
assert.NotEmpty(t, res.(core.SourceData).Source)
|
||||||
|
assert.NotEmpty(t, res.(core.SourceData).Language)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEditorRenderWithQuery(t *testing.T) {
|
func TestEditorRenderWithQuery(t *testing.T) {
|
||||||
load(t)
|
load(t)
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
@ -587,25 +608,40 @@ func TestPreviewRender(t *testing.T) {
|
||||||
assert.NotEmpty(t, res)
|
assert.NotEmpty(t, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEditorPageSource(t *testing.T) {
|
func TestBuildAll(t *testing.T) {
|
||||||
load(t)
|
load(t)
|
||||||
defer clean()
|
defer clean()
|
||||||
|
|
||||||
sources := []string{"page", "script", "style", "data"}
|
// test demo
|
||||||
for _, source := range sources {
|
p, err := process.Of("sui.build.all", "demo", "tech-blue", map[string]interface{}{"ssr": true})
|
||||||
p, err := process.Of("sui.editor.source", "demo", "tech-blue", "/index", source)
|
if err != nil {
|
||||||
if err != nil {
|
t.Fatal(err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := p.Exec()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
assert.IsType(t, core.SourceData{}, res)
|
|
||||||
assert.NotEmpty(t, res.(core.SourceData).Source)
|
|
||||||
assert.NotEmpty(t, res.(core.SourceData).Language)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res, err := p.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildPage(t *testing.T) {
|
||||||
|
load(t)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
// test demo
|
||||||
|
p, err := process.Of("sui.build.page", "demo", "tech-blue", "/index", map[string]interface{}{"ssr": true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := p.Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func load(t *testing.T) {
|
func load(t *testing.T) {
|
||||||
|
|
|
||||||
96
sui/core/build.go
Normal file
96
sui/core/build.go
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build is the struct for the public
|
||||||
|
func (page *Page) Build(option *BuildOption) (*goquery.Document, []string, error) {
|
||||||
|
|
||||||
|
warnings := []string{}
|
||||||
|
html, err := page.BuildHTML(option.AssetRoot)
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Style & Script & Warning
|
||||||
|
doc, err := NewDocument([]byte(html))
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add Style
|
||||||
|
style, err := page.BuildStyle(option.AssetRoot)
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, err.Error())
|
||||||
|
}
|
||||||
|
doc.Selection.Find("head").AppendHtml(style)
|
||||||
|
|
||||||
|
// Add Script
|
||||||
|
script, err := page.BuildScript(option.AssetRoot)
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, err.Error())
|
||||||
|
}
|
||||||
|
doc.Selection.Find("body").AppendHtml(script)
|
||||||
|
return doc, warnings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildHTML build the html
|
||||||
|
func (page *Page) BuildHTML(assetRoot string) (string, error) {
|
||||||
|
|
||||||
|
html := string(page.Document)
|
||||||
|
if page.Codes.HTML.Code != "" {
|
||||||
|
html = strings.Replace(html, "{{ __page }}", page.Codes.HTML.Code, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
code := strings.ReplaceAll(html, "@assets", assetRoot)
|
||||||
|
res, err := page.CompileHTML([]byte(code), false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildStyle build the style
|
||||||
|
func (page *Page) BuildStyle(assetRoot string) (string, error) {
|
||||||
|
if page.Codes.CSS.Code == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
code := strings.ReplaceAll(page.Codes.CSS.Code, "@assets", assetRoot)
|
||||||
|
res, err := page.CompileCSS([]byte(code), false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("<style>\n%s\n</style>\n", res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildScript build the script
|
||||||
|
func (page *Page) BuildScript(assetRoot string) (string, error) {
|
||||||
|
|
||||||
|
if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if page.Codes.TS.Code != "" {
|
||||||
|
res, err := page.CompileTS([]byte(page.Codes.TS.Code), false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("<script>\n%s\n</script>\n", res), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
code := strings.ReplaceAll(page.Codes.JS.Code, "@assets", assetRoot)
|
||||||
|
res, err := page.CompileJS([]byte(code), false)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("<script>\n%s\n</script>\n", res), nil
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/evanw/esbuild/pkg/api"
|
"github.com/evanw/esbuild/pkg/api"
|
||||||
"github.com/yaoapp/gou/runtime/transform"
|
"github.com/yaoapp/gou/runtime/transform"
|
||||||
|
|
@ -53,60 +51,3 @@ func (page *Page) CompileCSS(source []byte, minify bool) ([]byte, error) {
|
||||||
func (page *Page) CompileHTML(source []byte, minify bool) ([]byte, error) {
|
func (page *Page) CompileHTML(source []byte, minify bool) ([]byte, error) {
|
||||||
return source, nil
|
return source, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildHTML build the html
|
|
||||||
func (page *Page) BuildHTML(assetRoot string) (string, error) {
|
|
||||||
|
|
||||||
html := string(page.Document)
|
|
||||||
if page.Codes.HTML.Code != "" {
|
|
||||||
html = strings.Replace(html, "{{ __page }}", page.Codes.HTML.Code, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
code := strings.ReplaceAll(html, "@assets", assetRoot)
|
|
||||||
res, err := page.CompileHTML([]byte(code), false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(res), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildStyle build the style
|
|
||||||
func (page *Page) BuildStyle(assetRoot string) (string, error) {
|
|
||||||
if page.Codes.CSS.Code == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
code := strings.ReplaceAll(page.Codes.CSS.Code, "@assets", assetRoot)
|
|
||||||
res, err := page.CompileCSS([]byte(code), false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("<style>\n%s\n</style>\n", res), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildScript build the script
|
|
||||||
func (page *Page) BuildScript(assetRoot string) (string, error) {
|
|
||||||
|
|
||||||
if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if page.Codes.TS.Code != "" {
|
|
||||||
res, err := page.CompileTS([]byte(page.Codes.TS.Code), false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("<script>\n%s\n</script>\n", res), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
code := strings.ReplaceAll(page.Codes.JS.Code, "@assets", assetRoot)
|
|
||||||
res, err := page.CompileCSS([]byte(code), false)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("<script>\n%s\n</script>\n", res), nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ type ITemplate interface {
|
||||||
Themes() []SelectOption
|
Themes() []SelectOption
|
||||||
|
|
||||||
Asset(file string) (*Asset, error)
|
Asset(file string) (*Asset, error)
|
||||||
|
|
||||||
|
Build(option *BuildOption) error
|
||||||
|
SyncAssets(option *BuildOption) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPage is the interface for the page
|
// IPage is the interface for the page
|
||||||
|
|
@ -53,6 +56,8 @@ type IPage interface {
|
||||||
|
|
||||||
AssetScript() (*Asset, error)
|
AssetScript() (*Asset, error)
|
||||||
AssetStyle() (*Asset, error)
|
AssetStyle() (*Asset, error)
|
||||||
|
|
||||||
|
Build(option *BuildOption) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// IBlock is the interface for the block
|
// IBlock is the interface for the block
|
||||||
|
|
|
||||||
|
|
@ -8,41 +8,16 @@ import (
|
||||||
func (page *Page) PreviewRender(request *Request) (string, error) {
|
func (page *Page) PreviewRender(request *Request) (string, error) {
|
||||||
|
|
||||||
warnings := []string{}
|
warnings := []string{}
|
||||||
html, err := page.BuildHTML(request.AssetRoot)
|
doc, warnings, err := page.Build(&BuildOption{
|
||||||
if err != nil {
|
SSR: true,
|
||||||
warnings = append(warnings, err.Error())
|
AssetRoot: request.AssetRoot,
|
||||||
}
|
})
|
||||||
|
|
||||||
data, _, err := page.Data(request)
|
data, _, err := page.Data(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnings = append(warnings, err.Error())
|
warnings = append(warnings, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
html, err = page.Render(html, data, warnings)
|
|
||||||
if err != nil {
|
|
||||||
warnings = append(warnings, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Style & Script & Warning
|
|
||||||
doc, err := NewDocument([]byte(html))
|
|
||||||
if err != nil {
|
|
||||||
warnings = append(warnings, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add Style
|
|
||||||
style, err := page.BuildStyle(request.AssetRoot)
|
|
||||||
if err != nil {
|
|
||||||
warnings = append(warnings, err.Error())
|
|
||||||
}
|
|
||||||
doc.Selection.Find("head").AppendHtml(style)
|
|
||||||
|
|
||||||
// Add Script
|
|
||||||
script, err := page.BuildScript(request.AssetRoot)
|
|
||||||
if err != nil {
|
|
||||||
warnings = append(warnings, err.Error())
|
|
||||||
}
|
|
||||||
doc.Selection.Find("body").AppendHtml(script)
|
|
||||||
|
|
||||||
// Add Frame Height
|
// Add Frame Height
|
||||||
if request.Referer != "" {
|
if request.Referer != "" {
|
||||||
doc.Selection.Find("body").AppendHtml(`
|
doc.Selection.Find("body").AppendHtml(`
|
||||||
|
|
@ -80,5 +55,15 @@ func (page *Page) PreviewRender(request *Request) (string, error) {
|
||||||
doc.Selection.Find("body").AppendHtml(warningHTML)
|
doc.Selection.Find("body").AppendHtml(warningHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html, err := doc.Html()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
html, err = page.Render(html, data, warnings)
|
||||||
|
if err != nil {
|
||||||
|
warnings = append(warnings, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
return doc.Html()
|
return doc.Html()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,14 @@ type Asset struct {
|
||||||
Content []byte `json:"content"`
|
Content []byte `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildOption is the struct for the option option
|
||||||
|
type BuildOption struct {
|
||||||
|
SSR bool `json:"ssr"`
|
||||||
|
CDN bool `json:"cdn"`
|
||||||
|
UpdateAll bool `json:"update_all"`
|
||||||
|
AssetRoot string `json:"asset_root,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Request is the struct for the request
|
// Request is the struct for the request
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
|
|
|
||||||
121
sui/storages/local/build.go
Normal file
121
sui/storages/local/build.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/yao/sui/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Build the template
|
||||||
|
func (tmpl *Template) Build(option *core.BuildOption) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if option.AssetRoot == "" {
|
||||||
|
option.AssetRoot = filepath.Join(tmpl.local.DSL.Public.Root, "assets")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync the assets
|
||||||
|
if err = tmpl.SyncAssets(option); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build all pages
|
||||||
|
pages, err := tmpl.Pages()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, page := range pages {
|
||||||
|
perr := page.Load()
|
||||||
|
if err != nil {
|
||||||
|
err = multierror.Append(perr)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
perr = page.Build(option)
|
||||||
|
if perr != nil {
|
||||||
|
err = multierror.Append(perr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncAssets sync the assets
|
||||||
|
func (tmpl *Template) SyncAssets(option *core.BuildOption) error {
|
||||||
|
|
||||||
|
// get source abs path
|
||||||
|
sourceRoot := filepath.Join(tmpl.local.fs.Root(), tmpl.Root, "__assets")
|
||||||
|
if exist, _ := os.Stat(sourceRoot); exist == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//get target abs path
|
||||||
|
root := tmpl.local.DSL.Public.Root
|
||||||
|
targetRoot := filepath.Join(application.App.Root(), "public", root, "assets")
|
||||||
|
if exist, _ := os.Stat(targetRoot); exist == nil {
|
||||||
|
os.MkdirAll(targetRoot, os.ModePerm)
|
||||||
|
}
|
||||||
|
os.RemoveAll(targetRoot)
|
||||||
|
|
||||||
|
return copyDirectory(sourceRoot, targetRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build is the struct for the public
|
||||||
|
func (page *Page) Build(option *core.BuildOption) error {
|
||||||
|
|
||||||
|
if option.AssetRoot == "" {
|
||||||
|
option.AssetRoot = filepath.Join(page.tmpl.local.DSL.Public.Root, "assets")
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, _, err := page.Page.Build(option)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
html, err := doc.Html()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the html
|
||||||
|
err = page.writeHTML([]byte(html))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the data
|
||||||
|
return page.writeData()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (page *Page) publicFile() string {
|
||||||
|
root := page.tmpl.local.DSL.Public.Root
|
||||||
|
return filepath.Join(application.App.Root(), "public", root, page.Route)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeHTMLTo write the html to file
|
||||||
|
func (page *Page) writeHTML(html []byte) error {
|
||||||
|
htmlFile := fmt.Sprintf("%s.html", page.publicFile())
|
||||||
|
dir := filepath.Dir(htmlFile)
|
||||||
|
if exist, _ := os.Stat(dir); exist == nil {
|
||||||
|
os.MkdirAll(dir, os.ModePerm)
|
||||||
|
}
|
||||||
|
return os.WriteFile(htmlFile, html, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeHTMLTo write the html to file
|
||||||
|
func (page *Page) writeData() error {
|
||||||
|
if page.Codes.DATA.Code == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dataFile := fmt.Sprintf("%s.json", page.publicFile())
|
||||||
|
dir := filepath.Dir(dataFile)
|
||||||
|
if exist, _ := os.Stat(dir); exist == nil {
|
||||||
|
os.MkdirAll(dir, os.ModePerm)
|
||||||
|
}
|
||||||
|
return os.WriteFile(dataFile, []byte(page.Codes.DATA.Code), 0644)
|
||||||
|
}
|
||||||
22
sui/storages/local/build_test.go
Normal file
22
sui/storages/local/build_test.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/sui/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTemplateBuild(t *testing.T) {
|
||||||
|
tests := prepare(t)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
tmpl, err := tests.Demo.GetTemplate("tech-blue")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GetTemplate error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tmpl.Build(&core.BuildOption{SSR: true})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Components error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
127
sui/storages/local/copy.go
Normal file
127
sui/storages/local/copy.go
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// copyDirectory copy the directory
|
||||||
|
func copyDirectory(scrDir, dest string) error {
|
||||||
|
|
||||||
|
if err := createIfNotexists(dest, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := os.ReadDir(scrDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
sourcePath := filepath.Join(scrDir, entry.Name())
|
||||||
|
destPath := filepath.Join(dest, entry.Name())
|
||||||
|
|
||||||
|
fileInfo, err := os.Stat(sourcePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to get raw syscall.Stat_t data for '%s'", sourcePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fileInfo.Mode() & os.ModeType {
|
||||||
|
case os.ModeDir:
|
||||||
|
if err := createIfNotexists(destPath, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := copyDirectory(sourcePath, destPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case os.ModeSymlink:
|
||||||
|
if err := copySymLink(sourcePath, destPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if strings.HasPrefix(entry.Name(), ".") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := copy(sourcePath, destPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy '%s' to '%s': %s", sourcePath, destPath, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Lchown(destPath, int(stat.Uid), int(stat.Gid)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fInfo, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
isSymlink := fInfo.Mode()&os.ModeSymlink != 0
|
||||||
|
if !isSymlink {
|
||||||
|
if err := os.Chmod(destPath, fInfo.Mode()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copy(srcFile, dstFile string) error {
|
||||||
|
out, err := os.Create(dstFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
in, err := os.Open(srcFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func exists(filePath string) bool {
|
||||||
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIfNotexists(dir string, perm os.FileMode) error {
|
||||||
|
if exists(dir) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dir, perm); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copySymLink(source, dest string) error {
|
||||||
|
link, err := os.Readlink(source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return os.Symlink(link, dest)
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue