├── .env
├── .gitignore
├── Makefile
├── README.md
├── docs
└── man
├── scripts
├── create-component
└── create-model
├── server
├── api-routes.go
├── server.go
├── src
│ └── ui
│ │ ├── cache.go
│ │ ├── compile.go
│ │ ├── eval.go
│ │ ├── router.go
│ │ ├── runtime.go
│ │ ├── state.go
│ │ ├── ui.go
│ │ └── watch-changes.go
└── ui-routes.go
└── ui
├── app.css
├── app.js
├── client-side.js
├── components
└── example
│ ├── index.js
│ ├── reducers.js
│ ├── state.js
│ ├── style.css
│ └── view.js
├── index.html
├── package.json
├── routes.js
└── server-side.js
/.env:
--------------------------------------------------------------------------------
1 | ADDR=:9000
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | npm-debug.log
4 | public/dist.js
5 | public/dist.css
6 | server/vendor
7 | server/pkg
8 | server/bin
9 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | include .env
2 |
3 | PROJECTNAME=$(shell basename "$(PWD)")
4 | GOBASE=$(shell pwd)/server
5 | GOPATH=$(GOBASE)/vendor:$(GOBASE)
6 | GOBIN=$(GOBASE)/bin
7 | GOFILES=$(wildcard server/*.go)
8 | PID=/tmp/go-$(PROJECTNAME).pid
9 | UIBASE=$(shell pwd)/ui
10 | UIBIN=$(UIBASE)/node_modules/.bin
11 |
12 | all: develop
13 |
14 | start:
15 | @echo " Starting $(PROJECTNAME) at $(ADDR)"
16 | @-$(GOBIN)/$(PROJECTNAME) & echo $$! > $(PID)
17 | @echo " Process ID: "$(shell cat $(PID))
18 |
19 | stop:
20 | @echo " Stopping $(PROJECTNAME)"
21 | @-touch $(PID)
22 | @-kill `cat $(PID)` 2> /dev/null || true
23 |
24 | restart: stop start
25 | build: go-build ui-build
26 | clean: go-clean ui-clean
27 |
28 | develop: setup build
29 | @DEVELOP=1 LOG=* $(MAKE) restart
30 | @echo " Watching for changes..."
31 | @fswatch server/. -e "server/bin" -e "server/pkg" | (while read; do DEVELOP=1 LOG=* make setup clean build restart; done)
32 |
33 | setup:
34 | @echo " Please wait while I'm getting the dependencies of $(PROJECTNAME) from internet."
35 | @$(MAKE) go-get
36 | @$(MAKE) ui-install
37 |
38 | go-build:
39 | @echo " Building server into $(GOBIN)"
40 | @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go build -o $(GOBIN)/$(PROJECTNAME) $(GOFILES)
41 |
42 | go-get:
43 | @cd server && GOPATH=$(GOPATH) GOBIN=$(GOBIN) go get .
44 |
45 | go-install:
46 | @cd server && GOPATH=$(GOPATH) GOBIN=$(GOBIN) go install $(GOFILES)
47 |
48 | go-run:
49 | @GOPATH=$(GOPATH) GOBIN=$(GOBIN) go run $(GOFILES)
50 |
51 | go-clean:
52 | @echo " Cleaning Go build cache"
53 | @cd server && GOPATH=$(GOPATH) GOBIN=$(GOBIN) go clean
54 |
55 | go-rebuild: go-clean go-build
56 |
57 | ui-install:
58 | @cd ui && npm install --silent
59 |
60 | ui-build:
61 | @echo " Building UI into ./public"
62 | @mkdir -p ./public
63 | @cd ui && ./node_modules/.bin/browserify -r min-document --igv __filename,__dirname,_process -t [ babelify --presets [ es2015 ] ] client-side.js > ../public/dist.js
64 | @cd ui && cat *.css components/**/*.css > ../public/dist.css
65 |
66 | ui-clean:
67 | @echo " Cleaning UI builds"
68 | @-rm public/{dist.js,dist.css} 2> /dev/null || true
69 |
70 | ui-build-serverside:
71 | @cd ui && ./node_modules/.bin/browserify -r min-document --igv __filename,__dirname,_process -t [ babelify --presets [ es2015 ] ] --debug server-side.js
72 |
73 | export COMPONENT_INDEX
74 | export COMPONENT_VIEW
75 | create-component:
76 | define COMPONENT_INDEX
77 | import view from './view'
78 | import state from './state'
79 | import * as reducers from './reducers'
80 | import * as effects from './effects'
81 |
82 | export default {
83 | namespace: '$(name)',
84 | view,
85 | state,
86 | reducers,
87 | effects
88 | }
89 | endef
90 |
91 | define COMPONENT_VIEW
92 | import html from "choo/html"
93 |
94 | const view = (state, prev, send) => html`
95 | $(name)
96 | `
97 |
98 | export default view
99 | endef
100 |
101 | @mkdir ui/components/${name}
102 | @echo "$$COMPONENT_INDEX" > ui/components/${name}/index.js
103 | @echo "$$COMPONENT_VIEW" > ui/components/${name}/view.js
104 | @echo "export default {}" > ui/components/${name}/reducers.js
105 | @echo "export default {}" > ui/components/${name}/effects.js
106 | @echo "export default {}" > ui/components/${name}/state.js
107 |
108 | commands:
109 | @cat docs/man
110 |
111 | usage: commands
112 | help: commands
113 |
114 | .PHONY: default go-build go-get go-install go-run go-rebuild go-clean ui-build ui-clean ui-build-serverside start stop restart clean commands help usage
115 |
116 | ifndef VERBOSE
117 | .SILENT:
118 | endif
119 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## go-choo-starter
2 |
3 | Starter for [choo](https://github.com/yoshuawuyts/choo) projects with [Go](http://golang.org) backend. No configuration needed.
4 |
5 | ### How It Works?
6 |
7 | * Renders [choo](https://github.com/yoshuawuyts/choo) views on server-side with [go-duktape](https://github.com/olebedev/go-duktape).
8 | * Provides a simple Makefile to manage your project;
9 | * `make start` starts everything
10 | * `make stop` stops all
11 | * `make develop` starts the server and watches everything (Go, JS, CSS) for changes.
12 | * `make setup` installs all dependencies (Go and JS)
13 |
14 | ### Install
15 |
16 | Clone the repo and install the dependencies:
17 |
18 | ```bash
19 | git clone git@github.com:azer/go-choo-starter.git hello-world
20 | cd hello-world
21 | make setup # Runs `go get` and `npm install` for you.
22 | ```
23 |
24 | ### First Steps
25 |
26 | Here is how you start the server:
27 |
28 | ```bash
29 | make develop
30 | ```
31 |
32 | `develop` watches your code (Go, JS and CSS) and applies changes immediately. If you don't need that, you can run `make start` and `make stop` commands. You should use these two commands when you're not actively changing your code.
33 |
34 | ### Coding
35 |
36 | * Create UI components under `ui/components/` folder and route them at `ui/app.js`
37 | * Create API endpoints at `server/api.go` like the example there.
38 | * Use `make go-get` to fetch dependencies when you have new dependencies in the backend.
39 | * Any file under `./public` directory will be online at same path.
40 | * Run `make build` to build everything. Run `make go-build` and `make ui-build` to build each separately.
41 | * Run `make clean` to clean everything. Run `make go-clean` and `make ui-clean` to clean each separately.
42 |
--------------------------------------------------------------------------------
/docs/man:
--------------------------------------------------------------------------------
1 |
2 | USAGE
3 |
4 | make [command]
5 |
6 | COMMANDS
7 |
8 | develop
9 | start
10 | stop
11 | restart
12 | setup
13 | clean
14 | help
15 |
16 | go-build
17 | go-get
18 | go-install
19 | go-run
20 | go-rebuild
21 | go-clean
22 |
23 | ui-install
24 | ui-build
25 | ui-clean
26 |
27 | create-component
28 |
--------------------------------------------------------------------------------
/scripts/create-component:
--------------------------------------------------------------------------------
1 | createComponent () {
2 | mkdir ui/components/$1
3 | cat > ui/components/$1/index.js <<- EOM
4 | import view from './view'
5 | import state from './state'
6 | import * as reducers from './reducers'
7 | import * as effects from './effects'
8 |
9 | export default {
10 | namespace: '$1',
11 | view,
12 | state,
13 | reducers,
14 | effects
15 | }
16 | EOM
17 |
18 |
19 | cat > ui/components/$1/view.js <<- EOM
20 | import html from "choo/html"
21 |
22 | export default (state, prev, send) => html\`
23 |
24 |
25 | \`
26 | EOM
27 |
28 | @mkdir ui/components/${name}
29 | @echo "$$COMPONENT_INDEX" > ui/components/${name}/index.js
30 | @echo "$$COMPONENT_VIEW" > ui/components/${name}/view.js
31 | @echo "export default {}" > ui/components/${name}/reducers.js
32 | @echo "export default {}" > ui/components/${name}/effects.js
33 | @echo "export default {}" > ui/components/${name}/state.js
34 | }
35 |
36 | createComponent $1
37 |
--------------------------------------------------------------------------------
/scripts/create-model:
--------------------------------------------------------------------------------
1 | createModel () {
2 | mkdir src/ui/models/$1
3 | cat > src/ui/models/$1/index.js <<- EOM
4 | import state from './state'
5 | import * as reducers from './reducers'
6 | import * as effects from './effects'
7 |
8 | export default {
9 | namespace: '$(name)',
10 | state,
11 | reducers,
12 | effects
13 | }
14 | EOM
15 |
16 | echo "module.exports = {}" > ui/models/$1/state.js
17 | echo "module.exports = {}" > ui/models/$1/reducers.js
18 | echo "module.exports = {}" > ui/models/$1/effects.js
19 | }
20 |
21 | createModel $1
22 |
--------------------------------------------------------------------------------
/server/api-routes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/labstack/echo"
5 | "net/http"
6 | )
7 |
8 | // Set all your API endpoints in this function, on top of the file
9 | func SetAPIRoutes(server *echo.Echo) {
10 | server.GET("/api/hi", Hi)
11 | }
12 |
13 | // And create the handlers after that
14 | func Hi(c echo.Context) error {
15 | return c.JSON(http.StatusOK, &struct {
16 | Hi string
17 | }{"there"})
18 | }
19 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/joho/godotenv"
5 | "github.com/labstack/echo"
6 | "os"
7 | "ui"
8 | )
9 |
10 | func init() {
11 | // Read env variables from .env file in the root directory
12 | if err := godotenv.Load(); err != nil {
13 | panic(err)
14 | }
15 | }
16 |
17 | func main() {
18 | server := echo.New()
19 | SetUIRoutes(server)
20 | SetAPIRoutes(server)
21 |
22 | // Setup static files
23 | server.Static("/public", "./public")
24 | server.GET("/favicon.ico", func(c echo.Context) error {
25 | c.Redirect(301, "/public/favicon.ico")
26 | return nil
27 | })
28 |
29 | // Watch & build front-end stuff if develop mode is enabled
30 | if len(os.Getenv("DEVELOP")) > 0 {
31 | go ui.WatchCodeChanges()
32 | }
33 |
34 | // And finally, run the server. You can edit the ADDR from .env file on the project folder.
35 | server.Start(os.Getenv("ADDR"))
36 | }
37 |
--------------------------------------------------------------------------------
/server/src/ui/cache.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | type Cache struct {
4 | Content map[string]string
5 | }
6 |
7 | func (cache *Cache) Get(path string) (string, bool) {
8 | if _, ok := cache.Content[path]; !ok {
9 | return "", false
10 | }
11 |
12 | return cache.Content[path], true
13 | }
14 |
15 | func (cache *Cache) Set(path, html string) {
16 | cache.Content[path] = html
17 | }
18 |
19 | func NewCache() *Cache {
20 | return &Cache{
21 | Content: map[string]string{},
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/server/src/ui/compile.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "os/exec"
7 | )
8 |
9 | func CompileJS() (string, error) {
10 | var stderr bytes.Buffer
11 | cmd := exec.Command("make", "ui-build-serverside")
12 | cmd.Stderr = &stderr
13 |
14 | source, err := cmd.Output()
15 | if err != nil {
16 | return "", fmt.Errorf("%s: %s", err, stderr.String())
17 | }
18 |
19 | return string(source), nil
20 | }
21 |
22 | func BuildUI() error {
23 | var stderr bytes.Buffer
24 | cmd := exec.Command("make", "ui-build")
25 | cmd.Stderr = &stderr
26 |
27 | _, err := cmd.Output()
28 | if err != nil {
29 | return fmt.Errorf("%s: %s", err, stderr.String())
30 | }
31 |
32 | log.Info("UI assets has been compiled and saved into ./public directory.")
33 |
34 | return nil
35 | }
36 |
--------------------------------------------------------------------------------
/server/src/ui/eval.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "fmt"
5 | . "github.com/azer/go-style"
6 | "gopkg.in/olebedev/go-duktape.v2"
7 | "regexp"
8 | "strings"
9 | )
10 |
11 | const headers = `
12 | require = undefined
13 | console = { log: print, info: print, error: print, warn: print, trace: print }
14 | `
15 |
16 | func EvalJS(code string) (string, error) {
17 | ctx := duktape.New()
18 | code = fmt.Sprintf("%s\n%s", headers, code)
19 |
20 | if err := ctx.PevalString(code); err != nil {
21 | derr := err.(*duktape.Error)
22 | PrintJSError(code, derr.Message, derr.LineNumber)
23 | return "", err
24 | }
25 |
26 | result := ctx.GetString(-1)
27 | ctx.DestroyHeap()
28 |
29 | return result, nil
30 | }
31 |
32 | func PrintJSError(code, message string, lineno int) {
33 | lines := strings.Split(code, "\n")
34 | fmt.Println(Style("bold", fmt.Sprintf("\n 📍 JavaScript Error: %s", Style("red", message))))
35 |
36 | prev := lineno - 2
37 | for prev > 0 && (lines[prev] == "" || isBrowserifyCode(lines[prev])) {
38 | prev--
39 | }
40 |
41 | next := lineno
42 | for next < len(lines) && (lines[next] == "" || isBrowserifyCode(lines[next])) {
43 | next++
44 | }
45 |
46 | fmt.Println(Style("reset", fmt.Sprintf("\n %d. %s", prev+1, lines[prev])))
47 | fmt.Println(Style("red", fmt.Sprintf(" %d. %s", lineno, lines[lineno-1])))
48 | fmt.Println(Style("reset", fmt.Sprintf(" %d. %s\n", next+1, lines[next])))
49 | }
50 |
51 | func isBrowserifyCode(line string) bool {
52 | r, _ := regexp.Compile(`^\s*\}\,\{"[^\"]+":\d+`)
53 | return r.MatchString(line)
54 | }
55 |
--------------------------------------------------------------------------------
/server/src/ui/router.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "fmt"
5 | "github.com/labstack/echo"
6 | "net/http"
7 | )
8 |
9 | func Render(c echo.Context, path string, state *State) error {
10 | body, err := runtime.Render(path, state)
11 | if err != nil {
12 | log.Error("Can not render. Error: %v", err)
13 | return err
14 | }
15 |
16 | html := fmt.Sprintf(indexhtml, body)
17 | cache.Set(path, html)
18 |
19 | return c.HTML(http.StatusOK, html)
20 | }
21 |
22 | func HTTPHandler(next echo.HandlerFunc) echo.HandlerFunc {
23 | return func(c echo.Context) error {
24 | path := c.Request().URL.Path
25 |
26 | if html, ok := cache.Get(path); ok {
27 | return c.HTML(http.StatusOK, html)
28 | }
29 |
30 | if err := runtime.SyncRoutes(); err != nil {
31 | return err
32 | }
33 |
34 | match := runtime.Routes.Match(path)
35 |
36 | if match == nil {
37 | return next(c)
38 | }
39 |
40 | log.Info("%s is being automatically routed to UI component", path)
41 |
42 | state := &State{}
43 | state.Location.Pathname = path
44 | state.Params = match.Params
45 |
46 | return Render(c, path, state)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/server/src/ui/runtime.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "github.com/azer/url-router"
7 | "strings"
8 | )
9 |
10 | type Runtime struct {
11 | CachedSourceCode string
12 | Routes *urlrouter.Router
13 | }
14 |
15 | func (runtime *Runtime) Browserify() error {
16 | code, err := CompileJS()
17 | if err != nil {
18 | return err
19 | }
20 |
21 | runtime.CachedSourceCode = code
22 | return nil
23 | }
24 |
25 | func (runtime *Runtime) CleanCache() {
26 | log.Info("Clearing JS runtime cache...")
27 |
28 | runtime.CachedSourceCode = ""
29 | runtime.Routes = nil
30 | }
31 |
32 | func (runtime *Runtime) CheckForErrors() error {
33 | _, err := runtime.Render("/", nil)
34 | return err
35 | }
36 |
37 | func (runtime *Runtime) Render(route string, state interface{}) (string, error) {
38 | body, err := runtime.SourceCode()
39 | if err != nil {
40 | return "", err
41 | }
42 |
43 | encodedState, err := EncodeState(state)
44 | if err != nil {
45 | return "", err
46 | }
47 |
48 | html, err := EvalJS(fmt.Sprintf(`
49 | var app;
50 | %s
51 |
52 | app.toString("%s", %s)
53 |
54 | function start (_app) {
55 | app = _app;
56 | }`,
57 | body,
58 | route,
59 | encodedState,
60 | ))
61 |
62 | if err != nil {
63 | return "", err
64 | }
65 |
66 | return html, nil
67 | }
68 |
69 | func (runtime *Runtime) SyncRoutes() error {
70 | if runtime.Routes != nil {
71 | return nil
72 | }
73 |
74 | log.Info("Syncing UI and server-side routes...")
75 |
76 | body, err := runtime.SourceCode()
77 | if err != nil {
78 | return err
79 | }
80 |
81 | routes, err := EvalJS(fmt.Sprintf(`
82 | var app;
83 | var routes;
84 | %s
85 |
86 | Object.keys(routes).join(',')
87 |
88 | function start (_app, _routes) {
89 | app = _app;
90 | routes = _routes
91 | }`,
92 | body,
93 | ))
94 |
95 | if err != nil {
96 | return err
97 | }
98 |
99 | runtime.Routes = urlrouter.New()
100 | paths := strings.Split(routes, ",")
101 |
102 | for _, path := range paths {
103 | runtime.Routes.Add(path)
104 | }
105 |
106 | return nil
107 | }
108 |
109 | func (runtime *Runtime) SourceCode() (string, error) {
110 | if runtime.CachedSourceCode != "" {
111 | return runtime.CachedSourceCode, nil
112 | }
113 |
114 | if err := runtime.Browserify(); err != nil {
115 | return "", err
116 | }
117 |
118 | return runtime.CachedSourceCode, nil
119 | }
120 |
121 | func EncodeState(state interface{}) (string, error) {
122 | encoded, err := json.Marshal(state)
123 | if err != nil {
124 | return "", err
125 | }
126 |
127 | return string(encoded), nil
128 | }
129 |
--------------------------------------------------------------------------------
/server/src/ui/state.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | type State struct {
4 | Params map[string]string `json:"params"`
5 | Location struct {
6 | Pathname string `json:"pathname"`
7 | } `json:"location"`
8 | }
9 |
--------------------------------------------------------------------------------
/server/src/ui/ui.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "github.com/azer/logger"
5 | "io/ioutil"
6 | )
7 |
8 | var cache = NewCache()
9 | var log = logger.New("ui")
10 | var runtime *Runtime
11 | var indexhtml string
12 |
13 | func init() {
14 | runtime = &Runtime{}
15 | loadIndexHTML()
16 | }
17 |
18 | func loadIndexHTML() {
19 | indexbyt, err := ioutil.ReadFile("./ui/index.html")
20 | if err != nil {
21 | panic(err)
22 | }
23 |
24 | indexhtml = string(indexbyt)
25 | }
26 |
--------------------------------------------------------------------------------
/server/src/ui/watch-changes.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | "github.com/azer/logger"
5 | . "github.com/azer/on-tree-change"
6 | "os"
7 | "strings"
8 | "time"
9 | )
10 |
11 | const CHANGE_THRESHOLD_MS = 250
12 |
13 | func CheckRuntimeErrors() {
14 | if err := runtime.CheckForErrors(); err == nil {
15 | log.Info("🏁 JavaScript runtime is ready for server-side rendering.")
16 | } else {
17 | log.Error("JavaScript runtime could not be compiled.", logger.Attrs{
18 | "error": err,
19 | })
20 | }
21 | }
22 |
23 | func WatchCodeChanges() {
24 | go CheckRuntimeErrors()
25 |
26 | var lastchange = 0
27 |
28 | OnTreeChange("ui", filter, func(name string) {
29 | now := int(time.Now().UnixNano() / 1000000)
30 |
31 | if now-lastchange < CHANGE_THRESHOLD_MS {
32 | return
33 | }
34 |
35 | lastchange = now
36 |
37 | BuildUI()
38 |
39 | runtime.CleanCache()
40 | CheckRuntimeErrors()
41 |
42 | loadIndexHTML()
43 | })
44 | }
45 |
46 | func filter(name string, info os.FileInfo) bool {
47 | return (info != nil && !info.IsDir()) && !strings.Contains(name, "node_modules/")
48 | }
49 |
--------------------------------------------------------------------------------
/server/ui-routes.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/labstack/echo"
5 | "ui"
6 | )
7 |
8 | func SetUIRoutes(server *echo.Echo) {
9 | // Client-side routes will already be compiled and routed by the server automatically.
10 | // But you may want to do call the UI manually in some cases like passing a custom state.
11 | // Check this ExampleForm function to see how we accomplish that.
12 | // You can try commenting out following line and visiting the same URL again.
13 | // server.Get("/", ExampleForm)
14 |
15 | // If there is a UI route matching the URL path, it'll respond first. Otherwise, server will try to match other endpoints.
16 | server.Use(ui.HTTPHandler)
17 | }
18 |
19 | /*func ExampleForm(c echo.Context) error {
20 | return ui.Render(c, "/", &struct {
21 | ExampleTitle string `json:"title"`
22 | }{"Hello From The Server-side"})
23 | }*/
24 |
--------------------------------------------------------------------------------
/ui/app.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | width: 100%;
3 | padding: 0;
4 | margin: 0;
5 | }
6 |
--------------------------------------------------------------------------------
/ui/app.js:
--------------------------------------------------------------------------------
1 | import choo from 'choo'
2 | import example from './components/example'
3 | import routes from "./routes"
4 |
5 | const app = choo()
6 | app.model(example)
7 | router(routes)
8 |
9 | export default app
10 |
11 | // We need this extra function until Choo lets us get list of the routes easily
12 | function router (routes) {
13 | app.router(route => {
14 | const result = []
15 |
16 | var key
17 | for (key in routes) {
18 | result.push(route(key, routes[key]))
19 | }
20 |
21 | return result
22 | })
23 |
24 | return Object.keys(routes)
25 | }
26 |
--------------------------------------------------------------------------------
/ui/client-side.js:
--------------------------------------------------------------------------------
1 | import app from './app'
2 |
3 | const newTree = app.start()
4 | const oldTree = document.querySelector('main')
5 |
6 | if (oldTree) {
7 | document.body.replaceChild(newTree, oldTree)
8 | } else {
9 | document.body.appendChild(newTree)
10 | }
11 |
--------------------------------------------------------------------------------
/ui/components/example/index.js:
--------------------------------------------------------------------------------
1 | import view from './view'
2 | import state from './state'
3 | import * as reducers from './reducers'
4 |
5 | export default {
6 | // namespace: 'example',
7 | view,
8 | state,
9 | reducers
10 | }
11 |
--------------------------------------------------------------------------------
/ui/components/example/reducers.js:
--------------------------------------------------------------------------------
1 | export function setTitle (title, state) {
2 | return {
3 | title
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/ui/components/example/state.js:
--------------------------------------------------------------------------------
1 | export default {
2 | title: 'hello world'
3 | }
4 |
--------------------------------------------------------------------------------
/ui/components/example/style.css:
--------------------------------------------------------------------------------
1 | .example {
2 | padding: 25px;
3 | color: #333;
4 | text-shadow: 2px 2px pink;
5 | }
6 |
--------------------------------------------------------------------------------
/ui/components/example/view.js:
--------------------------------------------------------------------------------
1 | import choo from "choo"
2 | import html from "choo/html"
3 |
4 | const view = (state, prev, send) => html`
5 |
6 | Title: ${state.title}
7 | send('setTitle', e.target.value)}>
8 |
9 | `
10 |
11 | export default view
12 |
--------------------------------------------------------------------------------
/ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World
6 |
7 |
8 |
9 | %s
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "babel-preset-es2015": "^6.18.0",
4 | "babelify": "^7.3.0",
5 | "browserify": "^13.1.1",
6 | "choo": "^3.3.0",
7 | "exorcist": "^0.4.0",
8 | "watchify": "^3.7.0"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/ui/routes.js:
--------------------------------------------------------------------------------
1 | import example from './components/example'
2 |
3 | // Setup all the routes we need and export the list of the paths.
4 | export default {
5 | '/': example.view
6 | }
7 |
--------------------------------------------------------------------------------
/ui/server-side.js:
--------------------------------------------------------------------------------
1 | import app from './app'
2 | import routes from './routes'
3 |
4 | start(app, routes)
5 |
--------------------------------------------------------------------------------