diff --git a/sui/core/component.go b/sui/core/component.go new file mode 100644 index 00000000..4f04f93d --- /dev/null +++ b/sui/core/component.go @@ -0,0 +1,58 @@ +package core + +import ( + "fmt" + "strings" + + "github.com/evanw/esbuild/pkg/api" + "github.com/yaoapp/gou/runtime/transform" +) + +// Compile compile the component +func (component *Component) Compile() (string, error) { + + // Typescript is the default language + // Typescript + if component.Codes.TS.Code != "" { + varName := strings.Replace(component.ID, "-", "_", -1) + ts := strings.Replace(component.Codes.TS.Code, "export default", fmt.Sprintf("window.component__%s =", varName), 1) + if component.Codes.HTML.Code != "" && !strings.Contains(component.Codes.TS.Code, "content:") { + html := strings.ReplaceAll(component.Codes.HTML.Code, "`", "\\`") + ts = strings.Replace(ts, "defaults: {", "defaults: {\n components: `"+html+"`,", 1) + + } + + js, err := transform.TypeScript(ts, api.TransformOptions{ + Target: api.ESNext, + MinifyWhitespace: true, + MinifyIdentifiers: true, + MinifySyntax: true, + }) + + if err != nil { + return "", err + } + component.Compiled = js + return js, nil + } + + // Javascript + if component.Codes.JS.Code == "" { + return "", fmt.Errorf("Block %s has no JS code", component.ID) + } + + varName := strings.Replace(component.ID, "-", "_", -1) + js := strings.Replace(component.Codes.JS.Code, "export default", fmt.Sprintf("window.component__%s =", varName), 1) + if component.Codes.HTML.Code != "" && !strings.Contains(component.Codes.JS.Code, "content:") { + html := strings.ReplaceAll(component.Codes.HTML.Code, "`", "\\`") + js = strings.Replace(js, "defaults: {", "defaults: {\n components: `"+html+"`,", 1) + } + + minified, err := transform.MinifyJS(js) + if err != nil { + return "", err + } + + component.Compiled = minified + return minified, nil +} diff --git a/sui/core/types.go b/sui/core/types.go index b5993cc6..3313fcb3 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -25,8 +25,10 @@ type Page struct { // Component is the struct for the component type Component struct { - templage *Template - name string + ID string `json:"id"` + Name string `json:"name,omitempty"` + Compiled string `json:"-"` + Codes SourceCodes `json:"-"` } // Block is the struct for the block diff --git a/sui/storages/local/block.go b/sui/storages/local/block.go index 635ca9d7..9b87410f 100644 --- a/sui/storages/local/block.go +++ b/sui/storages/local/block.go @@ -1,9 +1,69 @@ package local import ( + "fmt" "path/filepath" + + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/sui/core" ) +// Blocks get the blocks +func (tmpl *Template) Blocks() ([]core.IBlock, error) { + path := filepath.Join(tmpl.Root, "__blocks") + + blocks := []core.IBlock{} + if exist, _ := tmpl.local.fs.Exists(path); !exist { + return blocks, nil + } + + dirs, err := tmpl.local.fs.ReadDir(path, false) + if err != nil { + return nil, err + } + + for _, dir := range dirs { + if !tmpl.local.fs.IsDir(dir) { + continue + } + + block, err := tmpl.getBlockFrom(dir) + if err != nil { + log.Error("Get block error: %v", err) + continue + } + + blocks = append(blocks, block) + } + + return blocks, nil +} + +// Block get the block +func (tmpl *Template) Block(id string) (core.IBlock, error) { + path := filepath.Join(tmpl.Root, "__blocks", id) + if exist, _ := tmpl.local.fs.Exists(path); !exist { + return nil, fmt.Errorf("Block %s not found", id) + } + + block, err := tmpl.getBlockFrom(path) + if err != nil { + return nil, err + } + + err = block.Load() + if err != nil { + return nil, err + } + + _, err = block.Compile() + if err != nil { + return nil, err + } + + return block, nil +} + // Load get the block from the storage func (block *Block) Load() error { @@ -38,3 +98,37 @@ func (block *Block) Load() error { return nil } + +func (tmpl *Template) getBlockFrom(path string) (core.IBlock, error) { + id := tmpl.getBlockID(path) + return tmpl.getBlock(id) +} + +func (tmpl *Template) getBlock(id string) (core.IBlock, error) { + + path := filepath.Join(tmpl.Root, "__blocks", id) + if !tmpl.local.fs.IsDir(path) { + return nil, fmt.Errorf("Block %s not found", id) + } + + jsFile := filepath.Join("/", id, fmt.Sprintf("%s.js", id)) + tsFile := filepath.Join("/", id, fmt.Sprintf("%s.ts", id)) + htmlFile := filepath.Join("/", id, fmt.Sprintf("%s.html", id)) + block := &Block{ + tmpl: tmpl, + Block: &core.Block{ + ID: id, + Codes: core.SourceCodes{ + HTML: core.Source{File: htmlFile}, + JS: core.Source{File: jsFile}, + TS: core.Source{File: tsFile}, + }, + }, + } + + return block, nil +} + +func (tmpl *Template) getBlockID(path string) string { + return filepath.Base(path) +} diff --git a/sui/storages/local/block_test.go b/sui/storages/local/block_test.go new file mode 100644 index 00000000..ea3027fd --- /dev/null +++ b/sui/storages/local/block_test.go @@ -0,0 +1,83 @@ +package local + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTemplateBlocks(t *testing.T) { + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + blocks, err := tmpl.Blocks() + if err != nil { + t.Fatalf("Blocks error: %v", err) + } + + if len(blocks) < 3 { + t.Fatalf("Blocks error: %v", len(blocks)) + } + + assert.Equal(t, "ColumnsTwo", blocks[0].(*Block).ID) + assert.Equal(t, "/ColumnsTwo/ColumnsTwo.html", blocks[0].(*Block).Codes.HTML.File) + assert.Equal(t, "/ColumnsTwo/ColumnsTwo.js", blocks[0].(*Block).Codes.JS.File) + assert.Equal(t, "/ColumnsTwo/ColumnsTwo.ts", blocks[0].(*Block).Codes.TS.File) + + assert.Equal(t, "Hero", blocks[1].(*Block).ID) + assert.Equal(t, "/Hero/Hero.html", blocks[1].(*Block).Codes.HTML.File) + assert.Equal(t, "/Hero/Hero.js", blocks[1].(*Block).Codes.JS.File) + assert.Equal(t, "/Hero/Hero.ts", blocks[1].(*Block).Codes.TS.File) + + assert.Equal(t, "Section", blocks[2].(*Block).ID) + assert.Equal(t, "/Section/Section.html", blocks[2].(*Block).Codes.HTML.File) + assert.Equal(t, "/Section/Section.js", blocks[2].(*Block).Codes.JS.File) + assert.Equal(t, "/Section/Section.ts", blocks[2].(*Block).Codes.TS.File) +} + +func TestTemplateBlockJS(t *testing.T) { + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + block, err := tmpl.Block("ColumnsTwo") + if err != nil { + t.Fatalf("Blocks error: %v", err) + } + + assert.Equal(t, "ColumnsTwo", block.(*Block).ID) + assert.NotEmpty(t, block.(*Block).Codes.HTML.Code) + assert.NotEmpty(t, block.(*Block).Codes.JS.Code) + assert.Contains(t, block.(*Block).Compiled, "window.block__ColumnsTwo") + assert.Contains(t, block.(*Block).Compiled, `
`) +} diff --git a/sui/storages/local/component.go b/sui/storages/local/component.go index f74fd61a..e55e5b3f 100644 --- a/sui/storages/local/component.go +++ b/sui/storages/local/component.go @@ -1,16 +1,134 @@ package local -// NewComponent create a new local component -func NewComponent(file string) (*Component, error) { - return nil, nil +import ( + "fmt" + "path/filepath" + + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/sui/core" +) + +// Components get the components +func (tmpl *Template) Components() ([]core.IComponent, error) { + + path := filepath.Join(tmpl.Root, "__components") + components := []core.IComponent{} + if exist, _ := tmpl.local.fs.Exists(path); !exist { + return components, nil + } + + dirs, err := tmpl.local.fs.ReadDir(path, false) + if err != nil { + return nil, err + } + + for _, dir := range dirs { + if !tmpl.local.fs.IsDir(dir) { + continue + } + + block, err := tmpl.getComponentFrom(dir) + if err != nil { + log.Error("Get block error: %v", err) + continue + } + + components = append(components, block) + } + + return components, nil } -// Get get the component -func (comp *Component) Get() error { +// Component get the component +func (tmpl *Template) Component(id string) (core.IComponent, error) { + + path := filepath.Join(tmpl.Root, "__components", id) + if exist, _ := tmpl.local.fs.Exists(path); !exist { + return nil, fmt.Errorf("Block %s not found", id) + } + + component, err := tmpl.getComponentFrom(path) + if err != nil { + return nil, err + } + + err = component.Load() + if err != nil { + return nil, err + } + + _, err = component.Compile() + if err != nil { + return nil, err + } + + return component, nil +} + +// Load get the component from the storage +func (component *Component) Load() error { + + root := filepath.Join(component.tmpl.Root, "__components") + + // Type script is the default language + tsFile := filepath.Join(root, component.Codes.TS.File) + if exist, _ := component.tmpl.local.fs.Exists(tsFile); exist { + tsCode, err := component.tmpl.local.fs.ReadFile(tsFile) + if err != nil { + return err + } + component.Codes.TS.Code = string(tsCode) + + } else { + jsFile := filepath.Join(root, component.Codes.JS.File) + jsCode, err := component.tmpl.local.fs.ReadFile(jsFile) + if err != nil { + return err + } + component.Codes.JS.Code = string(jsCode) + } + + htmlFile := filepath.Join(root, component.Codes.HTML.File) + if exist, _ := component.tmpl.local.fs.Exists(htmlFile); exist { + htmlCode, err := component.tmpl.local.fs.ReadFile(htmlFile) + if err != nil { + return err + } + component.Codes.HTML.Code = string(htmlCode) + } + return nil } -// Save save the component -func (comp *Component) Save() error { - return nil +func (tmpl *Template) getComponentFrom(path string) (core.IComponent, error) { + id := tmpl.getComponentID(path) + return tmpl.getComponent(id) +} + +func (tmpl *Template) getComponent(id string) (core.IComponent, error) { + + path := filepath.Join(tmpl.Root, "__components", id) + if !tmpl.local.fs.IsDir(path) { + return nil, fmt.Errorf("Component %s not found", id) + } + + jsFile := filepath.Join("/", id, fmt.Sprintf("%s.js", id)) + tsFile := filepath.Join("/", id, fmt.Sprintf("%s.ts", id)) + htmlFile := filepath.Join("/", id, fmt.Sprintf("%s.html", id)) + component := &Component{ + tmpl: tmpl, + Component: &core.Component{ + ID: id, + Codes: core.SourceCodes{ + HTML: core.Source{File: htmlFile}, + JS: core.Source{File: jsFile}, + TS: core.Source{File: tsFile}, + }, + }, + } + return component, nil +} + +func (tmpl *Template) getComponentID(path string) string { + return filepath.Base(path) } diff --git a/sui/storages/local/component_test.go b/sui/storages/local/component_test.go new file mode 100644 index 00000000..0e7a626d --- /dev/null +++ b/sui/storages/local/component_test.go @@ -0,0 +1,83 @@ +package local + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTemplateComponents(t *testing.T) { + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + components, err := tmpl.Components() + if err != nil { + t.Fatalf("Components error: %v", err) + } + + if len(components) < 2 { + t.Fatalf("Components error: %v", len(components)) + } + assert.Equal(t, "Box", components[0].(*Component).ID) + assert.Equal(t, "/Box/Box.html", components[0].(*Component).Codes.HTML.File) + assert.Equal(t, "/Box/Box.js", components[0].(*Component).Codes.JS.File) + assert.Equal(t, "/Box/Box.ts", components[0].(*Component).Codes.TS.File) + + assert.Equal(t, "Card", components[1].(*Component).ID) + assert.Equal(t, "/Card/Card.html", components[1].(*Component).Codes.HTML.File) + assert.Equal(t, "/Card/Card.js", components[1].(*Component).Codes.JS.File) + assert.Equal(t, "/Card/Card.ts", components[1].(*Component).Codes.TS.File) + + assert.Equal(t, "Nav", components[2].(*Component).ID) + assert.Equal(t, "/Nav/Nav.html", components[2].(*Component).Codes.HTML.File) + assert.Equal(t, "/Nav/Nav.js", components[2].(*Component).Codes.JS.File) + assert.Equal(t, "/Nav/Nav.ts", components[2].(*Component).Codes.TS.File) + +} + +func TestTemplateComponentJS(t *testing.T) { + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + component, err := tmpl.Component("Card") + if err != nil { + t.Fatalf("Components error: %v", err) + } + + assert.Equal(t, "Card", component.(*Component).ID) + assert.NotEmpty(t, component.(*Component).Codes.HTML.Code) + assert.NotEmpty(t, component.(*Component).Codes.JS.Code) + assert.Contains(t, component.(*Component).Compiled, "window.component__Card") + assert.Contains(t, component.(*Component).Compiled, `

