├── .gitignore
├── .npmignore
├── .travis.yml
├── README.md
├── cli.js
├── examples
├── basic.js
├── fetch.js
└── styled-components.js
├── index.js
├── package.json
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | index.html
2 | .nyc_output
3 | coverage
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .nyc_output
2 | example
3 | .travis.yml
4 | test.js*
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 8
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # static-react
3 |
4 | Zero-configuration CLI React static renderer
5 |
6 | [![Build Status][badge]](https://travis-ci.org/jxnblk/static-react)
7 |
8 | [badge]: https://img.shields.io/travis/jxnblk/static-react.svg?style=flat-square
9 |
10 |
11 | ## Usage
12 |
13 | ```
14 | npm i -g static-react
15 | ```
16 |
17 | ```
18 | static-react RootComponent.js > index.html
19 | ```
20 |
21 | Static-react include babel presets and React – there is no need to install them separately
22 |
23 | ## Examples
24 |
25 | See the [examples/](examples) directory
26 |
27 | ## Fetching Data
28 |
29 | Use the `getInitialProps` static method to fetch data or get server-side props for things like CSS-in-JS libraries.
30 |
31 | ```jsx
32 | import React from 'react'
33 | import fetch from 'isomorphic-fetch'
34 |
35 | export default class extends React.Component {
36 | static getInitialProps = async () => {
37 | const data = await fetch('https://api.example.com/data')
38 |
39 | return {
40 | data
41 | }
42 | }
43 |
44 | render () {
45 | const { data } = this.props
46 |
47 | return (
48 |
49 |
Data
50 |
51 | {JSON.stringify(data, null, 2)}
52 |
53 |
54 | )
55 | }
56 | }
57 | ```
58 |
59 | ## CSS-in-JS
60 |
61 | Use the `getInitialProps` to pass side effects from CSS-in-JS libraries as props.
62 |
63 | ```jsx
64 | import React from 'react'
65 | import { Box } from 'rebass'
66 |
67 | export default class Root extends React.Component {
68 | static getInitialProps = async (app) => {
69 | const { ServerStyleSheet } = require('styled-components')
70 | const { renderToString } = require('react-dom/server')
71 | const sheet = new ServerStyleSheet()
72 | renderToString(
73 | sheet.collectStyles(app)
74 | )
75 | const css = sheet.getStyleTags()
76 | return { css }
77 | }
78 |
79 | static defaultProps = {
80 | css: ''
81 | }
82 |
83 | render () {
84 | const { css } = this.props
85 |
86 | return (
87 |
88 |
89 | ${css}
90 |
91 |
92 |
93 | Beep boop
94 |
95 |
96 |
97 | )
98 | }
99 | }
100 | ```
101 |
102 | ---
103 |
104 | MIT License
105 |
--------------------------------------------------------------------------------
/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | require('babel-register')({
3 | presets: [
4 | 'babel-preset-env',
5 | 'babel-preset-stage-0',
6 | 'babel-preset-react'
7 | ].map(require.resolve),
8 | plugins: [
9 | 'babel-plugin-transform-runtime'
10 | ].map(require.resolve)
11 | })
12 | const fs = require('fs')
13 | const path = require('path')
14 | const meow = require('meow')
15 | const render = require('./index')
16 | const config = require('pkg-conf').sync('static-react')
17 |
18 | const cli = meow(`
19 | Usage
20 | $ static-react Component.js > index.html
21 | `, {
22 | flags: {}
23 | })
24 |
25 | const [ input ] = cli.input
26 | const opts = Object.assign({}, config, cli.flags)
27 | const file = path.resolve(input)
28 | const mod = require(file)
29 | const Component = mod.default || mod
30 |
31 | render(Component, opts)
32 | .then(html => {
33 | console.log(html)
34 | })
35 | .catch(err => {
36 | console.error(err)
37 | process.exit(1)
38 | })
39 |
40 | // process.stdout.write(html)
41 |
42 | require('update-notifier')({
43 | pkg: require('./package.json')
44 | }).notify()
45 |
--------------------------------------------------------------------------------
/examples/basic.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class Root extends React.Component {
4 | static defaultProps = {
5 | title: 'static-react',
6 | }
7 |
8 | render () {
9 | const { title } = this.props
10 |
11 | return (
12 |
13 |
14 | {title}
15 |
16 |
17 | {title}
18 |
19 |
20 | )
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/examples/fetch.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import fetch from 'isomorphic-fetch'
3 |
4 | const endpoint = 'https://microbeats.now.sh/tracks'
5 |
6 | export default class Root extends React.Component {
7 | static getInitialProps = async () => {
8 | const tracks = await fetch(endpoint).then(res => res.json())
9 | return {
10 | tracks
11 | }
12 | }
13 |
14 | static defaultProps = {
15 | title: 'static-react',
16 | }
17 |
18 | render () {
19 | const { title, tracks } = this.props
20 |
21 | return (
22 |
23 |
24 | {title}
25 |
26 |
27 | {title}
28 |
29 | {tracks.map(track => (
30 | -
31 | {track.title}
32 |
33 | ))}
34 |
35 |
36 |
37 | )
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/examples/styled-components.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {
3 | Box
4 | } from 'rebass'
5 |
6 | export default class Root extends React.Component {
7 | static getInitialProps = async (el) => {
8 | const { ServerStyleSheet } = require('styled-components')
9 | const { renderToString } = require('react-dom/server')
10 | const sheet = new ServerStyleSheet()
11 | renderToString(
12 | sheet.collectStyles(el)
13 | )
14 | const css = sheet.getStyleTags()
15 | return { css }
16 | }
17 |
18 | static defaultProps = {
19 | title: 'static-react',
20 | css: ''
21 | }
22 |
23 | render () {
24 | const {
25 | title,
26 | css
27 | } = this.props
28 |
29 | return (
30 |
31 |
32 | {title}
33 | ${css}
34 |
35 |
36 |
37 |
40 |
41 |
43 |
44 |
47 |
48 |
49 |
50 | )
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const h = require('react').createElement
3 | const { renderToStaticMarkup: render } = require('react-dom/server')
4 |
5 | module.exports = async (Component, opts = {}) => {
6 | const element = h(Component, opts)
7 | const props = typeof Component.getInitialProps === 'function'
8 | ? await Component.getInitialProps(element)
9 | : {}
10 | const html = render(
11 | h(Component, Object.assign({}, opts, props))
12 | )
13 |
14 | return `${html}`
15 | }
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "static-react",
3 | "version": "4.0.0-3",
4 | "description": "React static HTML generator",
5 | "author": "Brent Jackson",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "nyc ava",
9 | "start": "./cli.js examples/basic.js",
10 | "start:sc": "./cli.js examples/styled-components.js",
11 | "start:fetch": "./cli.js examples/fetch.js"
12 | },
13 | "main": "index.js",
14 | "bin": {
15 | "static-react": "./cli.js"
16 | },
17 | "dependencies": {
18 | "babel-plugin-transform-runtime": "^6.23.0",
19 | "babel-preset-env": "^1.7.0",
20 | "babel-preset-react": "^6.24.1",
21 | "babel-preset-stage-0": "^6.24.1",
22 | "babel-register": "^6.26.0",
23 | "meow": "^5.0.0",
24 | "pkg-conf": "^2.1.0",
25 | "react": "^16.4.1",
26 | "react-dom": "^16.4.1",
27 | "update-notifier": "^2.5.0"
28 | },
29 | "devDependencies": {
30 | "ava": "^0.19.1",
31 | "is-html": "^1.0.0",
32 | "nyc": "^12.0.2",
33 | "rebass": "^2.0.0-6",
34 | "styled-components": "^3.3.2"
35 | },
36 | "repository": {
37 | "type": "git",
38 | "url": "https://github.com/jxnblk/static-react.git"
39 | },
40 | "bugs": {
41 | "url": "https://github.com/jxnblk/static-react/issues"
42 | },
43 | "homepage": "https://github.com/jxnblk/static-react"
44 | }
45 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | const test = require('ava')
2 | const {
3 | Component,
4 | createElement: h
5 | } = require('react')
6 | const render = require('./index')
7 |
8 | const Root = props => h('div', null,
9 | h('h1', null, props.title)
10 | )
11 |
12 | class App extends Component {
13 | render () {
14 | return h('h1', null, this.props.hi)
15 | }
16 | }
17 |
18 | App.getInitialProps = async () => {
19 | return { hi: 'hello' }
20 | }
21 |
22 | test('exports a function', t => {
23 | t.is(typeof render, 'function')
24 | })
25 |
26 | test('returns a string', async t => {
27 | const html = await render(Root)
28 | t.is(typeof html, 'string')
29 | })
30 |
31 | test('accepts options', async t => {
32 | const html = await render(Root, {
33 | title: 'Hello',
34 | })
35 | t.regex(html, /Hello/)
36 | })
37 |
38 | test('getInitialProps', async t => {
39 | const html = await render(App)
40 | t.regex(html, /hello/)
41 | })
42 |
--------------------------------------------------------------------------------