├── .editorconfig ├── .gitignore ├── .travis.yml ├── app.js ├── assets ├── css │ ├── _global.sss │ └── index.sss └── js │ ├── components │ ├── client-only.js │ ├── static-and-client.js │ └── static-only.js │ └── index.js ├── package.json ├── readme.md ├── views └── index.sgr └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [Makefile] 15 | indent_style = tab 16 | 17 | [{*.md,*.json}] 18 | max_line_length = null 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | public 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 6 5 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const htmlStandards = require('reshape-standard') 2 | const cssStandards = require('spike-css-standards') 3 | const _jsStandards = require('spike-js-standards') 4 | const preactBabel = require('babel-preset-preact') 5 | const renderComponents = require('reshape-preact-components') 6 | 7 | // we need the preact babel plugin to process jsx 8 | // and we need babel-register to require these files and have their jsx 9 | // processed correctly. if you don't use jsx, none of this is needed 10 | const jsStandards = _jsStandards({ appendPresets: [preactBabel] }) 11 | require('babel-register')(jsStandards) 12 | const staticOnly = require('./assets/js/components/static-only') 13 | const staticAndClient = require('./assets/js/components/static-and-client') 14 | 15 | module.exports = { 16 | matchers: { html: '*(**/)*.sgr', css: '*(**/)*.sss' }, 17 | ignore: ['**/layout.sgr', '**/_*', '**/.*', 'readme.md', 'yarn.lock'], 18 | reshape: htmlStandards({ 19 | // here we pair a custom element name to a preact component 20 | appendPlugins: [renderComponents({ 21 | 'static-only': staticOnly, 22 | 'static-and-client': staticAndClient 23 | })] 24 | }), 25 | postcss: cssStandards(), 26 | babel: jsStandards 27 | } 28 | -------------------------------------------------------------------------------- /assets/css/_global.sss: -------------------------------------------------------------------------------- 1 | body 2 | padding: 50px 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif 4 | -------------------------------------------------------------------------------- /assets/css/index.sss: -------------------------------------------------------------------------------- 1 | @import '_global.sss' 2 | 3 | .c 4 | border: 1px solid #ccc 5 | margin-bottom: 25px 6 | padding-top: 9px 7 | 8 | &:before 9 | content: 'Component' 10 | background: #333 11 | padding: 10px 12 | width: 100% 13 | color: white 14 | margin-left: -1px 15 | -------------------------------------------------------------------------------- /assets/js/components/client-only.js: -------------------------------------------------------------------------------- 1 | // can't use es modules as this is required by node as well, and node does not 2 | // support es6 modules natively 3 | const {h, Component} = require('preact') 4 | 5 | module.exports = class ClientOnly extends Component { 6 | constructor () { 7 | super() 8 | this.state.counter = 0 9 | } 10 | 11 | render () { 12 | return ( 13 |
14 |

Hello, I am a component that is only rendered on the client side!

15 |

Props: foo = {this.props.foo}

16 |

17 | 18 | {this.state.counter} 19 |