Card

`) +} + +func TestTemplateComponentTS(t *testing.T) { + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + component, err := tmpl.Component("Box") + if err != nil { + t.Fatalf("Components error: %v", err) + } + + assert.Equal(t, "Box", component.(*Component).ID) + assert.NotEmpty(t, component.(*Component).Codes.HTML.Code) + assert.NotEmpty(t, component.(*Component).Codes.TS.Code) + assert.Contains(t, component.(*Component).Compiled, "window.component__Box") + assert.Contains(t, component.(*Component).Compiled, `
Box
`) +} diff --git a/sui/storages/local/template.go b/sui/storages/local/template.go index 6673c393..f819d238 100644 --- a/sui/storages/local/template.go +++ b/sui/storages/local/template.go @@ -91,106 +91,6 @@ func (tmpl *Template) getPageRoute(path string) string { return strings.TrimSuffix(path[len(tmpl.Root):], filepath.Ext(path)) } -// Blocks get the blocks -func (tmpl *Template) Blocks() ([]core.IBlock, error) { - path := filepath.Join(tmpl.Root, "__blocks") - - blocks := []core.IBlock{} - if exist, _ := tmpl.local.fs.Exists(path); !exist { - return blocks, nil - } - - dirs, err := tmpl.local.fs.ReadDir(path, false) - if err != nil { - return nil, err - } - - for _, dir := range dirs { - if !tmpl.local.fs.IsDir(dir) { - continue - } - - block, err := tmpl.getBlockFrom(dir) - if err != nil { - log.Error("Get block error: %v", err) - continue - } - - blocks = append(blocks, block) - } - - return blocks, nil -} - -// Block get the block -func (tmpl *Template) Block(id string) (core.IBlock, error) { - path := filepath.Join(tmpl.Root, "__blocks", id) - if exist, _ := tmpl.local.fs.Exists(path); !exist { - return nil, fmt.Errorf("Block %s not found", id) - } - - block, err := tmpl.getBlockFrom(path) - if err != nil { - return nil, err - } - - err = block.Load() - if err != nil { - return nil, err - } - - _, err = block.Compile() - if err != nil { - return nil, err - } - - return block, nil -} - -func (tmpl *Template) getBlockFrom(path string) (core.IBlock, error) { - id := tmpl.getBlockID(path) - return tmpl.getBlock(id) -} - -func (tmpl *Template) getBlock(id string) (core.IBlock, error) { - - path := filepath.Join(tmpl.Root, "__blocks", id) - if !tmpl.local.fs.IsDir(path) { - return nil, fmt.Errorf("Block %s not found", id) - } - - jsFile := filepath.Join("/", id, "main.js") - tsFile := filepath.Join("/", id, "main.ts") - htmlFile := filepath.Join("/", id, "main.html") - block := &Block{ - tmpl: tmpl, - Block: &core.Block{ - ID: id, - Codes: core.SourceCodes{ - HTML: core.Source{File: htmlFile}, - JS: core.Source{File: jsFile}, - TS: core.Source{File: tsFile}, - }, - }, - } - - return block, nil -} - -func (tmpl *Template) getBlockID(path string) string { - return filepath.Base(path) -} - -// Components get the components -func (tmpl *Template) Components() ([]core.IComponent, error) { - return nil, nil -} - -// Component get the component -func (tmpl *Template) Component(name string) (core.IComponent, error) { - return nil, nil -} - // Styles get the global styles func (tmpl *Template) Styles() []string { return nil diff --git a/sui/storages/local/template_test.go b/sui/storages/local/template_test.go index f82daa45..7666d884 100644 --- a/sui/storages/local/template_test.go +++ b/sui/storages/local/template_test.go @@ -60,79 +60,3 @@ func TestTemplatePage(t *testing.T) { _, err = tmpl.Page("/the/page/could/not/be/found") assert.Contains(t, err.Error(), "Page /the/page/could/not/be/found not found") } - -func TestTemplateBlocks(t *testing.T) { - tests := prepare(t) - defer clean() - - tmpl, err := tests.Demo.GetTemplate("tech-blue") - if err != nil { - t.Fatalf("GetTemplate error: %v", err) - } - - blocks, err := tmpl.Blocks() - if err != nil { - t.Fatalf("Blocks error: %v", err) - } - - if len(blocks) < 3 { - t.Fatalf("Blocks error: %v", len(blocks)) - } - - assert.Equal(t, "columns-two", blocks[0].(*Block).ID) - assert.Equal(t, "/columns-two/main.html", blocks[0].(*Block).Codes.HTML.File) - assert.Equal(t, "/columns-two/main.js", blocks[0].(*Block).Codes.JS.File) - assert.Equal(t, "/columns-two/main.ts", blocks[0].(*Block).Codes.TS.File) - - assert.Equal(t, "hero", blocks[1].(*Block).ID) - assert.Equal(t, "/hero/main.html", blocks[1].(*Block).Codes.HTML.File) - assert.Equal(t, "/hero/main.js", blocks[1].(*Block).Codes.JS.File) - assert.Equal(t, "/hero/main.ts", blocks[1].(*Block).Codes.TS.File) - - assert.Equal(t, "section", blocks[2].(*Block).ID) - assert.Equal(t, "/section/main.html", blocks[2].(*Block).Codes.HTML.File) - assert.Equal(t, "/section/main.js", blocks[2].(*Block).Codes.JS.File) - assert.Equal(t, "/section/main.ts", blocks[2].(*Block).Codes.TS.File) -} - -func TestTemplateBlockJS(t *testing.T) { - tests := prepare(t) - defer clean() - - tmpl, err := tests.Demo.GetTemplate("tech-blue") - if err != nil { - t.Fatalf("GetTemplate error: %v", err) - } - - block, err := tmpl.Block("columns-two") - if err != nil { - t.Fatalf("Blocks error: %v", err) - } - - assert.Equal(t, "columns-two", block.(*Block).ID) - assert.NotEmpty(t, block.(*Block).Codes.HTML.Code) - assert.NotEmpty(t, block.(*Block).Codes.JS.Code) - assert.Contains(t, block.(*Block).Compiled, "window.block__columns_two") - assert.Contains(t, block.(*Block).Compiled, `
`) -} diff --git a/sui/storages/local/types.go b/sui/storages/local/types.go index 7b8c07c0..794e2387 100644 --- a/sui/storages/local/types.go +++ b/sui/storages/local/types.go @@ -33,5 +33,6 @@ type Block struct { // Component is the struct for the local sui component type Component struct { + tmpl *Template *core.Component }