├── .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 | [](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 | See profile
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 |
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 |
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
--------------------------------------------------------------------------------