20 | {this.props.children} 21 |
22 | ) 23 | } 24 | 25 | increment () { 26 | this.setState({ counter: this.state.counter + 1 }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /assets/js/components/static-and-client.js: -------------------------------------------------------------------------------- 1 | // can't use es modules as this is required by node as well, and node does not 2 | // support es6 modules natively 3 | const {h, Component} = require('preact') 4 | 5 | module.exports = class StaticAndClient extends Component { 6 | constructor () { 7 | super() 8 | this.state.counter = 0 9 | } 10 | 11 | render () { 12 | return ( 13 |
14 |

Hello, I am a component that is rendered as static html and upgraded on the client side. Check the source for my html!

15 |

Props: foo = {this.props.foo}

16 |

17 | 18 | {this.state.counter} 19 |

20 | {this.props.children} 21 |
22 | ) 23 | } 24 | 25 | increment () { 26 | this.setState({ counter: this.state.counter + 1 }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /assets/js/components/static-only.js: -------------------------------------------------------------------------------- 1 | // can't use es modules as this is required by node as well, and node does not 2 | // support es6 modules natively 3 | const {h, Component} = require('preact') 4 | 5 | module.exports = class StaticOnly extends Component { 6 | constructor () { 7 | super() 8 | this.state.counter = 0 9 | } 10 | 11 | render () { 12 | return ( 13 |
14 |

Hello, I am a component that is rendered only as static html. I am not interactive since I am just html only.

15 |

Props: foo = {this.props.foo}

16 |

17 | 18 | {this.state.counter} 19 |

20 | {this.props.children} 21 |
22 | ) 23 | } 24 | 25 | increment () { 26 | this.setState({ counter: this.state.counter + 1 }) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /assets/js/index.js: -------------------------------------------------------------------------------- 1 | import 'preact/devtools' 2 | import {h, render} from 'preact' 3 | import {hydrateInitialState} from 'reshape-preact-components' 4 | import ClientOnly from './components/client-only' 5 | import StaticAndClient from './components/static-and-client' 6 | 7 | // first, we'll render the client only component as usual 8 | render( 9 | 10 |

child element

11 |
, 12 | document.querySelector('#client-root') 13 | ) 14 | 15 | // next, we'll render the static and client component 16 | const el = document.querySelector('.static-and-client') 17 | 18 | // the hydrateInitialState function allows us to render the same initial state 19 | // for the component as we had placed in the html, to reduce repetition. 20 | // this gives us back jsx representing the markup we used to initialize the 21 | // component in the html 22 | const vdom = hydrateInitialState(el.dataset.state, { 23 | 'static-and-client': StaticAndClient 24 | }) 25 | 26 | // finally, we take the hydrated element and render it as usual! 27 | // note how we don't need to rewrite the jsx like we did with the client render 28 | render(vdom, el.parentElement, el) 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spike-static-components-example", 3 | "description": "example of how to use static and client rendered preact components with spike", 4 | "version": "0.0.1", 5 | "author": "static-dev", 6 | "ava": { 7 | "verbose": "true" 8 | }, 9 | "bugs": "https://github.com/static-dev/spike-static-components-example/issues", 10 | "dependencies": { 11 | "babel-preset-preact": "^1.1.0", 12 | "babel-register": "^6.24.1", 13 | "reshape-preact-components": "^0.5.1", 14 | "reshape-standard": "^2.1.1", 15 | "spike": "^2.0.2", 16 | "spike-css-standards": "^2.0.1", 17 | "spike-js-standards": "^2.0.2" 18 | }, 19 | "devDependencies": { 20 | "snazzy": "^7.0.0", 21 | "standard": "^10.0.0" 22 | }, 23 | "homepage": "https://github.com/static-dev/spike-static-components-example", 24 | "main": "app.js", 25 | "private": true, 26 | "repository": "https://github.com/static-dev/spike-static-components-example", 27 | "scripts": { 28 | "lint": "standard | snazzy", 29 | "precommit": "npm run lint -s", 30 | "pretest": "npm run lint -s", 31 | "test": "NODE_ENV=test ava" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # spike-static-components-example 2 | 3 | An example of how to use static and client rendered preact components with spike 4 | 5 | ## Setup 6 | 7 | - make sure [node.js](http://nodejs.org) is at version >= `6` 8 | - `npm i spike -g` 9 | - clone this repo down and `cd` into the folder 10 | - run `npm install` 11 | - run `spike watch` or `spike compile` 12 | 13 | ## Architecture 14 | 15 | This is an entirely unique architecture pattern, to the best of our knowledge nothing else similar to this exists right now. So take a moment to let it sink in. As a simple guide: 16 | 17 | - Preact components are in `assets/js/components` 18 | - Client render is in `assets/js/index.js` 19 | - Static implementation is in `views/index.sgr` 20 | - "Server render" configuration is in `app.js` 21 | 22 | Happy to field any questions about this on twitter at [@jescalan](https://twitter.com/jescalan), or better yet in [the static-dev slack](https://static-dev-slack.herokuapp.com/) :grin: 23 | -------------------------------------------------------------------------------- /views/index.sgr: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Spike Static Components Example 5 | link(rel='stylesheet' href='/css/index.css') 6 | body 7 | p React devtools work on this page, feel free to open them up and look around! 8 | 9 | static-only(foo='bar') 10 | p child element 11 | static-and-client(foo='bar') 12 | p child element 13 | #client-root 14 | 15 | script(src='/js/main.js' defer) 16 | --------------------------------------------------------------------------------