├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── art ├── bae.png └── perf-checklist.png ├── benchmark ├── README.md ├── components │ └── recursiveDiv.js ├── package.json ├── pages │ └── home.js └── test.js ├── package.json ├── packages ├── bae │ ├── dev │ │ ├── babel.json │ │ ├── server.js │ │ └── webpack.config.js │ ├── index.js │ ├── package.json │ ├── prod │ │ ├── babel.json │ │ ├── server.js │ │ └── webpack.config.js │ └── utils │ │ ├── page-renderer.js │ │ ├── pages.js │ │ └── template.js └── examples │ ├── regithub │ ├── .gitignore │ ├── README.md │ ├── api │ │ ├── repos.js │ │ └── user.js │ ├── components │ │ ├── containers │ │ │ └── repositoriesContainer.js │ │ └── presentation │ │ │ ├── common │ │ │ ├── link.js │ │ │ ├── logo.js │ │ │ └── nav.js │ │ │ ├── library │ │ │ ├── anchor.js │ │ │ ├── blink.js │ │ │ ├── button.js │ │ │ ├── card.js │ │ │ ├── clear.js │ │ │ ├── input.js │ │ │ └── star.js │ │ │ └── profile │ │ │ ├── avatar.js │ │ │ ├── description.js │ │ │ ├── profile-input.js │ │ │ ├── profile.js │ │ │ └── repositories.js │ ├── package.json │ ├── pages │ │ ├── home.js │ │ └── profile.js │ └── static │ │ └── logo.png │ └── simple │ ├── README.md │ ├── components │ └── center.js │ ├── package.json │ ├── pages │ ├── about.js │ └── home.js │ └── static │ └── logo.png └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": ["react"], 4 | "ecmaFeatures": { 5 | "modules": true, 6 | "jsx": true, 7 | "experimentalObjectRestSpread": true 8 | }, 9 | "env": { 10 | "amd": true, 11 | "browser": true, 12 | "es6": true, 13 | "jquery": true, 14 | "node": true 15 | }, 16 | "rules": { 17 | "comma-dangle": [2, "never"], 18 | "no-cond-assign": 2, 19 | "no-debugger": 2, 20 | "no-dupe-args": 2, 21 | "no-dupe-keys": 2, 22 | "no-duplicate-case": 2, 23 | "no-empty": 2, 24 | "no-empty-function": 2, 25 | "no-func-assign": 2, 26 | "no-sparse-arrays": 2, 27 | "eqeqeq": 2, 28 | "no-eval": 2, 29 | "no-magic-numbers": 0, 30 | "no-lone-blocks": 2, 31 | "no-redeclare": 2, 32 | "no-unused-expressions": 0, 33 | "no-useless-concat": 2, 34 | "no-unused-vars": 2, 35 | "no-use-before-define": 2, 36 | "no-lonely-if": 2, 37 | "no-mixed-spaces-and-tabs": 2, 38 | "new-cap": 2, 39 | "indent": [2, 2], 40 | "vars-on-top": 2, 41 | "semi": [2, "never"], 42 | "react/jsx-uses-vars": 2, 43 | "react/jsx-uses-react": 2 44 | }, 45 | "parserOptions": { 46 | "sourceType": "module" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | .build 39 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" 4 | - "6" 5 | cache: 6 | directories: 7 | - node_modules 8 | notifications: 9 | email: false 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Siddharth Kshetrapal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | react made easy 5 |

6 |

