├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── defaults ├── .eslintrc ├── _lib │ ├── client.js │ └── pages.js ├── config.json ├── index.js ├── nodemon.json ├── package.json └── src │ ├── browser.js │ └── components │ ├── Example.react.js │ ├── Index.react.js │ ├── NestedExample.react.js │ ├── Root.react.js │ └── routes.js ├── index.js ├── lib └── react-static.js └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "optional": [ 4 | "es7.asyncFunctions", 5 | "es7.exportExtensions" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "strict": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.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 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | defaults/_site 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Robert Pearce 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 | # react-static 2 | (_NO LONGER ON NPM & NOT MAINTAINED_) 3 | React static site generator framework for Node.js. 4 | 5 | ## What is this? 6 | This project exists as a static site generator that utilizes React components for markup but, unlike other static site generators, also generates the client-side React JavaScript to allow the linking between pages to be incredibly fast out of the box. This means you'll also be able to have any other fancy client-side-oriented React bundled, as well. 7 | 8 | Under the hood, this tool builds off of [React](https://github.com/facebook/react), [react-router](https://github.com/rackt/react-router) and [nodejs](https://github.com/nodejs/node) to build static markup and JavaScript. 9 | 10 | ## Installation 11 | If for some reason you'd like to install this: 12 | 13 | 1. clone this repository 14 | 1. run `$ npm install` 15 | 1. run `$ npm link` 16 | 1. from your projects folder, run `$ npm link react-static` 17 | 1. use the tool as specified below 18 | 19 | ## Usage 20 | Create a new `react-static` project: 21 | 22 | ``` 23 | $ react-static new portfolio/ 24 | Installing react-static in to `portfolio/` 25 | => Successfully installed in to `portfolio/` 26 | => Run the following to complete setup: 27 | 28 | $ cd portfolio/ && npm install 29 | 30 | => Once setup is complete, to run the development server: 31 | 32 | $ react-static serve 33 | ``` 34 | 35 | Change directory in to `portfolio/` and install dependencies: 36 | 37 | ``` 38 | $ cd portfolio/ && npm install 39 | ``` 40 | 41 | This might take a minute. Once your dependencies are installed, start the local dev bundling and watching: 42 | 43 | ``` 44 | $ react-static serve 45 | 46 | > @ build /Users/rpearce/Desktop/portfolio 47 | > npm run lint && babel-node --optional es7.asyncFunctions --stage 0 "index.js" 48 | 49 | > @ lint /Users/rpearce/Desktop/portfolio 50 | > eslint src 51 | 52 | => Building static assets... 53 | => Static assets written to _site/ 54 | => A development server is running at http://localhost:4000 55 | ``` 56 | 57 | Navigate to [http://localhost:4000](http://localhost:4000) and see the dummy components in action. 58 | 59 | From this point on, all you need to do with regards to this core functionality is make changes to your app, and `react-static serve` will re-bundle and re-serve everything automatically. 60 | 61 | _NOTE: Do not edit anything in the `\_site/` folder. This is regularly removed and recreated._ 62 | 63 | ### Creating a Component 64 | Inside of the `src/components/` components directory is where your components live, and you are free to structure them in any way you see fit, but if you change the location of `routes.js`, you're going to have problems. 65 | 66 | Create a new file, e.g., `src/components/About.react.js`, and place this content inside of it: 67 | 68 | ```js 69 | import React from 'react'; 70 | 71 | const About = () => 72 |
73 |
74 |

About

75 |
76 |
77 | 78 | About.meta = { 79 | title: 'About', 80 | description: 'This is the about us page' 81 | }; 82 | 83 | export default About; 84 | ``` 85 | 86 | If you're wondering where `class` and `extend` are, don't worry! This is a stateless functional component, as included in the [React 0.14 release notes](NEED LINK), that also sets a few static properties: `title` and `description` that are used in `src/components/Root.react.js` to provide metadata for your page (extend this as you wish). 87 | 88 | Next go to your `src/components/routes.js` file and import your `About` component: 89 | 90 | ```js 91 | import About from './About.react'; 92 | ``` 93 | 94 | and add the route information to the `routes` object: 95 | 96 | ```js 97 | const routes = { 98 | // other routes 99 | 100 | About: { 101 | path: 'desired/path/to/about.html', 102 | component: About 103 | } 104 | } 105 | ``` 106 | 107 | Once you've done this, if you haven't already run 108 | 109 | ``` 110 | $ react-static serve 111 | ``` 112 | 113 | go ahead and do that. Happy coding! 114 | 115 | ## Contribute 116 | 117 | 1. Check out the [issues](https://github.com/rpearce/react-static/issues) 118 | 1. Fork this repository 119 | 1. Clone your fork 120 | 1. Check out a feature branch (`$ git checkout -b my-feature`) 121 | 1. Make your changes and push your branch to your GitHub repo 122 | 1. Create a pull request from your branch to this repo's master 123 | 1. When all is merged, pull down the upstream changes to your master 124 | -------------------------------------------------------------------------------- /defaults/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "rules": { 4 | "strict": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /defaults/_lib/client.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp'; 2 | import browserify from 'browserify'; 3 | import babelify from 'babelify'; 4 | import source from 'vinyl-source-stream'; 5 | 6 | const client = async () => { 7 | return browserify({ entries: 'src/browser.js', debug: true }) 8 | .transform(babelify) 9 | .bundle() 10 | .pipe(source('app.js')) 11 | .pipe(gulp.dest('_site')); 12 | } 13 | 14 | export default client; 15 | -------------------------------------------------------------------------------- /defaults/_lib/pages.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fsp from 'fs-promise'; 3 | import React from 'react'; 4 | import { renderToString } from 'react-dom/server'; 5 | import { match, RoutingContext } from 'react-router'; 6 | import routes from '../src/components/routes'; 7 | 8 | const buildPages = async () => { 9 | const allRoutes = [].concat(routes.indexRoute || []).concat(routes.childRoutes || []); 10 | allRoutes.forEach(matchAndWrite); 11 | }; 12 | 13 | const matchAndWrite = ({ path }) => { 14 | /* 15 | * Prepend route path with `/` to build `location`. 16 | * E.g., `blog.html` will be `/blog.html`. 17 | * Given IndexRoute has no path, it keeps a default of `/`. 18 | */ 19 | const location = '/'.concat(path || ''); 20 | 21 | /* 22 | * Trigger react-router's route matching 23 | */ 24 | match({ routes, location }, async function handleMatch(err, redirectLocation, renderProps) { 25 | if (err) { throw(err); }; 26 | 27 | try { 28 | /* 29 | * Render component markup, create output directories 30 | * and write markup to a file whose filename is 31 | * extracted from the route's path. 32 | * Provide fallback of `index.html` for IndexRoute. 33 | */ 34 | 35 | const componentHTML = renderToString(), 36 | directory = determineDirectory(path), 37 | filePath = path || 'index.html'; 38 | 39 | execSync(`mkdir -p _site/${directory}`, { stdio: [0,1,2] }); 40 | fsp.writeFile(`_site/${filePath}`, `${componentHTML}`, 'utf8', (err) => { if (err) { throw err; } }); 41 | 42 | } catch(e) { 43 | console.error(e); 44 | } 45 | }); 46 | }; 47 | 48 | const determineDirectory = (path = '/') => { 49 | const pathArr = path.split('/'); 50 | return pathArr.slice(0, pathArr.length - 1).join('/'); 51 | } 52 | 53 | export default buildPages; 54 | -------------------------------------------------------------------------------- /defaults/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "clientJS": true, 3 | "port": 4000 4 | } 5 | -------------------------------------------------------------------------------- /defaults/index.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import express from 'express'; 3 | import { clientJS, port } from './config'; 4 | import buildPages from './_lib/pages'; 5 | import buildClientJS from './_lib/client'; 6 | 7 | async () => { 8 | try { 9 | console.log('=> Building static assets...'); 10 | 11 | /* 12 | * Remove and recreate _site build folder 13 | */ 14 | execSync('rm -rf _site'); 15 | execSync('mkdir _site'); 16 | 17 | /* 18 | * Build the static pages. 19 | */ 20 | await buildPages(); 21 | 22 | console.log('=> Static assets written to _site/'); 23 | 24 | /* 25 | * Create client JS file (app.js) in the build 26 | * path if `clientJS` config option is `true` 27 | */ 28 | if (clientJS) { 29 | await buildClientJS(); 30 | } 31 | 32 | /* 33 | * Start dev server 34 | */ 35 | const app = express(); 36 | 37 | app.use('/', express.static('_site')); 38 | app.listen(port); 39 | 40 | console.log(`=> A development server is running at http://localhost:${port}`); 41 | } catch (err) { 42 | console.error(err); 43 | } 44 | }(); 45 | -------------------------------------------------------------------------------- /defaults/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": true, 3 | "script": "index.js", 4 | "exec": "npm run build", 5 | "ignore": [ 6 | "_site/*", 7 | "*.log" 8 | ], 9 | "ext": "js json" 10 | } 11 | -------------------------------------------------------------------------------- /defaults/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.js", 3 | "scripts": { 4 | "lint": "eslint src", 5 | "dev": "nodemon", 6 | "build": "babel-node --optional es7.asyncFunctions --stage 0" 7 | }, 8 | "devDependencies": { 9 | "babel": "^5.8.29", 10 | "babel-eslint": "^4.1.4", 11 | "babelify": "^6.4.0", 12 | "browserify": "^12.0.1", 13 | "eslint": "^1.9.0", 14 | "express": "^4.13.3", 15 | "fs-promise": "^0.3.1", 16 | "gulp": "^3.9.0", 17 | "nodemon": "^1.8.1", 18 | "uglify-js": "^2.5.0", 19 | "vinyl-source-stream": "^1.1.0" 20 | }, 21 | "dependencies": { 22 | "history": "^1.12.6", 23 | "react": "^0.14.2", 24 | "react-dom": "^0.14.2", 25 | "react-router": "^1.0.0-rc3" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /defaults/src/browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import Router from 'react-router'; 4 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 5 | import routes from './components/routes'; 6 | 7 | render(( 8 | 9 | { routes } 10 | 11 | ), document); 12 | -------------------------------------------------------------------------------- /defaults/src/components/Example.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Example = () => 4 |
5 |
6 |

Example

7 |
8 |
9 | 10 | Example.meta = { 11 | title: 'Example', 12 | description: 'This is the example' 13 | }; 14 | 15 | export default Example; 16 | -------------------------------------------------------------------------------- /defaults/src/components/Index.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Index = () => 4 |
5 |
6 |

Home

7 |
8 |
9 | 10 | Index.meta = { 11 | title: 'Homepage', 12 | description: 'This is the homepage' 13 | }; 14 | 15 | export default Index; 16 | -------------------------------------------------------------------------------- /defaults/src/components/NestedExample.react.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const NestedExample = () => 4 |
5 |
6 |

Nested Example

7 |
8 |
9 | 10 | NestedExample.meta = { 11 | title: 'NestedExample', 12 | description: 'This is the nested example' 13 | }; 14 | 15 | export default NestedExample; 16 | -------------------------------------------------------------------------------- /defaults/src/components/Root.react.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Link } from 'react-router'; 3 | import { clientJS } from '../../config'; 4 | import { path } from './routes'; 5 | 6 | const Root = ({ children }) => { 7 | const { title, description } = children.type.meta; 8 | const script = clientJS ? : null; 9 | return ( 10 | 11 | 12 | { title } 13 | 14 | 15 | 16 |
17 | 24 |
25 | { children } 26 | { script } 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default Root; 33 | -------------------------------------------------------------------------------- /defaults/src/components/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Root from './Root.react'; 3 | import Index from './Index.react'; 4 | import Example from './Example.react'; 5 | import NestedExample from './NestedExample.react'; 6 | 7 | /* 8 | * Define your routes and their desired 9 | * output paths here. 10 | * 11 | * DO NOT prefix a `path` with a `/` (backslash) 12 | */ 13 | const routes = { 14 | IndexRoute: { 15 | component: Index 16 | }, 17 | ExampleRoute: { 18 | path: 'example.html', 19 | component: Example 20 | }, 21 | NestedExampleRoute: { 22 | path: 'this/is/a/ridiculously/nested/example.html', 23 | component: NestedExample 24 | } 25 | }; 26 | 27 | /* 28 | * Helper function so that we can 29 | * read all the route keys in `routes` 30 | * and export them to `childRoutes` 31 | * in our default export. 32 | */ 33 | const getChildRoutes = () => { 34 | let childRoutes = []; 35 | for (let key in routes) { 36 | if (key !== 'IndexRoute') { 37 | childRoutes.push(routes[key]); 38 | } 39 | } 40 | return childRoutes; 41 | }; 42 | 43 | export const path = (route) => { 44 | return '/'.concat(routes[route].path || ''); 45 | } 46 | 47 | export default { 48 | path: '/', 49 | component: Root, 50 | indexRoute: routes.IndexRoute, 51 | childRoutes: getChildRoutes() 52 | }; 53 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 5 | 6 | var _path = require('path'); 7 | 8 | var _path2 = _interopRequireDefault(_path); 9 | 10 | var _child_process = require('child_process'); 11 | 12 | var _commander = require('commander'); 13 | 14 | var _commander2 = _interopRequireDefault(_commander); 15 | 16 | var _packageJson = require('./package.json'); 17 | 18 | var _packageJson2 = _interopRequireDefault(_packageJson); 19 | 20 | var defaultsPath = _path2['default'].join(__dirname, 'defaults/'); 21 | var execCallback = function execCallback(err) { 22 | console.log(err);if (err !== null) { 23 | throw err; 24 | } 25 | }; 26 | 27 | _commander2['default'].command('new [path]').description('create a new react-static project').action(function handleNew(path, options) { 28 | if (path) { 29 | console.log('Installing react-static in to `' + path + '`'); 30 | (0, _child_process.execSync)('mkdir -p ' + path); 31 | (0, _child_process.execSync)('cp -r ' + defaultsPath + ' ' + path); 32 | console.log('=> Successfully installed in to `' + path + '`'); 33 | console.log('=> Run the following to complete setup:\n\n $ cd ' + path + ' && npm install\n'); 34 | console.log('=> Once setup is complete, to run the development server:\n\n $ react-static serve'); 35 | } else { 36 | console.error('Error: Please provide a directory name to `react-static new `'); 37 | } 38 | }); 39 | 40 | _commander2['default'].command('serve').description('run the development server and have it watch for changes').action(function handleServe() { 41 | (0, _child_process.execSync)('node_modules/.bin/nodemon', { stdio: [0, 1, 2] }); 42 | }); 43 | 44 | _commander2['default'].version(_packageJson2['default'].version).parse(process.argv); 45 | -------------------------------------------------------------------------------- /lib/react-static.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import path from 'path'; 4 | import { execSync } from 'child_process'; 5 | import program from 'commander'; 6 | import pkg from './package.json'; 7 | 8 | const defaultsPath = path.join(__dirname, 'defaults/'); 9 | const execCallback = (err) => { console.log(err); if (err !== null) { throw err; } }; 10 | 11 | program 12 | .command('new [path]') 13 | .description('create a new react-static project') 14 | .action(function handleNew(path, options) { 15 | if (path) { 16 | console.log(`Installing react-static in to \`${path}\``); 17 | execSync(`mkdir -p ${path}`); 18 | execSync(`cp -r ${defaultsPath} ${path}`); 19 | console.log(`=> Successfully installed in to \`${path}\``); 20 | console.log(`=> Run the following to complete setup:\n\n $ cd ${path} && npm install\n`); 21 | console.log('=> Once setup is complete, to run the development server:\n\n $ react-static serve'); 22 | } else { 23 | console.error('Error: Please provide a directory name to `react-static new `'); 24 | } 25 | }); 26 | 27 | program 28 | .command('serve') 29 | .description('run the development server and have it watch for changes') 30 | .action(function handleServe() { 31 | execSync('node_modules/.bin/nodemon', { stdio: [0,1,2] }); 32 | }); 33 | 34 | program 35 | .version(pkg.version) 36 | .parse(process.argv); 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-static", 3 | "version": "0.1.1", 4 | "description": "React static site generator framework for Node.js", 5 | "main": "index.js", 6 | "scripts": { 7 | "clean": "rm -f index.js", 8 | "lint": "eslint src", 9 | "watch": "babel lib/react-static.js -w -o index.js", 10 | "build": "babel lib/react-static.js -o index.js", 11 | "prebuild": "npm run clean && npm run lint" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "reactjs", 16 | "static", 17 | "static site", 18 | "site generator", 19 | "static site framework", 20 | "react static", 21 | "react static framework" 22 | ], 23 | "author": "Robert Pearce", 24 | "license": "MIT", 25 | "dependencies": { 26 | "babel": "^5.8.29", 27 | "commander": "^2.9.0" 28 | }, 29 | "devDependencies": { 30 | "babel-eslint": "^4.1.4", 31 | "eslint": "^1.9.0" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/rpearce/react-static.git" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/rpearce/react-static/issues" 39 | }, 40 | "homepage": "https://github.com/rpearce/react-static#readme", 41 | "preferGlobal": "true", 42 | "bin": { 43 | "react-static": "index.js" 44 | } 45 | } 46 | --------------------------------------------------------------------------------