Skip to content

Commit

Permalink
Initial commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
webyak committed Jun 7, 2016
0 parents commit c325d62
Show file tree
Hide file tree
Showing 23 changed files with 724 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .gitignore
@@ -0,0 +1,8 @@
node_modules/
*.DS_Store
static/
build/
.tmp/
nginx.conf
npm-debug.log
webpack-assets.json
61 changes: 61 additions & 0 deletions README.md
@@ -0,0 +1,61 @@
# React Static Plate

> Build static sites with React to host on [Amazon S3](https://aws.amazon.com/s3/), [Github Pages](https://pages.github.com/), [Surge](https://surge.sh/) et cetera<br>
> (React.js, React Router, Radium, Babel 6, Webpack)
### Features

&nbsp; &nbsp; ✓ ES6+.<br>
&nbsp; &nbsp; ✓ Hot Reloading.<br>
&nbsp; &nbsp; ✓ ESLint rules based on Airbnb's Javascript Styleguide.<br>
&nbsp; &nbsp; ✓ Every route is completely rendered into a `.html` page with `renderToString`.<br>
&nbsp; &nbsp; ✓ Deferred script loading, so the browser can render the html without waiting for the js bundle first.<br>
&nbsp; &nbsp; ✓ Hash is added to every asset's filename, so you can cache all assets forever.<br>
&nbsp; &nbsp; ✓ Title, Meta and other SEO tags with [react-helmet](https://github.com/nfl/react-helmet).<br>
&nbsp; &nbsp; ✓ SEO friendly, no JavaScript required to view a page.<br>
&nbsp; &nbsp; ✓ Generates sitemap.xml <br>

### Getting Started

Clone the repo, install Node modules and run the development server:

```
$ git clone -o react-static-plate -b master --single-branch \
https://github.com/webyak/react-static-plate.git my-site
$ cd my-site
$ npm install
$ npm start
```

Then open [http://localhost:3000](http://localhost:3000) and have fun ;)

### Deploy

Set your `host` in `config.json` and generate all the static files with `npm run build`. Then upload the contents of the `build/` folder to your hosting solution of choice. Finish.

You can also see the production site on your local machine using `http-server`:

```
$ npm install -g http-server
$ cd build
$ http-server
```

### Update

You can fetch and merge recent changes from this repo into your own project:

```
$ git checkout master
$ git fetch react-static-plate
$ git merge react-static-plate/master
$ npm install
```

### FAQ

**My site is not working properly on Amazon S3**:<br>
Make sure you define paths *with* trailing slashes, like `<Route path="about/">`.

**My site is not working properly on Github Pages**:<br>
Make sure you define paths *without* trailing slashes, like `<Route path="about">`.
Binary file added client/assets/favicon.ico
Binary file not shown.
33 changes: 33 additions & 0 deletions client/components/hello.js
@@ -0,0 +1,33 @@
// ==================================================
// Hello
// ==================================================

import React from 'react';
import Radium from 'radium';

import colors from '../styling/colors.js';
import commonStyles from '../styling/common_styles.js';

const { resetHeading } = commonStyles;
const { darkGray } = colors;

const style = {
padding: '8rem 0',
textAlign: 'center',
};
const headingStyle = {
...resetHeading,
color: darkGray,
cursor: 'default',
':hover': {
fontWeight: 'bold',
},
};

const Hello = () => (
<div style={style}>
<h1 style={headingStyle}>Hi, React.</h1>
</div>
);

export default Radium(Hello);
35 changes: 35 additions & 0 deletions client/components/layout.js
@@ -0,0 +1,35 @@
// ==================================================
// Layout
// ==================================================

import React from 'react';
import Radium, { StyleRoot, Style } from 'radium';
import Helmet from 'react-helmet';

const Layout = ({ children }) => (
<StyleRoot>
<Helmet
title="My site"
meta={[{ name: 'description', content: 'A static site.' }]}
link={[{ rel: 'shortcut icon', href: require('../assets/favicon.ico') }]}
/>
<Style
rules={{
'html, body': {
width: '100%',
height: '100%',
},
'*, *:before, *:after': {
boxSizing: 'border-box',
},
}}
/>
{children}
</StyleRoot>
);

Layout.propTypes = {
children: React.PropTypes.element,
};

export default Radium(Layout);
12 changes: 12 additions & 0 deletions client/components/not_found.js
@@ -0,0 +1,12 @@
// ==================================================
// NotFound
// ==================================================

import React from 'react';
import Radium from 'radium';

const NotFound = () => (
<div>404</div>
);

export default Radium(NotFound);
16 changes: 16 additions & 0 deletions client/components/root.js
@@ -0,0 +1,16 @@
// ==================================================
// Root
// ----
// Entry component for hot reloading.
// ==================================================

import React from 'react';
import { Router, browserHistory } from 'react-router';

import routes from '../routes.js';

const Root = () => (
<Router routes={routes} history={browserHistory} />
);

export default Root;
41 changes: 41 additions & 0 deletions client/index.js
@@ -0,0 +1,41 @@
/* eslint-disable global-require */
import React from 'react';
import ReactDOM from 'react-dom';

const rootEl = document.getElementById('react-root');

let render = () => {
const Root = require('./components/root.js').default;

ReactDOM.render(
<Root />,
rootEl
);
};

// manually rerender on hot reloads and show errors in development.
if (module.hot) {
const renderApp = render;

const renderError = (error) => {
const RedBox = require('redbox-react');
ReactDOM.render(
<RedBox error={error} />,
rootEl
);
};

render = () => {
try {
renderApp();
} catch (error) {
renderError(error);
}
};

module.hot.accept('./components/root.js', () => {
render();
});
}

render();
15 changes: 15 additions & 0 deletions client/routes.js
@@ -0,0 +1,15 @@
import React from 'react';
import { Route, IndexRoute } from 'react-router';

import Layout from './components/layout.js';
import Hello from './components/hello.js';
import NotFound from './components/not_found.js';

const routes = (
<Route path="/" component={Layout}>
<IndexRoute component={Hello} />
<Route path="*" component={NotFound} />
</Route>
);

export default routes;
5 changes: 5 additions & 0 deletions client/styling/colors.js
@@ -0,0 +1,5 @@
const colors = {
darkGray: '#323233',
};

export default colors;
10 changes: 10 additions & 0 deletions client/styling/common_styles.js
@@ -0,0 +1,10 @@
const commonStyles = {
resetHeading: {
padding: 0,
margin: 0,
fontWeight: 400,
lineHeight: 1.2,
},
};

export default commonStyles;
3 changes: 3 additions & 0 deletions config.json
@@ -0,0 +1,3 @@
{
"host": "http://example.com"
}
79 changes: 79 additions & 0 deletions package.json
@@ -0,0 +1,79 @@
{
"name": "react-static-plate",
"version": "0.1.0",
"description": "Build static sites with React to host on Amazon S3, Github Pages, Surge et cetera",
"license": "MIT",
"private": true,
"scripts": {
"start:dev": "babel-node ./tools/dev_server.js",
"start": "npm run start:dev",
"clean": "rimraf build",
"build:bundle": "cross-env NODE_ENV=production webpack --config ./tools/webpack.prod.config.js",
"build:static": "cross-env NODE_ENV=production babel-node ./tools/build_static_entry.js",
"build": "npm run clean && npm run build:bundle && npm run build:static"
},
"babel": {
"presets": [
"es2015",
"react",
"stage-0"
]
},
"eslintConfig": {
"parser": "babel-eslint",
"extends": "airbnb",
"rules": {
"strict": [
2,
"never"
],
"new-cap": 0,
"global-require": 0
}
},
"engines": {
"node": ">=4.4"
},
"dependencies": {
"radium": "^0.17.1",
"radium-normalize": "^2.0.3",
"react": "^15.1.0",
"react-dom": "^15.1.0",
"react-helmet": "^3.1.0",
"react-router": "^2.4.1",
"rebass": "^0.2.7"
},
"devDependencies": {
"babel-cli": "^6.9.0",
"babel-core": "^6.9.1",
"babel-eslint": "^6.0.4",
"babel-loader": "^6.2.4",
"babel-preset-es2015": "^6.9.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-0": "^6.5.0",
"cross-env": "^1.0.8",
"eslint": "^2.11.1",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.8.1",
"eslint-plugin-jsx-a11y": "^1.3.0",
"eslint-plugin-react": "^5.1.1",
"express": "^4.13.4",
"file-loader": "^0.8.5",
"json-loader": "^0.5.4",
"lodash": "^4.13.1",
"mkdirp": "^0.5.1",
"redbox-react": "^1.2.6",
"rimraf": "^2.5.2",
"sitemap": "^1.6.0",
"url-loader": "^0.5.7",
"webpack": "^1.13.1",
"webpack-dev-middleware": "^1.6.1",
"webpack-hot-middleware": "^2.10.0",
"webpack-isomorphic-tools": "^2.3.0"
},
"author": "Yannik Schweinzer",
"repository": {
"type": "git",
"url": "https://github.com/webyak/react-static-plate"
}
}
66 changes: 66 additions & 0 deletions tools/build_static.js
@@ -0,0 +1,66 @@
import React from 'react';
import { renderToString } from 'react-dom/server';
import { match, RouterContext } from 'react-router';
import Helmet from 'react-helmet';

import getPaths from './lib/get_paths.js';
import renderSitemap from './lib/render_sitemap.js';
import writeFile from './lib/write_file.js';
import renderDocument from './lib/render_document.js';

import config from '../config.json';
import webpackConfig from './webpack.prod.config.js';
import routes from '../client/routes.js';

/**
* Build a site by rendering every route into it's own file.
* @param {String} bundle Public path to bundled js file
*/
const buildStatic = ({ bundle }) => {
const { host } = config;
const paths = getPaths(routes);
// make sure the not found route is not part of the sitemap.
const sitemapPaths = paths.filter(value => value !== '/*');

const sitemap = renderSitemap({ paths: sitemapPaths, hostname: host });
writeFile({
dir: webpackConfig.output.path,
fileName: '/sitemap.xml',
content: sitemap,
});

paths.forEach(path => {
match({ routes, location: path }, (error, redirectLocation, renderProps) => {
if (error) throw error;

if (renderProps) {
const body = renderToString(<RouterContext {...renderProps} />);
const { title, meta, link, script } = Helmet.rewind();

let fileName = path === '/*'
? '/404'
// replace trailing slash with /index
: path.replace(/\/$/, '/index');

fileName = `${fileName}.html`;

const html = renderDocument({
Title: title.toComponent(),
metas: meta.toComponent(),
links: link.toComponent(),
scripts: script.toComponent(),
bundle,
body,
});

writeFile({
dir: webpackConfig.output.path,
fileName,
content: html,
});
}
});
});
};

export default buildStatic;

0 comments on commit c325d62

Please sign in to comment.