7 | 8 |   9 | 10 | [![Build Status](https://travis-ci.org/siddharthkp/bae.svg?branch=master)](https://travis-ci.org/siddharthkp/bae) 11 | 12 |   13 | 14 | ### minimal setup 15 | ``` 16 | npm install bae --save 17 | ``` 18 | 19 | 20 | Add these 2 lines in your `package.json` 21 | 22 | ```json 23 | "scripts": { 24 | "dev": "bae dev", 25 | "start": "bae" 26 | } 27 | ``` 28 | 29 | Start the dev server with `npm run dev`. You just setup server rendering with hot module replacement and hot reload! 30 | 31 |   32 | 33 | ### pages 34 | 35 | Make pages like it's the 90s. 36 |   37 | - pages are routes: `pages/about` renders `/about` of your website 38 | 39 | - pages are rendered on the server 40 | 41 | - pages are `streamed` to the browser for quick `time-to-first-byte` 42 | 43 | - built in code splitting, each page gets it's own _`page.js`_ 44 | 45 | - code shared between pages is served as `common.js` for long term caching 46 | 47 | - `pages/home.js` renders the homepage `/` 48 | 49 | - [why is this important?](https://rauchg.com/2014/7-principles-of-rich-web-applications#server-rendered-pages-are-not-optional) 50 | 51 |   52 | 53 | ```js 54 | import React from 'react' 55 | 56 | export default class extends React.Component { 57 | constructor (props) { 58 | super(props) 59 | this.state = {message: 'hello'} // rendered on the server 60 | } 61 | componentDidMount () { 62 | this.setState({message: 'hello world'}) // updated on the browser 63 | } 64 | render () { 65 | return
{this.state.message}
66 | } 67 | } 68 | 69 | ``` 70 | 71 |   72 | 73 | ### asyncComponentWillMount 74 | 75 | React has a lifecycle method that get's called on the server `componentWillMount` that can be used to set data for server rendering. But, it [does not support](https://github.com/facebook/react/issues/1739) asynchronous data fetching before rendering the component. 76 | 77 | bae introduces a new lifecycle method **to pages** that runs only on the server. 78 | 79 | ```js 80 | import React from 'react' 81 | 82 | export default class extends React.Component { 83 | constructor (props) { 84 | super(props) 85 | this.state = {username: 'siddharthkp'} 86 | } 87 | asyncComponentWillMount () { 88 | /* 89 | Return a promise. 90 | It will get resolved on the server and passed as props to the component. 91 | */ 92 | return axios.get(`https://api.github.com/users/${this.state.username}`) 93 | } 94 | render () { 95 | return
{this.props.bio}
96 | } 97 | } 98 | 99 | ``` 100 | 101 |   102 | 103 | ### components 104 | 105 | the usual, nothing special here. 106 | 107 |   108 | 109 | ### styling 110 | 111 | comes with [styled-components](https://github.com/styled-components/styled-components) which gets rendered on the server. 112 | 113 |   114 | 115 | ### static assets 116 | 117 | keep your images, fonts, etc. in a directory named `static` 118 | 119 |   120 | 121 | ### production 122 | 123 | `npm start` will compile, optimize and serve your app. 124 | 125 |   126 | 127 | ### example 128 | 129 | Check out the [example applications](https://github.com/siddharthkp/bae/tree/master/examples) to see how simple this is. 130 | 131 |   132 | 133 | ### like it? 134 | 135 | :star: this repo 136 | 137 |   138 | 139 | ### todo 140 | 141 | - init by default 142 | - easy api for lazy loading components 143 | - server worker support 144 | - make first build faster 145 | 146 |   147 | 148 | ### license 149 | 150 |   151 | 152 | MIT © [siddharthkp](https://github.com/siddharthkp) 153 | -------------------------------------------------------------------------------- /art/bae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthkp/bae/2efbab72415a6004ee1e9ccddcb370fa690069a3/art/bae.png -------------------------------------------------------------------------------- /art/perf-checklist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthkp/bae/2efbab72415a6004ee1e9ccddcb370fa690069a3/art/perf-checklist.png -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | benchmarks for bae on prod 5 |

6 |

7 | 8 |   9 | 10 | Benchmarks for rendering a heavily nested DOM tree on production. 11 | 12 | ``` 13 | npm install 14 | 15 | npm t 16 | ``` 17 | 18 |   19 | -------------------------------------------------------------------------------- /benchmark/components/recursiveDiv.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const RecursiveDiv = ({ length, width }) => { 4 | if (length <= 0) return I am a leaf. 5 | 6 | let children = [] 7 | for (let i = 0; i < width; i++) { 8 | children.push() 9 | } 10 | return
{children}
11 | } 12 | 13 | export default RecursiveDiv 14 | -------------------------------------------------------------------------------- /benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bae-example", 3 | "scripts": { 4 | "app": "node ../index.js", 5 | "debug": "node ../index.js dev", 6 | "start": "node test.js" 7 | }, 8 | "author": "siddharthkp", 9 | "license": "MIT", 10 | "dependencies": { 11 | "benchmark": "2.1.3", 12 | "cli-table2": "0.2.0", 13 | "colors": "1.1.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /benchmark/pages/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import RecursiveDiv from '../components/recursiveDiv' 3 | 4 | export default () => { 5 | let length = 6, width = 4 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /benchmark/test.js: -------------------------------------------------------------------------------- 1 | const spawnSync = require('child_process').spawnSync 2 | const Table = require('cli-table2') 3 | const { yellow, red } = require('colors/safe') 4 | 5 | console.log(yellow('Running benchmarks...')) 6 | 7 | const options = '-o /dev/null -w %{time_starttransfer}:%{time_total} -s localhost:3000'.split( 8 | ' ' 9 | ) 10 | 11 | const attempts = 10 12 | 13 | const results = { 14 | ttfb: [], 15 | total: [] 16 | } 17 | 18 | for (let i = 0; i < attempts + 1; i++) { 19 | // Make a request to localhost:3000 20 | const result = spawnSync('curl', options, { encoding: 'utf8' }) 21 | 22 | // The first request is always slower because of warmup, ignore this request 23 | if (i === 0) continue 24 | 25 | // The output is time to first bite : total time taken 26 | let ttfb = result.stdout.split(':')[0] 27 | let total = result.stdout.split(':')[1] 28 | results.ttfb.push(parseInt(parseFloat(ttfb) * 1000, 10)) 29 | results.total.push(parseInt(parseFloat(total) * 1000, 10)) 30 | } 31 | 32 | const average = elements => { 33 | const sum = elements.reduce((a, b) => parseFloat(a) + parseFloat(b)) 34 | return parseInt(sum / elements.length, 10) 35 | } 36 | 37 | let table = new Table({ head: ['run', 'ttfb (ms)', 'total (ms)'] }) 38 | for (let i = 0; i < attempts; i++) 39 | table.push([i + 1, results.ttfb[i], results.total[i]]) 40 | table.push( 41 | ['average', average(results.ttfb), average(results.total)].map(text => 42 | yellow(text) 43 | ) 44 | ) 45 | 46 | if (average(results.ttfb)) console.log(table.toString()) 47 | else 48 | console.log( 49 | red( 50 | ` 51 | Seems like the sample application is not running. 52 | 53 | Run it in parallel 'npm run app' 54 | ` 55 | ) 56 | ) 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bae", 3 | "version": "1.4.0", 4 | "description": "react made easy", 5 | "homepage": "https://github.com/siddharthkp/bae#readme", 6 | "repository": "siddharthkp/bae", 7 | "scripts": { 8 | "test": "eslint .", 9 | "benchmarks": "node benchmark/test.js", 10 | "precommit": "lint-staged" 11 | }, 12 | "private": true, 13 | "workspaces": [ 14 | "packages/bae", 15 | "packages/examples/simple" 16 | ], 17 | "keywords": [ 18 | "react", 19 | "easy", 20 | "server rendering", 21 | "ssr", 22 | "code splitting", 23 | "hot module replacement", 24 | "bae" 25 | ], 26 | "files": [ 27 | "dev", 28 | "prod", 29 | "utils", 30 | "index.js" 31 | ], 32 | "author": "siddharthkp", 33 | "license": "MIT", 34 | "dependencies": { 35 | "babel-cli": "6.23.0", 36 | "babel-loader": "6.3.2", 37 | "babel-plugin-transform-object-rest-spread": "6.23.0", 38 | "babel-plugin-transform-react-constant-elements": "6.9.1", 39 | "babel-plugin-transform-react-inline-elements": "6.8.0", 40 | "babel-preset-es2015": "6.22.0", 41 | "babel-preset-react": "6.23.0", 42 | "chokidar": "1.6.1", 43 | "clear-require": "2.0.0", 44 | "compression": "1.6.2", 45 | "dependency-tree": "5.9.0", 46 | "eslint-plugin-import": "2.2.0", 47 | "express": "4.14.1", 48 | "fs-extra": "1.0.0", 49 | "prettycli": "1.0.1", 50 | "rapscallion": "2.1.5", 51 | "react": "15.4.2", 52 | "react-dom": "15.4.2", 53 | "react-hot-loader": "3.0.0-beta.6", 54 | "styled-components": "2.0.0-8", 55 | "webpack": "2.2.1", 56 | "webpack-dev-server": "2.3.0", 57 | "webpack-hot-middleware": "2.17.1", 58 | "webpack-node-externals": "1.5.4" 59 | }, 60 | "devDependencies": { 61 | "babel-eslint": "7.1.1", 62 | "eslint": "3.16.0", 63 | "eslint-config-airbnb-base": "11.1.0", 64 | "eslint-config-react": "1.1.7", 65 | "eslint-config-standard": "6.2.1", 66 | "eslint-plugin-promise": "3.4.0", 67 | "eslint-plugin-react": "6.10.0", 68 | "eslint-plugin-standard": "2.0.1", 69 | "husky": "0.13.3", 70 | "lint-staged": "3.4.0", 71 | "prettier": "1.2.2" 72 | }, 73 | "engines": { 74 | "node": ">= 6.0.0", 75 | "npm": ">= 3.0.0" 76 | }, 77 | "bugs": { 78 | "url": "https://github.com/siddharthkp/bae/issues" 79 | }, 80 | "lint-staged": { 81 | "*.js": [ 82 | "prettier --write --single-quote --no-semi", 83 | "git add" 84 | ] 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /packages/bae/dev/babel.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["es2015", {"modules": false}], "react"], 3 | "plugins": ["react-hot-loader/babel", "rapscallion/babel-plugin-client"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/bae/dev/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import React from 'react' 3 | import { render } from 'rapscallion' 4 | import fs from 'fs' 5 | import clearRequire from 'clear-require' 6 | import { loading, info } from 'prettycli' 7 | import webpack from 'webpack' 8 | import webpackDevMiddleware from 'webpack-dev-middleware' 9 | import webpackHotMiddleware from 'webpack-hot-middleware' 10 | import { resolve } from 'path' 11 | import styleSheet from 'styled-components/lib/models/StyleSheet' 12 | import chokidar from 'chokidar' 13 | import dependencyTree from 'dependency-tree' 14 | 15 | import template from '../utils/template' 16 | import entryPoints from '../utils/pages' 17 | import config from './webpack.config' 18 | 19 | /* generate dependency tree */ 20 | let tree = {} 21 | const generateTree = files => 22 | files.map( 23 | file => 24 | (tree[file] = dependencyTree.toList({ 25 | filename: resolve(`./pages/${file}`), 26 | directory: '/' 27 | })) 28 | ) 29 | 30 | /* generate pages cache */ 31 | let pages = {} 32 | const getPages = () => { 33 | const files = fs.readdirSync('./pages') 34 | files 35 | .filter(file => file.endsWith('.js')) 36 | .filter(file => !file.endsWith('.test.js')) 37 | .map( 38 | file => 39 | (pages[file.replace('.js', '')] = require(resolve(`./pages/${file}`))) 40 | ) 41 | generateTree(files) 42 | } 43 | 44 | loading('PAGES', 'Really Defining routes...') 45 | getPages() 46 | 47 | /* uncache dependency tree on change */ 48 | const uncache = path => { 49 | const roots = Object.keys(tree) 50 | roots.map(root => { 51 | /* find matching trees */ 52 | const index = tree[root].indexOf(resolve(path)) 53 | if (index !== -1) { 54 | /* get components up the tree */ 55 | const stale = tree[root].slice(index) 56 | /* remove from require cache */ 57 | stale.map(component => clearRequire(resolve(component))) 58 | } 59 | }) 60 | getPages() // generate page cache again 61 | } 62 | 63 | const getInstance = config => { 64 | /* create an express instance */ 65 | const instance = express() 66 | 67 | /* setup hot module replacement */ 68 | 69 | const compiler = webpack(config) 70 | 71 | const devMiddleware = webpackDevMiddleware(compiler, { 72 | noInfo: true, 73 | publicPath: config.output.publicPath 74 | }) 75 | instance.use(devMiddleware) 76 | 77 | instance.use(webpackHotMiddleware(compiler)) 78 | 79 | /* handle static files */ 80 | instance.use('/static', express.static('static')) 81 | 82 | /* handle build files */ 83 | instance.use('/build', express.static('.build/dist')) 84 | 85 | /* handle routes */ 86 | instance.get('*', (req, res) => { 87 | let route = req.path.replace('/', '') 88 | 89 | const request = { 90 | path: req.path, 91 | query: req.query 92 | } 93 | 94 | /* default route */ 95 | if (route === '') route = 'home' 96 | 97 | /* get page from cached pages */ 98 | if (pages[route]) { 99 | const Page = pages[route].default 100 | 101 | const renderPage = (asyncProps = {}) => { 102 | const props = Object.assign({}, { req: request }, { ...asyncProps }) 103 | 104 | /* get rendered component from ReactDOM */ 105 | const component = render() 106 | 107 | /* get styles */ 108 | let styles 109 | try { 110 | /* This breaks when there are no styled-components in your code */ 111 | styles = styleSheet.rules().map(rule => rule.cssText).join('\n') 112 | } catch (error) { 113 | styles = '' 114 | } 115 | 116 | /* render html page */ 117 | const response = template(component, styles, props, route) 118 | response.toStream().pipe(res) 119 | } 120 | 121 | /* 122 | If component has asyncComponentWillMount, 123 | fetch data and return it as props 124 | */ 125 | if (Page.prototype.asyncComponentWillMount) { 126 | const pageInstance = new Page({ req: request }) 127 | pageInstance 128 | .asyncComponentWillMount() 129 | .then(asyncProps => renderPage(asyncProps)) 130 | } else renderPage() 131 | } else res.status(404).end() 132 | }) 133 | 134 | /* start the instance */ 135 | const server = instance.listen(3000, () => { 136 | info('SERVER', 'Listening on 3000') 137 | loading('WATCH', 'Watching for changes...') 138 | }) 139 | 140 | /* close instance */ 141 | instance.close = callback => { 142 | /* close middleware */ 143 | devMiddleware.close(() => { 144 | /* close http server */ 145 | server.close() 146 | if (callback) callback() 147 | }) 148 | } 149 | 150 | return instance 151 | } 152 | 153 | /* create server */ 154 | let server = getInstance(config) 155 | 156 | /* replace server instance */ 157 | const replaceInstance = config => { 158 | /* close ongoing connections before replacing instance */ 159 | server.close(() => { 160 | server = {} 161 | server = getInstance(config) 162 | }) 163 | } 164 | 165 | /* define watchers */ 166 | 167 | chokidar 168 | .watch('**/*.js', { ignored: ['node_modules'] }) 169 | .on('change', uncache) 170 | .on('unlink', uncache) 171 | 172 | const pageWatcher = chokidar.watch('pages/*.js').on('ready', () => { 173 | pageWatcher.on('add', () => { 174 | /* regenerate pages cache */ 175 | getPages() 176 | 177 | /* get updated entry points */ 178 | config.entry = entryPoints({ hot: true }) 179 | 180 | /* replace server instance */ 181 | replaceInstance(config) 182 | }) 183 | }) 184 | -------------------------------------------------------------------------------- /packages/bae/dev/webpack.config.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack' 2 | import { resolve } from 'path' 3 | import babelOptions from './babel.json' 4 | import pages from '../utils/pages' 5 | 6 | module.exports = { 7 | cache: true, 8 | entry: pages({ hot: true }), 9 | output: { 10 | filename: '[name].js', 11 | path: resolve(__dirname, '.build/dist'), 12 | publicPath: '/build' 13 | }, 14 | module: { 15 | rules: [{ test: /\.js?$/, loader: 'babel-loader', options: babelOptions }] 16 | }, 17 | devtool: 'cheap-module-source-map', 18 | devServer: { hot: true }, 19 | plugins: [ 20 | new webpack.optimize.CommonsChunkPlugin({ name: 'common' }), 21 | new webpack.HotModuleReplacementPlugin(), 22 | new webpack.NamedModulesPlugin(), 23 | new webpack.IgnorePlugin(/react-native/), 24 | new webpack.IgnorePlugin(/styled-components\/native/) 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/bae/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const dev = process.argv[2] === 'dev' 4 | if (!dev) process.env.NODE_ENV = 'production' 5 | 6 | require('babel-core/register')({ 7 | ignore: /node_modules\/(?!bae)/, 8 | presets: ['es2015', 'react'], 9 | plugins: [ 10 | 'rapscallion/babel-plugin-server', 11 | 'transform-react-constant-elements', 12 | 'transform-react-inline-elements', 13 | 'transform-object-rest-spread' 14 | ] 15 | }) 16 | 17 | if (dev) require('./dev/server') 18 | else require('./prod/server') 19 | -------------------------------------------------------------------------------- /packages/bae/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bae", 3 | "version": "2.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "bin": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "siddharthkp", 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /packages/bae/prod/babel.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["es2015", {"modules": false}], "react"], 3 | "plugins": [ 4 | "transform-react-constant-elements", 5 | "transform-react-inline-elements" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/bae/prod/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import React from 'react' 3 | import { render } from 'rapscallion' 4 | import fs from 'fs' 5 | import compression from 'compression' 6 | import webpack from 'webpack' 7 | import { loading, info } from 'prettycli' 8 | import { resolve } from 'path' 9 | import styleSheet from 'styled-components/lib/models/StyleSheet' 10 | 11 | import template from '../utils/template' 12 | import config from './webpack.config' 13 | 14 | loading('BUILD', 'Building...') 15 | 16 | /* get webpack compiler */ 17 | const compiler = webpack(config) 18 | 19 | /* compile! */ 20 | compiler.run(() => { 21 | loading('PAGES', 'Defining routes...') 22 | 23 | /* generate pages cache */ 24 | let pages = {} 25 | const files = fs.readdirSync('./pages') 26 | files 27 | .filter(file => file.endsWith('.js')) 28 | .filter(file => !file.endsWith('.test.js')) 29 | .map( 30 | file => 31 | (pages[file.replace('.js', '')] = require(resolve(`./pages/${file}`))) 32 | ) 33 | 34 | /* create an express server */ 35 | const server = express() 36 | 37 | /* gzip */ 38 | server.use(compression()) 39 | 40 | /* handle static files */ 41 | server.use('/static', express.static('static')) 42 | 43 | /* handle build files */ 44 | server.use('/build', express.static('.build/dist')) 45 | 46 | server.get('*', (req, res) => { 47 | let route = req.path.replace('/', '') 48 | 49 | const request = { 50 | path: req.path, 51 | query: req.query 52 | } 53 | 54 | /* default route */ 55 | if (route === '') route = 'home' 56 | 57 | /* get page from cached pages */ 58 | if (pages[route]) { 59 | const Page = pages[route].default 60 | 61 | const renderPage = (asyncProps = {}) => { 62 | const props = Object.assign({}, { req: request }, { ...asyncProps }) 63 | 64 | /* get rendered component from ReactDOM */ 65 | const component = render() 66 | 67 | /* get styles */ 68 | let styles 69 | try { 70 | /* This breaks when there are no styled-components in your code */ 71 | styles = styleSheet.rules().map(rule => rule.cssText).join('\n') 72 | } catch (error) { 73 | styles = '' 74 | } 75 | 76 | /* render html page */ 77 | const response = template(component, styles, props, route) 78 | response.toStream().pipe(res) 79 | } 80 | 81 | /* 82 | If component has asyncComponentWillMount, 83 | fetch data and return it as props 84 | */ 85 | if (Page.prototype.asyncComponentWillMount) { 86 | const pageInstance = new Page({ req: request }) 87 | pageInstance 88 | .asyncComponentWillMount() 89 | .then(asyncProps => renderPage(asyncProps)) 90 | } else renderPage() 91 | } else res.status(404).end() 92 | }) 93 | 94 | /* start the server */ 95 | server.listen(3000, () => info('SERVER', 'Listening on 3000')) 96 | }) 97 | -------------------------------------------------------------------------------- /packages/bae/prod/webpack.config.js: -------------------------------------------------------------------------------- 1 | import webpack from 'webpack' 2 | import babelOptions from './babel.json' 3 | import pages from '../utils/pages' 4 | 5 | module.exports = { 6 | cache: true, 7 | entry: pages({ hot: false }), 8 | output: { filename: '[name].js', path: '.build/dist' }, 9 | module: { 10 | rules: [{ test: /\.js?$/, loader: 'babel-loader', options: babelOptions }] 11 | }, 12 | plugins: [ 13 | new webpack.DefinePlugin({ 14 | 'process.env': { NODE_ENV: JSON.stringify('production') } 15 | }), 16 | new webpack.optimize.UglifyJsPlugin({ minimize: true }), 17 | new webpack.optimize.AggressiveMergingPlugin(), 18 | new webpack.optimize.CommonsChunkPlugin({ name: 'common' }), 19 | new webpack.IgnorePlugin(/react-native/), 20 | new webpack.IgnorePlugin(/styled-components\/native/) 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /packages/bae/utils/page-renderer.js: -------------------------------------------------------------------------------- 1 | module.exports = path => 2 | ` 3 | import React from 'react' 4 | import {render} from 'react-dom' 5 | import Page from '${path}' 6 | 7 | render( 8 | , 9 | document.getElementById('root') 10 | ); 11 | 12 | if (module.hot) { 13 | module.hot.accept('${path}', () => { 14 | render(, document.getElementById('root')) 15 | }); 16 | } 17 | ` 18 | -------------------------------------------------------------------------------- /packages/bae/utils/pages.js: -------------------------------------------------------------------------------- 1 | import glob from 'glob' 2 | import { resolve, basename, extname } from 'path' 3 | import fs from 'fs-extra' 4 | import pageRenderer from './page-renderer' 5 | 6 | module.exports = ({ hot }) => { 7 | /* create .build and .build/pages */ 8 | if (!fs.existsSync('./.build')) fs.mkdirSync('./.build') 9 | if (!fs.existsSync('./.build/pages')) fs.mkdirSync('./.build/pages') 10 | fs.emptyDirSync('./.build/pages') 11 | 12 | /* drop renderable pages */ 13 | glob 14 | .sync('./pages/*.js') 15 | .map(file => 16 | fs.writeFileSync(`./.build/${file}`, pageRenderer(resolve(file)), 'utf8') 17 | ) 18 | 19 | /* map of pages for entry point */ 20 | const pages = {} 21 | glob 22 | .sync('./.build/pages/*.js') 23 | .map(file => (pages[basename(file, extname(file))] = file)) 24 | 25 | if (hot) { 26 | /* Add webpack-hot-middleware client for each entry point */ 27 | const keys = Object.keys(pages) 28 | keys.map( 29 | key => (pages[key] = [pages[key], 'webpack-hot-middleware/client']) 30 | ) 31 | } 32 | 33 | return pages 34 | } 35 | -------------------------------------------------------------------------------- /packages/bae/utils/template.js: -------------------------------------------------------------------------------- 1 | import { template } from 'rapscallion' 2 | 3 | module.exports = (body, styles, props, page) => template` 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
${body}
13 | 14 | 15 | 16 | 17 | ` 18 | -------------------------------------------------------------------------------- /packages/examples/regithub/.gitignore: -------------------------------------------------------------------------------- 1 | G# Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | .DS_Store 40 | .build 41 | config/config.json 42 | -------------------------------------------------------------------------------- /packages/examples/regithub/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | example for using bae 5 |

6 |

7 | 8 |   9 | 10 | ### minimal setup 11 | 12 | ``` 13 | npm install bae --save 14 | ``` 15 | 16 |   17 | 18 | ### folder structure 19 | 20 | . 21 | ├── pages (top level routes) 22 | ├── components (organize them as you like) 23 | └── static (images, fonts, that kind of stuff) 24 | 25 |   26 | 27 | ### dev mode 28 | 29 | ``` 30 | npm run dev 31 | 32 | > Running on http://localhost:3000 33 | ``` 34 | 35 |   36 | 37 | ### ready for production? 38 | 39 | ``` 40 | npm start 41 | ``` 42 | 43 | that's all you need 44 | 45 |   46 | -------------------------------------------------------------------------------- /packages/examples/regithub/api/repos.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const get = username => { 4 | return axios 5 | .get(`https://api.github.com/users/${username}/repos?sort=updated`) 6 | .then(response => { 7 | let repos = response.data.map(repo => { 8 | return { 9 | name: repo.name, 10 | stars: repo.stargazers_count, 11 | description: repo.description, 12 | url: repo.html_url 13 | } 14 | }) 15 | repos = repos.sort((a, b) => b.stars - a.stars).slice(0, 5) 16 | return repos 17 | }) 18 | .catch(error => { 19 | /* Error handling */ 20 | return { 21 | error: error.response.data.message 22 | } 23 | }) 24 | } 25 | 26 | export default get 27 | -------------------------------------------------------------------------------- /packages/examples/regithub/api/user.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const get = username => { 4 | return axios 5 | .get(`https://api.github.com/users/${username}`) 6 | .then(response => { 7 | return { 8 | username: response.data.login, 9 | name: response.data.name, 10 | photo: response.data.avatar_url, 11 | bio: response.data.bio || 'no description', 12 | url: response.data.html_url 13 | } 14 | }) 15 | .catch(error => { 16 | return { error: error.response.data.message } 17 | }) 18 | } 19 | 20 | export default get 21 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/containers/repositoriesContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import Repositories from '../presentation/profile/repositories' 4 | import getRepos from '../../api/repos' 5 | 6 | export default class extends React.Component { 7 | constructor(props) { 8 | super(props) 9 | /* 10 | Set initial state. 11 | Initilising with 5 placeholders 12 | */ 13 | this.state = { 14 | repos: [{}, {}, {}, {}, {}] 15 | } 16 | } 17 | componentDidMount() { 18 | /* Fetch data and set state */ 19 | getRepos(this.props.username) 20 | .then(repos => this.setState({ repos })) 21 | .catch(error => this.setState(error)) 22 | } 23 | render() { 24 | /* Passes data to presentation component */ 25 | return 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/common/link.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import A from '../library/anchor' 5 | import Blink from '../library/blink' 6 | 7 | const Loading = styled(A)` 8 | background: #2CC1ED; 9 | width: 40%; 10 | height: 22px; 11 | margin-top: 10px; 12 | &::before { 13 | content: 'x'; 14 | } 15 | animation: ${Blink} 2s linear infinite; 16 | ` 17 | 18 | export default props => { 19 | if (props.url) return {props.children} 20 | else return 21 | } 22 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/common/logo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | const Logo = () => 3 | 4 | export default Logo 5 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/common/nav.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled, { injectGlobal } from 'styled-components' 3 | import Logo from '../common/logo' 4 | import Helmet from 'react-helmet' 5 | 6 | injectGlobal` 7 | body { 8 | margin: 0; 9 | padding: 0; 10 | background: #EEE; 11 | color: #FFF; 12 | font-family: 'Nunito', sans-serif; 13 | } 14 | ` 15 | 16 | const NavBar = styled.div` 17 | height: 30px; 18 | padding: 10px; 19 | background: #FFF; 20 | border-bottom: 1px solid #DDD; 21 | text-align: center; 22 | > img { 23 | height: 30px; 24 | } 25 | ` 26 | 27 | const Nav = () => ( 28 | 29 | 38 | 39 | 40 | ) 41 | 42 | export default Nav 43 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/library/anchor.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const A = styled.a` 4 | text-decoration: none; 5 | color: #2CC1ED; 6 | display: inline-block; 7 | width: '20px'; 8 | ` 9 | 10 | export default A 11 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/library/blink.js: -------------------------------------------------------------------------------- 1 | import { keyframes } from 'styled-components' 2 | 3 | const Blink = keyframes` 4 | 0% {opacity: 0.1} 5 | 50% {opacity: 0.3} 6 | 100% {opacity: 0.1} 7 | ` 8 | 9 | export default Blink 10 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/library/button.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Button = styled.button` 4 | background: #2CC1ED; 5 | color: #FFF; 6 | border: 1px solid #1d99bd; 7 | border-radius: 2px; 8 | padding: 10px; 9 | width: 100%; 10 | outline: none; 11 | font-size: 16px; 12 | ` 13 | 14 | export default Button 15 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/library/card.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Card = styled.div` 4 | background: #FFF; 5 | border: 1px solid #DDD; 6 | border-radius: 2px; 7 | padding: 10px; 8 | ` 9 | 10 | export default Card 11 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/library/clear.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Clear = styled.div` 4 | clear: both; 5 | ` 6 | 7 | export default Clear 8 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/library/input.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Input = styled.input` 4 | background: #FFF; 5 | border: 1px solid #DDD; 6 | border-radius: 2px; 7 | padding: 10px; 8 | width: calc(100% - 20px); 9 | outline: none; 10 | font-size: 16px; 11 | 12 | &:hover, &:focus { 13 | border-color: #2CC1ED; 14 | } 15 | ` 16 | 17 | export default Input 18 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/library/star.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Star = styled.span` 4 | margin: 10px; 5 | &::after { 6 | content: ' \\2605'; 7 | color: gold; 8 | } 9 | ` 10 | 11 | export default Star 12 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/profile/avatar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Avatar = styled.span` 5 | display: inline-block; 6 | height: 100px; 7 | width: 100px; 8 | border-radius: 50%; 9 | border: 5px solid #EEE; 10 | background-color: #EEE; 11 | ${props => (props.src ? `background-image: url(${props.src})` : '')}; 12 | background-size: cover; 13 | ` 14 | 15 | export default props => 16 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/profile/description.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | const Description = styled.div` 4 | color: #999; 5 | font-size: 12px; 6 | ` 7 | 8 | export default props => {props.content} 9 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/profile/profile-input.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Card from '../library/card' 4 | import Input from '../library/input' 5 | import A from '../library/anchor' 6 | import Button from '../library/button' 7 | import Logo from '../common/logo' 8 | 9 | const ProfileInput = styled(Card)` 10 | text-align: center; 11 | box-sizing: border-box; 12 | margin: 70px auto 0; 13 | padding: 30px 50px; 14 | width: 500px; 15 | min-height: 100px; 16 | 17 | @media (max-width: 600px) { 18 | width: 90%; 19 | } 20 | 21 | > input { 22 | margin-top: 30px; 23 | } 24 | 25 | > a { 26 | width: 100%; 27 | margin-top: 10px; 28 | } 29 | ` 30 | 31 | export default class extends React.Component { 32 | constructor(props) { 33 | super(props) 34 | /* Set initial state */ 35 | this.state = { username: '' } 36 | } 37 | 38 | /* 39 | Compose presentation components inside our 40 | ProfileInput components 41 | */ 42 | render() { 43 | return ( 44 | 45 | 46 | 53 | 54 | 55 | 56 | 57 | ) 58 | } 59 | /* Change state on change */ 60 | onChange(event) { 61 | this.setState({ username: event.target.value }) 62 | } 63 | /* Trigger submit on enter key */ 64 | onKeyUp(event) { 65 | if (event.which === 13) 66 | location.href = `/profile?user=${this.state.username}` 67 | } 68 | /* Pick value from input on focus */ 69 | onFocus(event) { 70 | this.setState({ username: event.target.value }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/profile/profile.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Card from '../library/card' 4 | import Link from '../common/link' 5 | 6 | import Avatar from './avatar' 7 | import Description from './description' 8 | 9 | const Profile = styled(Card)` 10 | text-align: center; 11 | box-sizing: border-box; 12 | margin: 50px auto 0; 13 | width: 500px; 14 | min-height: 180px; 15 | 16 | @media (max-width: 600px) { 17 | width: 90%; 18 | } 19 | ` 20 | 21 | export default props => ( 22 | 23 | 24 |
25 | {props.name} 26 |
27 | 28 |
29 | ) 30 | -------------------------------------------------------------------------------- /packages/examples/regithub/components/presentation/profile/repositories.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Card from '../library/card' 4 | import Star from '../library/star' 5 | import Clear from '../library/clear' 6 | import Link from '../common/link' 7 | 8 | const RepoList = styled(Card)` 9 | margin: 10px auto; 10 | box-sizing: border-box; 11 | width: 500px; 12 | min-height: 215px; 13 | 14 | @media (max-width: 600px) { 15 | width: 90%; 16 | } 17 | ` 18 | 19 | const Repo = styled.div` 20 | color: #777; 21 | > a { 22 | float: left; 23 | margin-top: 10px; 24 | } 25 | > span { 26 | float: right; 27 | } 28 | ` 29 | 30 | export default props => ( 31 | 32 | {props.repos && 33 | props.repos.map((repo, index) => ( 34 | 35 | {repo.name} 36 | {repo.stars} 37 | 38 | 39 | ))} 40 | 41 | ) 42 | -------------------------------------------------------------------------------- /packages/examples/regithub/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "regithub", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "bae dev", 8 | "start": "bae" 9 | }, 10 | "keywords": [], 11 | "author": "siddharthkp", 12 | "license": "MIT", 13 | "dependencies": { 14 | "axios": "0.15.3", 15 | "react-helmet": "4.0.0" 16 | }, 17 | "devDependencies": { 18 | "bae": "1.2.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/examples/regithub/pages/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Nav from '../components/presentation/common/nav' 3 | import ProfileInput from '../components/presentation/profile/profile-input' 4 | 5 | export default () => ( 6 |
7 |
10 | ) 11 | -------------------------------------------------------------------------------- /packages/examples/regithub/pages/profile.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Nav from '../components/presentation/common/nav' 3 | import Profile from '../components/presentation/profile/profile' 4 | import RepoContainer from '../components/containers/repositoriesContainer.js' 5 | import getUser from '../api/user' 6 | 7 | export default class extends React.Component { 8 | constructor(props) { 9 | super(props) 10 | let username = props.req.query.user 11 | this.state = { username } 12 | } 13 | asyncComponentWillMount() { 14 | let username = this.state.username 15 | return getUser(username) 16 | } 17 | render() { 18 | return ( 19 |
20 |
24 | ) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/examples/regithub/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthkp/bae/2efbab72415a6004ee1e9ccddcb370fa690069a3/packages/examples/regithub/static/logo.png -------------------------------------------------------------------------------- /packages/examples/simple/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | example for using bae 5 |

6 |

7 | 8 |   9 | 10 | ### minimal setup 11 | 12 | ``` 13 | npm install bae --save 14 | ``` 15 | 16 |   17 | 18 | ### folder structure 19 | 20 | . 21 | ├── pages (top level routes) 22 | ├── components (organize them as you like) 23 | └── static (images, fonts, that kind of stuff) 24 | 25 |   26 | 27 | ### dev mode 28 | 29 | ``` 30 | npm run dev 31 | 32 | > Running on http://localhost:3000 33 | ``` 34 | 35 |   36 | 37 | ### ready for production? 38 | 39 | ``` 40 | npm start 41 | ``` 42 | 43 | that's all you need 44 | 45 |   46 | -------------------------------------------------------------------------------- /packages/examples/simple/components/center.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | const Center = styled.div` 4 | text-align: center; 5 | ` 6 | export default Center 7 | -------------------------------------------------------------------------------- /packages/examples/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bae-example", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "dev": "bae dev", 6 | "start": "bae" 7 | }, 8 | "author": "siddharthkp", 9 | "license": "MIT", 10 | "dependencies": { 11 | "bae": "2.0.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/examples/simple/pages/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default () =>
about
4 | -------------------------------------------------------------------------------- /packages/examples/simple/pages/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default class extends React.Component { 4 | constructor() { 5 | super() 6 | this.state = { message: 'hello' } 7 | } 8 | componentDidMount() { 9 | setTimeout(() => this.setState({ message: 'hello world' }), 1000) 10 | } 11 | render() { 12 | return
{this.state.message}
13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/examples/simple/static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/siddharthkp/bae/2efbab72415a6004ee1e9ccddcb370fa690069a3/packages/examples/simple/static/logo.png --------------------------------------------------------------------------------