├── README.md ├── lerna.json ├── package-lock.json ├── package.json └── packages ├── examples ├── rola-compiler-test │ ├── client.js │ ├── components │ │ ├── App.js │ │ ├── History.js │ │ └── State.js │ ├── package-lock.json │ ├── package.json │ ├── routes.js │ ├── routes │ │ ├── 404.js │ │ ├── about.js │ │ └── home.js │ ├── server.js │ ├── styles │ │ └── main.css │ └── test.js ├── rola-static-styled-components │ ├── .gitignore │ ├── client.js │ ├── components │ │ ├── App.js │ │ ├── History.js │ │ └── State.js │ ├── package-lock.json │ ├── package.json │ ├── rola.config.js │ ├── rola.plugins.js │ ├── routes.js │ ├── routes │ │ ├── About.js │ │ └── Home.js │ ├── server.js │ └── static │ │ └── Home.js ├── rola-static-test │ ├── .gitignore │ ├── components │ │ ├── App.js │ │ ├── History.js │ │ └── State.js │ ├── package-lock.json │ ├── package.json │ ├── rola.config.js │ └── routes │ │ ├── about.js │ │ └── home.js └── rola-test │ ├── .gitignore │ ├── build │ ├── assets │ │ ├── about │ │ │ └── index.html │ │ ├── client.css │ │ ├── client.js │ │ └── index.html │ └── server.js │ ├── client.js │ ├── components │ ├── App.js │ ├── History.js │ └── State.js │ ├── package-lock.json │ ├── package.json │ ├── rola.config.js │ ├── routes.js │ ├── routes │ ├── 404.js │ ├── about.js │ └── home.js │ ├── server.js │ ├── static │ ├── About.js │ └── Home.js │ └── styles │ └── main.css ├── plugin-document ├── README.md ├── package-lock.json ├── package.json └── rola-plugin-document.js ├── plugin-styled-components ├── README.md ├── dist │ └── rola-plugin-styled-components.js ├── package-lock.json ├── package.json └── rola-plugin-styled-components.js ├── preset-node ├── README.md ├── package-lock.json ├── package.json └── rola-preset-node.js ├── preset-postcss ├── README.md ├── package-lock.json ├── package.json └── rola-preset-postcss.js ├── preset-sass ├── README.md ├── package-lock.json ├── package.json └── rola-preset-sass.js ├── rola-compiler ├── .gitignore ├── README.md ├── cli.js ├── index.js ├── lib │ ├── clientReloader.js │ ├── createConfig.js │ ├── emitter.js │ └── stats.js ├── package-lock.json └── package.json ├── rola-log ├── README.md ├── index.js ├── lib │ └── flattenRoutes.js ├── package-lock.json └── package.json ├── rola-static ├── .gitignore ├── README.md ├── cli.js ├── index.js ├── lib │ ├── emitter.js │ ├── fileLedger.js │ ├── fileManager.js │ ├── getRoutes.js │ ├── html.js │ ├── loadRoutes.js │ ├── logger.js │ ├── render.js │ └── require.js ├── package-lock.json └── package.json ├── rola-util ├── README.md ├── createClientRoot.js ├── createDocument.js ├── createServerRoot.js ├── document.js ├── filterStaticRoutePaths.js ├── getConfig.js ├── getModule.js ├── index.js ├── lib │ ├── transpile.js │ └── transpileAndGetModule.js ├── package-lock.json ├── package.json ├── postServerRender.js ├── preServerRender.js └── tests │ ├── filterStaticRoutePaths.test.js │ └── getModule.test.js └── rola ├── .gitignore ├── README.md ├── cli.js ├── index.js ├── lib ├── Link.js ├── Rola.js ├── Router.js ├── client.js ├── createStatic.js ├── history.js ├── matcher.js ├── server.js └── state.js ├── package-lock.json ├── package.json └── util ├── clientReloader.js ├── createConfig.js └── createServer.js /README.md: -------------------------------------------------------------------------------- 1 | ![repo-banner](https://user-images.githubusercontent.com/4732330/54534195-3cf7b480-4962-11e9-87bf-f04e83f61198.png) 2 | 3 | ``` 4 | npm i rola -g 5 | ``` 6 |
7 |
8 | 9 | > This project is *alpha*, so I don't recommend using it in production *yet*. I 10 | > welcome all ideas, critiques, and PRs :) 11 | 12 |
13 | 14 | `rola` is a framework for building production React sites. Easily build static 15 | pages, a client bundle, server-side rendering and a custom API, all within the 16 | same project structure. It's simple to opt-in or out of what you don't need, and 17 | the codebase is simple enough that anyone can contribute plugins and features. 18 | 19 | ## Features 20 | - easy opt-in builds 21 | - ⚡️ static page generation 22 | - 🤓 server side rendering 23 | - 🏖 client-side application bundle 24 | - simple built-in routing 25 | - zero learning curve state management 26 | - simple plugin API 27 | 28 | ## Install 29 | ``` 30 | npm i rola --save 31 | ``` 32 | 33 | ## Usage 34 | Coming soooooon. 35 | 36 | ## Presets 37 | - [@rola/preset-postcss](https://github.com/estrattonbailey/rola/tree/master/packages/preset-postcss) 38 | - [@rola/preset-sass](https://github.com/estrattonbailey/rola/tree/master/packages/preset-sass) 39 | 40 | > Have a request? Open an 41 | > [issue](https://github.com/estrattonbailey/rola/issues)! 42 | 43 | ## Plugins 44 | - [@rola/plugin-styled-components](https://github.com/estrattonbailey/rola/tree/master/packages/plugin-styled-components) 45 | - [@rola/plugin-document](https://github.com/estrattonbailey/rola/tree/master/packages/plugin-document) 46 | 47 | > Have a request? Open an 48 | > [issue](https://github.com/estrattonbailey/rola/issues)! 49 | 50 | ## Motivation 51 | More flexible, less config, API with high "guessability". 52 | 53 | ## Contributing 54 | Oh please do! Let's collab :) 55 | 56 | ## License 57 | MIT License © [Eric Bailey](https://estrattonbailey.com) 58 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.10.8" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "scripts": { 5 | "publish": "NPM_CONFIG_OTP= lerna publish" 6 | }, 7 | "devDependencies": { 8 | "lerna": "^3.13.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/client.js: -------------------------------------------------------------------------------- 1 | import '@/styles/main.css' 2 | 3 | import { client } from 'rola' 4 | import routes from '@/routes.js' 5 | 6 | client(routes, {})(document.getElementById('root')) 7 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'rola' 3 | 4 | export default function App (props) { 5 | return ( 6 |
7 | 12 | 13 |
14 | {props.children} 15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/components/History.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withHistory } from 'hypr/history' 3 | 4 | export default withHistory( 5 | class ComponentWithHistory extends React.Component { 6 | componentDidMount () { 7 | console.log('history', this.props) 8 | } 9 | 10 | render () { 11 | return null 12 | } 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/components/State.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withState } from 'hypr/state' 3 | 4 | export default withState(state => state)( 5 | class ComponentWithState extends React.Component { 6 | componentDidMount () { 7 | console.log('state', this.props) 8 | } 9 | 10 | render () { 11 | return null 12 | } 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rola-hello-world", 3 | "private": true, 4 | "scripts": { 5 | "start": "rola-compiler watch", 6 | "build": "rola-compiler build" 7 | }, 8 | "author": "estrattonbailey", 9 | "dependencies": { 10 | "@rola/compiler": "^0.2.3", 11 | "@rola/plugin-node": "^0.2.2", 12 | "@rola/plugin-postcss": "^0.2.3", 13 | "react": "^16.6.3", 14 | "react-dom": "^16.6.3", 15 | "rola": "^0.2.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/routes.js: -------------------------------------------------------------------------------- 1 | import * as home from '@/routes/home.js' 2 | import * as about from '@/routes/about.js' 3 | import * as notfound from '@/routes/404.js' 4 | 5 | export default [ 6 | home, 7 | about, 8 | notfound 9 | ] 10 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/routes/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const pathname = '*' 4 | 5 | export function load (state, req) { 6 | return { 7 | status: 404, 8 | state: { 9 | meta: { 10 | title: '404 Not Found' 11 | } 12 | } 13 | } 14 | } 15 | 16 | export function view ({ pathname, state }) { 17 | return ( 18 |

404 Not Found

19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/routes/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import App from '@/components/App.js' 3 | 4 | export const pathname = '/about' 5 | 6 | export function config () { 7 | return load() 8 | } 9 | 10 | export function load (state, req) { 11 | return { 12 | state: { 13 | title: 'about - hypr - the react toolkit', 14 | meta: { 15 | title: 'about - hypr - the react toolkit', 16 | }, 17 | } 18 | } 19 | } 20 | 21 | export function view ({ pathname, state }) { 22 | return ( 23 | 24 |

{state.title}

25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/routes/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import App from '@/components/App.js' 3 | 4 | export const pathname = '/' 5 | 6 | export function config () { 7 | return load() 8 | } 9 | 10 | export function load (state, req) { 11 | return { 12 | state: { 13 | title: 'home - hypr - the react toolkit', 14 | meta: { 15 | title: 'home - hypr - the react toolkit', 16 | }, 17 | } 18 | } 19 | } 20 | 21 | export function view ({ pathname, state }) { 22 | return ( 23 | 24 |

{state.title}

25 |
26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/server.js: -------------------------------------------------------------------------------- 1 | import { server } from 'rola' 2 | import routes from '@/routes.js' 3 | 4 | export default server(routes, {}) 5 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/styles/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | background: whitesmoke; 3 | } 4 | -------------------------------------------------------------------------------- /packages/examples/rola-compiler-test/test.js: -------------------------------------------------------------------------------- 1 | const compiler = require('@rola/compiler') 2 | 3 | const app = compiler([ 4 | { 5 | in: './client.js', 6 | out: './build', 7 | presets: [ 8 | require('@rola/preset-postcss')() 9 | ] 10 | }, 11 | { 12 | in: './server.js', 13 | out: './build', 14 | presets: [ 15 | require('@rola/preset-node')() 16 | ] 17 | }, 18 | { 19 | in: './routes/*.js', 20 | out: './build/routes', 21 | preset: [ 22 | require('@rola/preset-node')() 23 | ] 24 | } 25 | ]) 26 | 27 | app.on('error', e => console.error(e)) 28 | app.on('stats', stats => console.log(JSON.stringify(stats, null, ' '))) 29 | 30 | // app.watch() 31 | app.build() 32 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/client.js: -------------------------------------------------------------------------------- 1 | import { client } from 'rola' 2 | import routes from '@/routes.js' 3 | 4 | client(routes)(document.getElementById('root')) 5 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'rola' 3 | 4 | export default function App (props) { 5 | return ( 6 |
7 | 12 | 13 |
14 | {props.children} 15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/components/History.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withHistory } from 'hypr/history' 3 | 4 | export default withHistory( 5 | class ComponentWithHistory extends React.Component { 6 | componentDidMount () { 7 | console.log('history', this.props) 8 | } 9 | 10 | render () { 11 | return null 12 | } 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/components/State.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withState } from 'hypr/state' 3 | 4 | export default withState(state => state)( 5 | class ComponentWithState extends React.Component { 6 | componentDidMount () { 7 | console.log('state', this.props) 8 | } 9 | 10 | render () { 11 | return null 12 | } 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rola-static-styled-components", 3 | "private": true, 4 | "scripts": { 5 | "start": "rola watch", 6 | "build": "rola build" 7 | }, 8 | "author": "estrattonbailey", 9 | "dependencies": { 10 | "@rola/plugin-document": "^0.2.7", 11 | "@rola/plugin-styled-components": "^0.6.4", 12 | "react": "^16.6.3", 13 | "react-dom": "^16.6.3", 14 | "rola": "^0.6.4", 15 | "styled-components": "^4.1.3" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/rola.config.js: -------------------------------------------------------------------------------- 1 | export const presets = [ 2 | ] 3 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/rola.plugins.js: -------------------------------------------------------------------------------- 1 | import styled from '@rola/plugin-styled-components' 2 | 3 | export default [ 4 | styled() 5 | ] 6 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/routes.js: -------------------------------------------------------------------------------- 1 | import * as Home from '@/routes/Home.js' 2 | import * as About from '@/routes/About.js' 3 | 4 | export default [ 5 | Home, 6 | About 7 | ] 8 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/routes/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withState } from 'rola' 3 | import App from '@/components/App.js' 4 | 5 | export const pathname = '/about' 6 | 7 | export function load (state, req) { 8 | return { 9 | state: { 10 | title: 'about - hypr - the react toolkit', 11 | meta: { 12 | title: 'about - hypr - the react toolkit', 13 | }, 14 | } 15 | } 16 | } 17 | 18 | export const view = withState(state => ({ 19 | foo: true 20 | }))( 21 | function view ({ pathname, state, foo }) { 22 | return ( 23 | 24 |

{state.title}

25 |
26 | ) 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/routes/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import App from '@/components/App.js' 3 | import styled from 'styled-components' 4 | 5 | export const pathname = '/' 6 | 7 | export function load (state, req) { 8 | return { 9 | state: { 10 | title: 'home - hypr - the react toolkit', 11 | meta: { 12 | title: 'home - hypr - the react toolkit', 13 | }, 14 | } 15 | } 16 | } 17 | 18 | const H1 = styled.h1` 19 | color: blue; 20 | ` 21 | 22 | export function view ({ pathname, state }) { 23 | return ( 24 | 25 |

{state.title}

26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/server.js: -------------------------------------------------------------------------------- 1 | import { server } from 'rola' 2 | import routes from '@/routes.js' 3 | 4 | export default server(routes, {}) 5 | -------------------------------------------------------------------------------- /packages/examples/rola-static-styled-components/static/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createStatic } from 'rola' 3 | import * as Home from '@/routes/Home.js' 4 | 5 | export const pathname = Home.pathname 6 | 7 | export function config (state, req) { 8 | return Home.load() 9 | } 10 | 11 | export const view = createStatic(Home.view) 12 | -------------------------------------------------------------------------------- /packages/examples/rola-static-test/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /packages/examples/rola-static-test/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'rola' 3 | 4 | export default function App (props) { 5 | return ( 6 | 7 | 8 | 9 | 10 |
11 | 16 | 17 |
18 | {props.children} 19 |
20 |
21 | 22 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/rola-test/build/assets/client.css: -------------------------------------------------------------------------------- 1 | body,html{background:#f5f5f5} 2 | 3 | /*# sourceMappingURL=client.css.map*/ -------------------------------------------------------------------------------- /packages/examples/rola-test/build/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | home - hypr - the react toolkit 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |

home - hypr - the react toolkit

15 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/examples/rola-test/client.js: -------------------------------------------------------------------------------- 1 | import '@/styles/main.css' 2 | 3 | import { client } from 'rola' 4 | import routes from '@/routes.js' 5 | 6 | client(routes, {}, {}) 7 | -------------------------------------------------------------------------------- /packages/examples/rola-test/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'rola' 3 | 4 | export default function App (props) { 5 | return ( 6 |
7 | 12 | 13 |
14 | {props.children} 15 |
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /packages/examples/rola-test/components/History.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withHistory } from 'hypr/history' 3 | 4 | export default withHistory( 5 | class ComponentWithHistory extends React.Component { 6 | componentDidMount () { 7 | console.log('history', this.props) 8 | } 9 | 10 | render () { 11 | return null 12 | } 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /packages/examples/rola-test/components/State.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withState } from 'hypr/state' 3 | 4 | export default withState(state => state)( 5 | class ComponentWithState extends React.Component { 6 | componentDidMount () { 7 | console.log('state', this.props) 8 | } 9 | 10 | render () { 11 | return null 12 | } 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /packages/examples/rola-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rola-test", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "rola watch", 7 | "build": "rola build" 8 | }, 9 | "author": "estrattonbailey", 10 | "dependencies": { 11 | "@rola/plugin-document": "^0.9.0", 12 | "@rola/preset-postcss": "^0.10.0", 13 | "react": "^17.8.4", 14 | "react-dom": "^16.8.4", 15 | "rola": "^0.10.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/examples/rola-test/rola.config.js: -------------------------------------------------------------------------------- 1 | import postcss from '@rola/preset-postcss' 2 | 3 | export const presets = [ 4 | postcss() 5 | ] 6 | -------------------------------------------------------------------------------- /packages/examples/rola-test/routes.js: -------------------------------------------------------------------------------- 1 | import * as Home from '@/routes/Home.js' 2 | import * as About from '@/routes/About.js' 3 | import * as Notfound from '@/routes/404.js' 4 | 5 | export default [ 6 | Home, 7 | About, 8 | Notfound 9 | ] 10 | -------------------------------------------------------------------------------- /packages/examples/rola-test/routes/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const pathname = '*' 4 | 5 | export function load (state, req) { 6 | return { 7 | status: 404, 8 | state: { 9 | meta: { 10 | title: '404 Not Found' 11 | } 12 | } 13 | } 14 | } 15 | 16 | export function view ({ pathname, state }) { 17 | return ( 18 |

404 Not Found

19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /packages/examples/rola-test/routes/about.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import App from '@/components/App.js' 3 | 4 | export const pathname = '/about' 5 | 6 | export function load (state, req) { 7 | return { 8 | state: { 9 | title: 'about - hypr - the react toolkit', 10 | meta: { 11 | title: 'about - hypr - the react toolkit', 12 | }, 13 | } 14 | } 15 | } 16 | 17 | export function view ({ pathname, state }) { 18 | return ( 19 | 20 |

{state.title}

21 |
22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /packages/examples/rola-test/routes/home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { withState } from 'rola' 3 | import App from '@/components/App.js' 4 | 5 | export const pathname = '/' 6 | 7 | export function load (state, req) { 8 | return { 9 | state: { 10 | title: 'home - hypr - the react toolkit', 11 | meta: { 12 | title: 'home - hypr - the react toolkit', 13 | }, 14 | } 15 | } 16 | } 17 | 18 | export const view = withState(state => state)( 19 | function view ({ pathname, state }) { 20 | return ( 21 | 22 |

{state.title}

23 |
24 | ) 25 | } 26 | ) 27 | -------------------------------------------------------------------------------- /packages/examples/rola-test/server.js: -------------------------------------------------------------------------------- 1 | import { server } from 'rola' 2 | import routes from '@/routes.js' 3 | 4 | export default server(routes, {}) 5 | -------------------------------------------------------------------------------- /packages/examples/rola-test/static/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as About from '@/routes/About.js' 3 | 4 | export const pathname = About.pathname 5 | 6 | export function load () { 7 | return { 8 | state: { 9 | title: 'home - hypr - the react toolkit', 10 | meta: { 11 | title: 'home - hypr - the react toolkit', 12 | }, 13 | } 14 | } 15 | } 16 | 17 | export const view = About.view 18 | -------------------------------------------------------------------------------- /packages/examples/rola-test/static/Home.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as Home from '@/routes/Home.js' 3 | 4 | export const pathname = Home.pathname 5 | 6 | export function load () { 7 | return { 8 | state: { 9 | title: 'home - hypr - the react toolkit', 10 | meta: { 11 | title: 'home - hypr - the react toolkit', 12 | }, 13 | } 14 | } 15 | } 16 | 17 | export const view = Home.view 18 | -------------------------------------------------------------------------------- /packages/examples/rola-test/styles/main.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | background: whitesmoke; 3 | } 4 | -------------------------------------------------------------------------------- /packages/plugin-document/README.md: -------------------------------------------------------------------------------- 1 | # @rola/plugin-document 2 | Quick access to the document creation process in 3 | [rola](https://github.com/estrattonbailey/rola). 4 | 5 | > See [plugin docs](https://github.com/estrattonbailey/rola/issues/20) for more info. 6 | 7 | ## Install 8 | ```bash 9 | npm i @rola/plugin-document --save 10 | ``` 11 | 12 | ## Usage 13 | Add the plugin to the `plugins` array in your `rola.plugins.js`: 14 | 15 | ```javascript 16 | import document from '@rola/plugin-document' 17 | 18 | export default [ 19 | document(({ context, ...customProps }) => { 20 | return { 21 | head: [ '' ], 22 | body: [ ' 22 | ${body.join('')} 23 | 24 | 25 | ` 26 | } 27 | -------------------------------------------------------------------------------- /packages/rola-util/filterStaticRoutePaths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * unused 3 | */ 4 | const path = require('path') 5 | const assert = require('assert') 6 | const match = require('matched') 7 | 8 | const transpile = require('./lib/transpile.js') 9 | const transpileAndGetModule = require('./lib/transpileAndGetModule.js') 10 | 11 | module.exports = function filterStaticRoutePaths (glob) { 12 | assert(path.isAbsolute(glob), 'glob must be absolute path') 13 | 14 | const { alias } = transpileAndGetModule(path.join(process.cwd(), 'rola.config.js'), null, { quiet: true }) 15 | 16 | const paths = match.sync(glob) 17 | 18 | return paths.filter(p => { 19 | try { 20 | const code = transpile(p, { alias }) 21 | return /exports\.config\s=\s\w+/.test(code) 22 | } catch (e) { 23 | return false 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /packages/rola-util/getConfig.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const assert = require('assert') 4 | const { transformSync } = require('@babel/core') 5 | 6 | const cwd = process.cwd() 7 | 8 | function transpile (file, outDir, options = {}) { 9 | if (outDir) { 10 | assert(path.isAbsolute(outDir), 'outDir needs to be an absolute path') 11 | } else { 12 | outDir = path.join(path.dirname(file), '.rola') 13 | } 14 | 15 | try { 16 | const out = path.join(outDir, path.basename(file)) 17 | 18 | const { code } = transformSync(fs.readFileSync(file), { 19 | plugins: [ 20 | require.resolve('@babel/plugin-syntax-object-rest-spread'), 21 | require.resolve('@babel/plugin-proposal-class-properties'), 22 | require.resolve('fast-async'), 23 | [require.resolve('babel-plugin-module-resolver'), { 24 | alias: { 25 | '@': cwd, 26 | ...(options.alias || {}) 27 | } 28 | }] 29 | ], 30 | presets: [ 31 | require.resolve('@babel/preset-env'), 32 | require.resolve('@babel/preset-react') 33 | ] 34 | }) 35 | 36 | fs.outputFileSync(out, code) 37 | 38 | const mod = require(out) 39 | 40 | return mod 41 | } catch (e) { 42 | return {} 43 | } 44 | } 45 | 46 | module.exports = function getConfig () { 47 | const out = path.join(cwd, '.rola') 48 | const conf = path.join(cwd, 'rola.config.js') 49 | const plug = path.join(cwd, 'rola.plugins.js') 50 | 51 | let config = { 52 | env: {}, 53 | alias: {}, 54 | presets: [] 55 | } 56 | 57 | if (fs.existsSync(conf)) { 58 | const mod = transpile(conf, out) 59 | config = Object.assign(config, mod.default || mod) 60 | } 61 | 62 | let plugins = [] 63 | 64 | if (fs.existsSync(plug)) { 65 | const mod = transpile(plug, out, { alias: config.alias }) 66 | plugins = mod.default || mod 67 | } else { 68 | fs.outputFileSync(path.join(out, 'rola.plugins.js'), `module.exports = []`) 69 | } 70 | 71 | return { 72 | ...config, 73 | plugins 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/rola-util/getModule.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const assert = require('assert') 4 | const { transformSync } = require('@babel/core') 5 | 6 | function transpile (file, outDir, options = {}) { 7 | if (outDir) { 8 | assert(path.isAbsolute(outDir), 'outDir needs to be an absolute path') 9 | } else { 10 | outDir = path.join(path.dirname(file), '.rola') 11 | } 12 | 13 | try { 14 | const out = path.join(outDir, path.basename(file)) 15 | 16 | const { code } = transformSync(fs.readFileSync(file), { 17 | plugins: [ 18 | require.resolve('@babel/plugin-syntax-object-rest-spread'), 19 | require.resolve('@babel/plugin-proposal-class-properties'), 20 | require.resolve('fast-async'), 21 | [require.resolve('babel-plugin-module-resolver'), { 22 | alias: { 23 | '@': process.cwd(), 24 | ...(options.alias || {}) 25 | } 26 | }] 27 | ], 28 | presets: [ 29 | require.resolve('@babel/preset-env'), 30 | require.resolve('@babel/preset-react') 31 | ] 32 | }) 33 | 34 | fs.outputFileSync(out, code) 35 | 36 | const mod = require(out) 37 | 38 | fs.removeSync(out) 39 | 40 | return mod 41 | } catch (e) { 42 | return {} 43 | } 44 | } 45 | 46 | module.exports = function getModule (file, outDir, options) { 47 | console.error('getModule is deprecated, use getConfig') 48 | 49 | const { alias } = transpile(path.join(process.cwd(), 'rola.config.js'), null, { quiet: true }) 50 | 51 | assert(!path.isAbsolute(file), 'file needs to be an absolute path') 52 | 53 | return transpile(file, outDir, { alias }) 54 | } 55 | -------------------------------------------------------------------------------- /packages/rola-util/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * internal utils 3 | */ 4 | const getModule = require('./getModule.js') 5 | const getConfig = require('./getConfig.js') 6 | 7 | /** 8 | * the default document creator 9 | */ 10 | const document = require('./document.js') 11 | 12 | /** 13 | * plugin API utils 14 | */ 15 | const createDocument = require('./createDocument.js') 16 | const createClientRoot = require('./createClientRoot.js') 17 | const createServerRoot = require('./createServerRoot.js') 18 | const postServerRender = require('./postServerRender.js') 19 | const preServerRender = require('./preServerRender.js') 20 | 21 | module.exports = { 22 | getModule, 23 | getConfig, 24 | 25 | document, 26 | 27 | createDocument, 28 | createClientRoot, 29 | createServerRoot, 30 | postServerRender, 31 | preServerRender, 32 | } 33 | -------------------------------------------------------------------------------- /packages/rola-util/lib/transpile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * unused 3 | */ 4 | const fs = require('fs-extra') 5 | const path = require('path') 6 | const assert = require('assert') 7 | const { transformSync } = require('@babel/core') 8 | 9 | module.exports = function transpile (file, options = {}) { 10 | const { code } = transformSync(fs.readFileSync(file), { 11 | plugins: [ 12 | require.resolve('@babel/plugin-syntax-object-rest-spread'), 13 | require.resolve('@babel/plugin-proposal-class-properties'), 14 | require.resolve('fast-async'), 15 | [require.resolve('babel-plugin-module-resolver'), { 16 | alias: { 17 | '@': process.cwd(), 18 | ...(options.alias || {}) 19 | } 20 | }] 21 | ], 22 | presets: [ 23 | require.resolve('@babel/preset-env'), 24 | require.resolve('@babel/preset-react') 25 | ] 26 | }) 27 | 28 | return code 29 | } 30 | -------------------------------------------------------------------------------- /packages/rola-util/lib/transpileAndGetModule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * unused 3 | */ 4 | const fs = require('fs-extra') 5 | const path = require('path') 6 | const assert = require('assert') 7 | const transpile = require('./transpile.js') 8 | 9 | module.exports = function transpileAndGetModule (file, outDir, options = {}) { 10 | if (outDir) { 11 | assert(path.isAbsolute(outDir), 'outDir needs to be an absolute path') 12 | } else { 13 | outDir = path.join(path.dirname(file), '.cache') 14 | } 15 | 16 | try { 17 | const out = path.join(outDir, path.basename(file)) 18 | 19 | const code = transpile(file, options) 20 | 21 | fs.outputFileSync(out, code) 22 | 23 | const mod = require(out) 24 | 25 | fs.removeSync(out) 26 | 27 | return mod 28 | } catch (e) { 29 | if (!options.quiet) { 30 | console.error(`transpileAndGetModule for ${file} failed`) 31 | console.error(e) 32 | } 33 | return {} 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/rola-util/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rola/util", 3 | "version": "0.10.7", 4 | "description": "Internal utilities for rola.", 5 | "author": "estrattonbailey", 6 | "license": "MIT", 7 | "main": "index.js", 8 | "scripts": { 9 | "test": "ava" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/estrattonbailey/rola.git" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/estrattonbailey/rola/issues" 17 | }, 18 | "ava": { 19 | "files": [ 20 | "tests/*.js", 21 | "!filterStaticRoutePaths.test.js" 22 | ] 23 | }, 24 | "homepage": "https://github.com/estrattonbailey/rola", 25 | "dependencies": { 26 | "@babel/core": "^7.3.4", 27 | "@babel/plugin-proposal-class-properties": "^7.3.4", 28 | "@babel/plugin-proposal-object-rest-spread": "^7.3.4", 29 | "@babel/plugin-transform-flow-strip-types": "^7.3.4", 30 | "@babel/plugin-transform-react-jsx": "^7.3.0", 31 | "@babel/preset-env": "^7.3.4", 32 | "@babel/preset-react": "^7.0.0", 33 | "@rola/log": "^0.10.7", 34 | "babel-loader": "^8.0.5", 35 | "babel-plugin-module-resolver": "^3.2.0", 36 | "fast-async": "^6.3.8", 37 | "flatted": "^2.0.0", 38 | "html-meta-tags": "^1.1.0", 39 | "matched": "^3.0.1" 40 | }, 41 | "devDependencies": { 42 | "ava": "^1.3.1", 43 | "fs-extra": "^7.0.1", 44 | "memory-fs": "^0.4.1", 45 | "react": "^16.8.4" 46 | }, 47 | "gitHead": "570605c164cc377cf02c6819a29398857ca7bb7" 48 | } 49 | -------------------------------------------------------------------------------- /packages/rola-util/postServerRender.js: -------------------------------------------------------------------------------- 1 | module.exports = function postServerRender ({ context, plugins }) { 2 | const handlers = plugins.filter(p => p && p.postServerRender).map(p => p.postServerRender) 3 | 4 | return handlers.reduce((data, handler) => { 5 | try { 6 | data = Object.assign(data, handler({ context })) 7 | } catch (e) { 8 | console.error(`postServerRender failed`) 9 | console.error(e) 10 | } 11 | 12 | return data 13 | }, {}) 14 | } 15 | -------------------------------------------------------------------------------- /packages/rola-util/preServerRender.js: -------------------------------------------------------------------------------- 1 | module.exports = function preServerRender ({ context, plugins }) { 2 | const handlers = plugins.filter(p => p && p.preServerRender).map(p => p.preServerRender) 3 | 4 | return handlers.reduce((data, handler) => { 5 | try { 6 | data = Object.assign(data, handler({ context })) 7 | } catch (e) { 8 | console.error(`preServerRender failed`) 9 | console.error(e) 10 | } 11 | 12 | return data 13 | }, {}) 14 | } 15 | -------------------------------------------------------------------------------- /packages/rola-util/tests/filterStaticRoutePaths.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const test = require('ava') 4 | 5 | const outDir = path.join(process.cwd(), '/.cache') 6 | 7 | const filterStaticRoutePaths = require('../filterStaticRoutePaths.js') 8 | 9 | test.beforeEach(() => { 10 | fs.outputFileSync( 11 | path.join(outDir, 'foo.js'), 12 | ` 13 | export function config () { 14 | } 15 | ` 16 | ) 17 | fs.outputFileSync( 18 | path.join(outDir, 'bar.js'), 19 | ` 20 | export function load () { 21 | } 22 | ` 23 | ) 24 | }) 25 | 26 | test('returns route with valid config() fn', t => { 27 | const routes = filterStaticRoutePaths(path.join(outDir, '*.js')) 28 | t.true(routes.length === 1) 29 | t.true(path.basename(routes[0]) === 'foo.js') 30 | }) 31 | 32 | test('returns emptry array for single invalid route', t => { 33 | const routes = filterStaticRoutePaths(path.join(outDir, 'bar.js')) 34 | t.true(routes.length === 0) 35 | }) 36 | 37 | test.after(() => { 38 | fs.removeSync(outDir) 39 | }) 40 | -------------------------------------------------------------------------------- /packages/rola-util/tests/getModule.test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra') 2 | const path = require('path') 3 | const test = require('ava') 4 | 5 | const cwd = process.cwd() 6 | 7 | const outDir = path.join(cwd, '/.cache') 8 | 9 | const getModule = require('../getModule.js') 10 | 11 | test.beforeEach(() => { 12 | fs.outputFileSync( 13 | path.join(outDir, 'es6.js'), 14 | ` 15 | import React from 'react' 16 | export const foo = 'foo export' 17 | export function component (props) { 18 | return
19 | } 20 | ` 21 | ) 22 | }) 23 | 24 | test('transpiles es6', t => { 25 | const mod = getModule( 26 | path.join(outDir, 'es6.js'), 27 | path.join(outDir, 'sub') 28 | ) 29 | 30 | t.true(mod.foo === 'foo export') 31 | t.true(typeof mod.component === 'function') 32 | }) 33 | 34 | test('works without outDir arg', t => { 35 | const mod = getModule( 36 | path.join(outDir, 'es6.js') 37 | ) 38 | 39 | t.true(mod.foo === 'foo export') 40 | t.true(typeof mod.component === 'function') 41 | }) 42 | 43 | test('file fails with relative path', t => { 44 | try { 45 | const mod = getModule('es6.js', outDir) 46 | } catch (e) { 47 | t.pass() 48 | } 49 | }) 50 | 51 | test('outDir fails with relative path', t => { 52 | try { 53 | const mod = getModule(path.join(outDir, 'es6.js'), './') 54 | } catch (e) { 55 | t.pass() 56 | } 57 | }) 58 | 59 | test('gets local aliases, if available', t => { 60 | fs.outputFileSync( 61 | path.join(outDir, 'alias.js'), 62 | ` 63 | import self from '@' 64 | export default self 65 | ` 66 | ) 67 | fs.outputFileSync( 68 | path.join(cwd, 'rola.config.js'), 69 | `export const alias = { 70 | '@': '../rola.config.js' 71 | }` 72 | ) 73 | 74 | try { 75 | const mod = getModule(path.join(outDir, 'alias.js')) 76 | t.pass() 77 | } catch (e) { 78 | t.fail(e) 79 | } 80 | }) 81 | 82 | test.after(() => { 83 | fs.removeSync(outDir) 84 | fs.removeSync(path.join(cwd, 'rola.config.js')) 85 | }) 86 | -------------------------------------------------------------------------------- /packages/rola/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | demo 3 | -------------------------------------------------------------------------------- /packages/rola/README.md: -------------------------------------------------------------------------------- 1 | # rola 2 | See main README.md in repo root for details. 3 | 4 | ## License 5 | MIT License © [Eric Bailey](https://estrattonbailey.com) 6 | -------------------------------------------------------------------------------- /packages/rola/cli.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 'use strict' 3 | 4 | let log = null 5 | 6 | const fs = require('fs-extra') 7 | const path = require('path') 8 | const exit = require('exit') 9 | const onExit = require('exit-hook') 10 | const React = require('react') 11 | 12 | const rolaCompiler = require('@rola/compiler') 13 | const rolaStatic = require('@rola/static') 14 | const { getConfig, createDocument } = require('@rola/util') 15 | 16 | const createServer = require('./util/createServer.js') 17 | const createConfig = require('./util/createConfig.js') 18 | 19 | const cwd = process.cwd() 20 | 21 | const pkg = require('./package.json') 22 | const userPkg = require(path.join(cwd, './package.json')) 23 | 24 | const PORT = process.env.PORT || 3000 25 | const prog = require('commander') 26 | .version(pkg.version) 27 | 28 | process.env.ROLA_VERSION = pkg.version 29 | 30 | let clientEntry 31 | let serverEntry 32 | 33 | try { 34 | clientEntry = require.resolve(path.join(cwd, 'client.js')) 35 | } catch (e) {} 36 | 37 | try { 38 | serverEntry = require.resolve(path.join(cwd, 'server.js')) 39 | } catch (e) {} 40 | 41 | /** 42 | * clean up outdir 43 | */ 44 | fs.removeSync(path.join(cwd, 'build')) 45 | 46 | /** 47 | * remove temp dir on exit 48 | */ 49 | onExit(() => { 50 | fs.removeSync(path.join(cwd, '.rola')) 51 | }) 52 | 53 | let server 54 | 55 | function serve () { 56 | if (!server) { 57 | server = createServer({ 58 | file: path.join(cwd, '/build/server.js'), 59 | port: PORT 60 | }) 61 | 62 | server.init() 63 | 64 | log({ server: [ PORT ] }) 65 | 66 | onExit(() => { 67 | server && server.close() 68 | }) 69 | } 70 | } 71 | 72 | function createGenerator (config, plugins) { 73 | const generator = rolaStatic({ 74 | env: config.env, 75 | alias: config.alias, 76 | presets: config.presets, 77 | plugins 78 | }) 79 | 80 | generator.on('rendered', pages => { 81 | log({ static: pages }) 82 | // TODO clear logs? 83 | server && server.update() 84 | }) 85 | generator.on('warn', e => { 86 | log(state => ({ 87 | warn: state.warn.concat(e) 88 | })) 89 | }) 90 | generator.on('error', e => { 91 | log(state => ({ 92 | error: state.error.concat(e) 93 | })) 94 | }) 95 | 96 | return generator 97 | } 98 | 99 | function createServerProps ({ presets }) { 100 | const context = { 101 | name: userPkg.name, 102 | version: userPkg.version 103 | } 104 | 105 | const tags = createDocument({ 106 | context, 107 | plugins: presets 108 | }) 109 | 110 | const props = { 111 | context, 112 | tags 113 | } 114 | 115 | fs.outputFileSync( 116 | path.join(cwd, '.rola', 'props.js'), 117 | `module.exports = ${JSON.stringify(props, null, ' ')}` 118 | ) 119 | } 120 | 121 | prog 122 | .command('build') 123 | .action(async () => { 124 | log({ actions: [ 'build' ] }) 125 | 126 | const { plugins, ...config } = getConfig() 127 | 128 | for (let key in (config.env || {})) { 129 | process.env[key] = config.env[key] 130 | } 131 | 132 | const configs = [] 133 | 134 | if (clientEntry) configs.push(createConfig({ 135 | entry: clientEntry, 136 | env: config.env, 137 | alias: config.alias, 138 | presets: config.presets 139 | })) 140 | 141 | if (serverEntry) configs.push(createConfig({ 142 | entry: serverEntry, 143 | env: config.env, 144 | alias: config.alias, 145 | presets: config.presets 146 | })) 147 | 148 | createServerProps({ 149 | presets: [ 150 | ...config.presets, 151 | (clientEntry ? { 152 | createDocument ({ context }) { 153 | return { 154 | body: `` 155 | } 156 | } 157 | } : {}) 158 | ] 159 | }) 160 | 161 | async function done () { 162 | /** 163 | * for api requests, if needed 164 | */ 165 | if (serverEntry) serve() 166 | 167 | await createGenerator(config, plugins).render('/static', '/build/assets') 168 | 169 | exit() 170 | } 171 | 172 | if (configs.length) { 173 | let allstats = [] 174 | const compiler = rolaCompiler(configs) 175 | 176 | compiler.on('error', e => { 177 | log(state => ({ 178 | error: state.error.concat(e) 179 | })) 180 | }) 181 | 182 | compiler.on('warn', e => { 183 | log(state => ({ 184 | warn: state.warn.concat(e) 185 | })) 186 | }) 187 | 188 | compiler.on('stats', async stats => { 189 | stats.map(_stats => { 190 | const server = _stats.assets.reduce((bool, asset) => { 191 | if (/server/.test(asset.name)) bool = true 192 | return bool 193 | }, false) 194 | 195 | if (server) { 196 | allstats[1] = _stats 197 | } else { 198 | allstats[0] = _stats 199 | } 200 | }) 201 | 202 | log({ 203 | actions: [], 204 | stats: allstats 205 | }) 206 | 207 | done() 208 | }) 209 | 210 | compiler.build() 211 | } else { 212 | done() 213 | } 214 | }) 215 | 216 | prog 217 | .command('watch') 218 | .action(async () => { 219 | log({ actions: [ 'watch' ] }) 220 | 221 | const { plugins, ...config } = getConfig() 222 | 223 | for (let key in (config.env || {})) { 224 | process.env[key] = config.env[key] 225 | } 226 | 227 | let compiled = false 228 | const configs = [] 229 | 230 | if (clientEntry) configs.push(createConfig({ 231 | entry: clientEntry, 232 | env: config.env, 233 | alias: config.alias, 234 | banner: require('./util/clientReloader.js')(PORT), 235 | presets: config.presets 236 | })) 237 | 238 | if (serverEntry) configs.push(createConfig({ 239 | entry: serverEntry, 240 | env: config.env, 241 | alias: config.alias, 242 | presets: config.presets 243 | })) 244 | 245 | let allstats = [] 246 | 247 | createServerProps({ 248 | presets: [ 249 | ...config.presets, 250 | (clientEntry ? { 251 | createDocument ({ context }) { 252 | return { 253 | body: `` 254 | } 255 | } 256 | } : {}) 257 | ] 258 | }) 259 | 260 | if (configs.length) { 261 | const compiler = rolaCompiler(configs) 262 | 263 | compiler.on('error', e => { 264 | log(state => ({ 265 | error: state.error.concat(e) 266 | })) 267 | }) 268 | 269 | compiler.on('warn', e => { 270 | log(state => ({ 271 | warn: state.warn.concat(e) 272 | })) 273 | }) 274 | 275 | compiler.on('stats', stats => { 276 | // TODO 277 | // use the errors/warnings on stats objects 278 | // to keep track on pertinent warnings 279 | stats.map(_stats => { 280 | const isServer = _stats.assets.reduce((bool, asset) => { 281 | if (/server/.test(asset.name)) bool = true 282 | return bool 283 | }, false) 284 | 285 | if (isServer) { 286 | allstats[1] = _stats 287 | } else { 288 | allstats[0] = _stats 289 | } 290 | }) 291 | 292 | /** 293 | * reset logs 294 | */ 295 | log({ 296 | error: [], 297 | warn: [], 298 | log: [], 299 | stats: allstats 300 | }) 301 | 302 | if (server) { 303 | server.update() 304 | } else { 305 | serve() 306 | } 307 | 308 | if (!compiled) { 309 | createGenerator(config, plugins).watch('/static', '/build/assets') 310 | 311 | compiled = true 312 | } 313 | }) 314 | 315 | compiler.watch() 316 | } else { 317 | serve() 318 | createGenerator(config, plugins).watch('/static', '/build/assets') 319 | } 320 | }) 321 | 322 | if (!process.argv.slice(2).length) { 323 | prog.outputHelp(txt => { 324 | console.log(txt) 325 | exit() 326 | }) 327 | } else { 328 | /** 329 | * fresh console 330 | */ 331 | console.clear() 332 | 333 | log = require('@rola/log') 334 | 335 | log({ actions: [ 'initializing' ] }) 336 | 337 | prog.parse(process.argv) 338 | } 339 | -------------------------------------------------------------------------------- /packages/rola/index.js: -------------------------------------------------------------------------------- 1 | import client from './lib/client.js' 2 | import server from './lib/server.js' 3 | import { withHistory, history } from './lib/history.js' 4 | import { withState, store } from './lib/state.js' 5 | import Link from './lib/Link.js' 6 | import Router from './lib/Router.js' 7 | import createStatic from './lib/createStatic.js' 8 | 9 | export { 10 | server, 11 | client, 12 | withHistory, 13 | history, 14 | withState, 15 | store, 16 | Link, 17 | Router, 18 | createStatic 19 | } 20 | -------------------------------------------------------------------------------- /packages/rola/lib/Link.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import cx from 'nanoclass' 3 | import { history } from './history.js' 4 | 5 | export default function Link (props) { 6 | const { 7 | children, 8 | href, 9 | className, 10 | target, 11 | download, 12 | onClick, 13 | ...rest 14 | } = props 15 | 16 | const cn = cx([ 17 | className, 18 | (history.location === href) && 'active' 19 | ]) 20 | 21 | const opts = {} 22 | 23 | if (target) opts.target = target 24 | if (download) opts.download = download 25 | 26 | return ( 27 | { 28 | onClick && onClick(e) 29 | 30 | if ( 31 | e.ctrlKey || 32 | e.metaKey || 33 | e.altKey || 34 | e.shiftKey || 35 | e.defaultPrevented || 36 | opts.target === '_blank' || 37 | opts.download || 38 | /mailto|tel/.test(href) || 39 | /^(https?:)?\/\//.test(href) 40 | ) return 41 | 42 | e.preventDefault() 43 | 44 | history.pushState(href) 45 | }} {...opts} {...rest}>{children} 46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /packages/rola/lib/Rola.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from '@picostate/react' 3 | import Router from './router.js' 4 | 5 | /** 6 | * this is used internally for server.js, client.js, and App.js 7 | */ 8 | export default function Rola ({ store, router, location, resolve, children }) { 9 | return ( 10 | 11 | 12 | {children} 13 | 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /packages/rola/lib/Router.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import * as state from './state.js' 3 | import { store, history } from './history.js' 4 | 5 | export default class Router extends React.Component { 6 | constructor (props) { 7 | super(props) 8 | 9 | this.state = { 10 | children: props.children.pop ? props.children[0] : props.children 11 | } 12 | 13 | this.currentLocation = props.location 14 | 15 | store.listen(({ location }) => { 16 | const [ route, params ] = props.router(location) 17 | 18 | if (location === this.currentLocation) return 19 | if (route.redirect) return history.pushState(route.redirect.to) 20 | 21 | props.resolve({ location, params, route }, children => { 22 | this.currentLocation = location 23 | this.setState({ children }) 24 | }) 25 | }) 26 | } 27 | 28 | componentDidMount () { 29 | window.addEventListener('popstate', e => { 30 | if (!e.target.window) return 31 | history.pushState(e.target.location.href, true) 32 | }) 33 | } 34 | 35 | render () { 36 | const { children: Child } = this.state 37 | return typeof Child === 'function' ? : Child 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/rola/lib/client.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import createStore from 'picostate' 4 | import { parse } from 'flatted/esm' 5 | 6 | import matcher from './matcher.js' 7 | import { history, withHistory } from './history.js' 8 | import { store } from './state.js' 9 | import Rola from './Rola.js' 10 | 11 | import plugins from '@/.rola/rola.plugins.js' 12 | 13 | const createClientRoot = require('@rola/util/createClientRoot.js') 14 | 15 | export default function client (routes, initialState = {}, options = {}) { 16 | const location = window.location.href.replace(window.location.origin, '') 17 | 18 | const router = matcher(routes.map(({ pathname, ...route }) => [ 19 | pathname, 20 | route 21 | ])) 22 | 23 | const [ route, params ] = router(location) 24 | 25 | const { state, ...serverContext } = parse(JSON.stringify(window.__rola)) 26 | 27 | store.hydrate(Object.assign({}, state, initialState, { 28 | router: { 29 | location, 30 | params 31 | } 32 | })) 33 | 34 | let view = route.view 35 | 36 | const context = { 37 | ...serverContext, 38 | state: store.state, 39 | pathname: window.location.pathname 40 | } 41 | 42 | const View = createClientRoot({ 43 | root: view, 44 | context: { ...context }, 45 | plugins 46 | }) 47 | 48 | ReactDOM.hydrate(( 49 | { 54 | store.hydrate({ 55 | router: { 56 | location, 57 | params 58 | } 59 | }) 60 | 61 | const { 62 | load = () => {}, 63 | view 64 | } = route 65 | 66 | Promise.resolve(load(store.state)) 67 | .then(({ redirect, state }) => { 68 | if (redirect) return history.pushState(redirect.to) 69 | 70 | const meta = state.meta || {} 71 | 72 | store.hydrate(state) 73 | 74 | Promise.resolve(options.resolve ? options.resolve({ state: store.state, pathname: location }) : null) 75 | .then(() => { 76 | document.title = meta.title || document.title 77 | 78 | let view = route.view 79 | 80 | const context = { 81 | state: store.state, 82 | pathname: route.pathname 83 | } 84 | 85 | const View = createClientRoot({ 86 | root: view, 87 | context: { ...context }, 88 | plugins 89 | }) 90 | 91 | done() 92 | }) 93 | .catch(e => { 94 | console.error('options.resolve failed') 95 | console.error(e) 96 | }) 97 | }) 98 | .catch(e => { 99 | console.error('route.load failed') 100 | console.error(e) 101 | }) 102 | }}> 103 | 104 | 105 | ), document.getElementById('root')) 106 | } 107 | -------------------------------------------------------------------------------- /packages/rola/lib/createStatic.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { renderToString } from 'react-dom/server' 3 | import createStore from 'picostate' 4 | import matcher from './matcher.js' 5 | import Rola from './Rola.js' 6 | 7 | import plugins from '@/.rola/rola.plugins.js' 8 | 9 | const doc = require('@rola/util/document.js') 10 | const createDocument = require('@rola/util/createDocument.js') 11 | const createServerRoot = require('@rola/util/createServerRoot.js') 12 | const postServerRender = require('@rola/util/postServerRender.js') 13 | const preServerRender = require('@rola/util/preServerRender.js') 14 | 15 | export default function createStatic (view) { 16 | return function StaticComponent (context, serverProps) { 17 | context = { 18 | ...context, 19 | ...serverProps.context, 20 | state: { 21 | ...context.state, 22 | router: { 23 | location: context.pathname, 24 | params: {} 25 | } 26 | } 27 | } 28 | 29 | const View = createServerRoot({ 30 | root: view, 31 | context: { ...context }, 32 | plugins 33 | }) 34 | 35 | /** 36 | * preServerRender hook 37 | */ 38 | const preRenderData = preServerRender({ 39 | context: { ...context }, 40 | plugins 41 | }) 42 | 43 | /** 44 | * render 45 | */ 46 | const renderedView = renderToString( 47 | 48 | 49 | 50 | ) 51 | 52 | /** 53 | * postServerRender hook 54 | */ 55 | const postRenderData = postServerRender({ 56 | context: { ...context }, 57 | plugins: plugins 58 | }) 59 | 60 | /** 61 | * create tags with new context 62 | */ 63 | const tags = createDocument({ 64 | context: { 65 | ...context, 66 | ...serverProps.context 67 | }, 68 | plugins, 69 | ...preRenderData, 70 | ...postRenderData, 71 | ...serverProps.tags // { head, body } 72 | }, true) 73 | 74 | return doc({ ...tags, context, view: renderedView }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/rola/lib/history.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import createStore from 'picostate' 3 | 4 | export const store = createStore({}) 5 | 6 | function historyUpdater (url, cb) { 7 | let location = typeof url === 'function' ? ( 8 | url(store.state) 9 | ) : ( 10 | url 11 | ) 12 | 13 | location = location.replace(window.location.origin, '') 14 | 15 | store.hydrate({ location })(() => { 16 | cb(location) 17 | }) 18 | } 19 | 20 | export const history = { 21 | get state () { 22 | return store.state 23 | }, 24 | replaceState (url) { 25 | historyUpdater(url, sanitized => { 26 | window.history.replaceState({}, '', sanitized) 27 | }) 28 | }, 29 | pushState (url, popstate) { 30 | historyUpdater(url, sanitized => { 31 | !popstate && window.history.pushState({}, '', sanitized) 32 | }) 33 | } 34 | } 35 | 36 | export function withHistory (Component) { 37 | return props => ( 38 | 39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /packages/rola/lib/matcher.js: -------------------------------------------------------------------------------- 1 | import { parse, match, exec } from 'matchit' 2 | 3 | export default function matcher (routes, redirects = []) { 4 | const matchers = routes.map(route => parse(route[0])) 5 | const dictionary = routes.reduce((dict, route) => { 6 | dict[route[0]] = route[1] 7 | return dict 8 | }, {}) 9 | 10 | return function route (path) { 11 | const m = match(path, matchers) 12 | 13 | if (!m.length) return [] 14 | 15 | return [ 16 | dictionary[m[0].old], 17 | exec(path, m) 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/rola/lib/server.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOMServer from 'react-dom/server' 3 | import createStore from 'picostate' 4 | 5 | import matcher from './matcher.js' 6 | import Rola from './Rola.js' 7 | 8 | import plugins from '@/.rola/rola.plugins.js' 9 | 10 | const doc = require('@rola/util/document.js') 11 | const createDocument = require('@rola/util/createDocument.js') 12 | const createServerRoot = require('@rola/util/createServerRoot.js') 13 | const postServerRender = require('@rola/util/postServerRender.js') 14 | const preServerRender = require('@rola/util/preServerRender.js') 15 | 16 | function redir (res, Location, Referer, status = 302) { 17 | res.writeHead(status, { Location, Referer }) 18 | res.end() 19 | } 20 | 21 | export default function server (routes, initialState = {}, options = {}) { 22 | const router = matcher(routes.map(({ pathname, ...route }) => [ 23 | pathname, 24 | route 25 | ])) 26 | 27 | return function handler (req, res, next) { 28 | const serverProps = res.serverProps 29 | 30 | const store = createStore({}) 31 | 32 | const [ route, params ] = router(req.url) 33 | 34 | if (!route) { 35 | return next() 36 | } 37 | 38 | let { 39 | state: initialRouteState = {}, 40 | load = () => {}, 41 | view, 42 | redirect 43 | } = route 44 | 45 | if (redirect) return redir(res, redirect) 46 | 47 | store.hydrate(Object.assign( 48 | initialState, 49 | initialRouteState, 50 | { 51 | router: { 52 | location: req.url, 53 | params 54 | } 55 | } 56 | )) 57 | 58 | return Promise.resolve(load(store.state, req)) 59 | .then(({ redirect, cache, status, pathname, state = {} } = {}) => { 60 | if (redirect) return redir(res, redirect) 61 | 62 | /** 63 | * set default response headers 64 | */ 65 | res.statusCode = status || 200 66 | res.setHeader('Content-Type', 'text/html') 67 | res.setHeader( 68 | 'Cache-Control', 69 | typeof cache === 'string' ? cache : ( 70 | cache === false ? ( 71 | `no-cache, no-store, must-revalidate` 72 | ) : ( 73 | `public, max-age=${cache || 86400}` 74 | ) 75 | ) 76 | ) 77 | 78 | /** 79 | * add any loaded state to store 80 | */ 81 | store.hydrate(state) 82 | 83 | /** 84 | * create initial context 85 | */ 86 | let context = { 87 | state: store.state, 88 | pathname: route.pathname || pathname, 89 | ...serverProps.context 90 | } 91 | 92 | const View = createServerRoot({ 93 | root: view, 94 | context: { ...context }, 95 | plugins 96 | }) 97 | 98 | /** 99 | * preServerRender hook 100 | */ 101 | const preRenderData = preServerRender({ 102 | context: { ...context }, 103 | plugins 104 | }) 105 | 106 | /** 107 | * render 108 | */ 109 | const renderedView = ReactDOMServer.renderToString( 110 | 111 | 112 | 113 | ) 114 | 115 | /** 116 | * postServerRender hook 117 | */ 118 | const postRenderData = postServerRender({ 119 | context: { ...context }, 120 | plugins 121 | }) 122 | 123 | /** 124 | * create tags with new context 125 | */ 126 | const tags = createDocument({ 127 | context, 128 | plugins, 129 | ...preRenderData, 130 | ...postRenderData, 131 | ...serverProps.tags // { head, body } 132 | }) 133 | 134 | /** 135 | * return response 136 | */ 137 | res.end( 138 | doc({ ...tags, context, view: renderedView }) 139 | ) 140 | }).catch(e => { 141 | res.statusCode = 500 142 | res.setHeader('Content-Type', 'text/plain') 143 | res.end('rola error') 144 | console.log(e) 145 | }) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /packages/rola/lib/state.js: -------------------------------------------------------------------------------- 1 | import createStore from 'picostate' 2 | import { connect } from '@picostate/react' 3 | 4 | export const store = createStore({}) 5 | 6 | export const withState = connect 7 | -------------------------------------------------------------------------------- /packages/rola/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rola", 3 | "version": "0.10.8", 4 | "description": "", 5 | "main": "dist/index.js", 6 | "files": [ 7 | "dist", 8 | "util", 9 | "cli.js" 10 | ], 11 | "bin": { 12 | "rola": "./cli.js" 13 | }, 14 | "scripts": { 15 | "prepublish": "npm run build", 16 | "build": "bili index.js --format cjs --out-dir dist --minify", 17 | "watch": "bili index.js --format cjs --out-dir dist --watch" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+ssh://git@github.com/estrattonbailey/rola.git" 22 | }, 23 | "keywords": [ 24 | "spa", 25 | "single page app", 26 | "static site generator", 27 | "react", 28 | "server side rendering", 29 | "website", 30 | "web app" 31 | ], 32 | "author": "estrattonbailey", 33 | "license": "MIT", 34 | "bugs": { 35 | "url": "https://github.com/estrattonbailey/rola/issues" 36 | }, 37 | "homepage": "https://github.com/estrattonbailey/rola#readme", 38 | "dependencies": { 39 | "@picostate/react": "^2.0.0", 40 | "@rola/compiler": "^0.10.7", 41 | "@rola/log": "^0.10.7", 42 | "@rola/preset-node": "^0.10.7", 43 | "@rola/static": "^0.10.8", 44 | "@rola/util": "^0.10.7", 45 | "ansi-colors": "^3.2.3", 46 | "biti": "^2.4.0", 47 | "commander": "^2.19.0", 48 | "compression": "^1.7.3", 49 | "connect": "^3.6.6", 50 | "deepmerge": "^3.2.0", 51 | "exit-hook": "^2.0.0", 52 | "flatted": "^2.0.0", 53 | "fs-extra": "^5.0.0", 54 | "html-meta-tags": "^1.1.0", 55 | "log-update": "^2.3.0", 56 | "matchit": "^1.0.7", 57 | "nanoclass": "0.0.2", 58 | "picostate": "^1.4.0", 59 | "pocket.io": "^0.1.4", 60 | "react": "^16.8.4", 61 | "react-dom": "^16.8.4", 62 | "serve-static": "^1.13.2", 63 | "source-map-support": "^0.5.9", 64 | "webpack": "^4.29.6" 65 | }, 66 | "devDependencies": { 67 | "bili": "^4.7.1" 68 | }, 69 | "gitHead": "570605c164cc377cf02c6819a29398857ca7bb7" 70 | } 71 | -------------------------------------------------------------------------------- /packages/rola/util/clientReloader.js: -------------------------------------------------------------------------------- 1 | module.exports = function clientReloader (port) { 2 | return ` 3 | (function (global) { 4 | try { 5 | const pocketio = document.createElement('script') 6 | pocketio.src = 'https://unpkg.com/pocket.io@0.1.4/min.js' 7 | pocketio.onload = function init () { 8 | var disconnected = false 9 | var socket = io('http://localhost:${port}', { 10 | reconnectionAttempts: 3 11 | }) 12 | socket.on('connect', () => console.log('rola connected')) 13 | socket.on('update', () => { 14 | global.location.reload() 15 | }) 16 | socket.on('disconnect', () => { 17 | disconnected = true 18 | }) 19 | socket.on('reconnect_failed', e => { 20 | if (disconnected) return 21 | console.error("rola - connection to server on :${port} failed") 22 | }) 23 | } 24 | document.head.appendChild(pocketio) 25 | } catch (e) {} 26 | })(this); 27 | ` 28 | } 29 | -------------------------------------------------------------------------------- /packages/rola/util/createConfig.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const node = require('@rola/preset-node') 3 | 4 | const cwd = process.cwd() 5 | 6 | module.exports = function createConfig ({ entry, watch, env, alias, banner, presets }) { 7 | const isNode = /server/.test(entry) 8 | 9 | return { 10 | in: entry, 11 | out: isNode ? { 12 | path: path.join(cwd, 'build'), 13 | libraryTarget: 'commonjs2' 14 | } : path.join(cwd, 'build/assets'), 15 | env: env || {}, 16 | alias: alias || {}, 17 | presets: [].concat(presets || []).concat([ 18 | isNode && node() 19 | ].filter(Boolean)), 20 | banner: isNode ? ( 21 | `require('source-map-support').install();` 22 | ) : ( 23 | '/** built with rola */' 24 | ) + banner || '' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/rola/util/createServer.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const http = require('http') 3 | const { document } = require('@rola/util') 4 | 5 | const cwd = process.cwd() 6 | 7 | function req (file) { 8 | let mod = null 9 | 10 | try { 11 | mod = require(file) 12 | mod = mod.default || mod 13 | } catch (e) { 14 | console.error(e) 15 | } 16 | 17 | return mod 18 | } 19 | 20 | module.exports = function createServer ({ file, port }) { 21 | let active = false 22 | 23 | const serverProps = require(path.join(cwd, '.rola', 'props.js')) 24 | 25 | return { 26 | server: null, 27 | app: null, 28 | get active () { 29 | return active 30 | }, 31 | update () { 32 | try { 33 | delete require.cache[file] 34 | } catch (e) {} 35 | this.app = req(file) 36 | this.socket && this.socket.emit('update') 37 | }, 38 | init () { 39 | this.app = req(file) 40 | 41 | this.server = http.createServer( 42 | require('connect')() 43 | .use(require('compression')()) 44 | .use(require('serve-static')(path.join(cwd, 'build/assets'))) 45 | .use((req, res, next) => { 46 | if (!this.app) return next() 47 | 48 | res.serverProps = serverProps 49 | 50 | this.app(req, res, next) 51 | }) 52 | // TODO pass an error from developer through to brwoser? 53 | .use((req, res) => { 54 | const { name, version } = serverProps.context 55 | 56 | res.writeHead(404, { 'Content-Type': 'text/html' }) 57 | 58 | res.write(document({ 59 | head: [ `` ], 60 | view: `
404 | ${name}@${version}
`, 61 | context: { 62 | state: { 63 | meta: { 64 | title: '404 | rola' 65 | } 66 | } 67 | } 68 | })) 69 | 70 | res.end() 71 | }) 72 | ) 73 | 74 | this.socket = require('pocket.io')(this.server, { 75 | serveClient: false 76 | }) 77 | 78 | this.server.listen(port, e => { 79 | if (e) return console.log(e) 80 | active = true 81 | }) 82 | }, 83 | close () { 84 | this.server && this.server.close() 85 | } 86 | } 87 | } 88 | --------------------------------------------------------------------------------