Skip to content

Commit

Permalink
[added] react-hot-loader when developing docs
Browse files Browse the repository at this point in the history
+ Enable webpack-hot-loader for css changes
+ Enable react-hot-loader for component changes with state retention.
+ Better fail over when nodemon process fail
+ If you change the webpack configuration this will restart the
webpack-dev-server.
+ Video link to demo this feature in docs readme.
  • Loading branch information
mtscout6 committed May 9, 2015
1 parent fdf0ec2 commit 0f90799
Show file tree
Hide file tree
Showing 17 changed files with 140 additions and 50 deletions.
3 changes: 3 additions & 0 deletions docs/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,8 @@
"comma-spacing": 0,
"react/no-multi-comp": 0,
"react/prop-types": 0
},
"globals": {
"CodeMirror": true
}
}
18 changes: 17 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,26 @@ Pages](http://pages.github.com/).
From the repository root run `npm run docs` and navigate your browser to
`http://localhost:4000`. This will start an express base node server with
webpack-dev middleware that will watch your file changes and recompile all the
static assets needed to generate the site.
static assets needed to generate the site. In the console output you'll see that
we bind to two ports. The first port is the one you'll use to load the docs in
your browser. The second is the webpack-dev-server we use to build the client
side assets in watch mode. _Note: while the docs should start on port 4000 if
that port is in use we progressively look for an available port. Observe
console output for the actual port that we use._ We use the
[webpack][webpack-hot] and [react][react-hot] hot loading functionality to allow
your development experience to have quickest feedback loop possible.

For a demo of how the hot loader works checkout this video:

<a href="http://www.youtube.com/watch?feature=player_embedded&v=vViVUbyAWeY
" target="_blank"><img src="http://img.youtube.com/vi/vViVUbyAWeY/0.jpg"
alt="Demo of hot loader" width="240" height="180" border="10" /></a>

## Production

This site is statically published on github pages, to do this the static assets
need to be generated. You can simulate a similar experience with `npm run
docs-prod` and navigating your browser to `http://localhost:4000`

[webpack-hot]: http://webpack.github.io/docs/hot-module-replacement-with-webpack.html
[react-hot]: http://gaearon.github.io/react-hot-loader/
File renamed without changes.
8 changes: 8 additions & 0 deletions docs/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,18 @@ import './assets/carousel.png';
import './assets/logo.png';
import './assets/favicon.ico';

import 'codemirror/mode/javascript/javascript';
import 'codemirror/theme/solarized.css';
import 'codemirror/lib/codemirror.css';
import './assets/CodeMirror.css';

import React from 'react';
import CodeMirror from 'codemirror';
import Router from 'react-router';
import routes from './src/Routes';

global.CodeMirror = CodeMirror;

Router.run(routes, Router.RefreshLocation, Handler => {
React.render(
React.createElement(Handler, window.INITIAL_PROPS), document);
Expand Down
66 changes: 33 additions & 33 deletions docs/dev-run
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import { exec } from 'child-process-promise';
portfinder.basePort = 4000;

const SIGINT = 'SIGINT';
let webpackDevServer;
let docsServer;
let processMap = {};

function output(prefix, message) {
let formattedMessage = message.trim().split('\n')
Expand All @@ -23,22 +22,37 @@ function listen({stdout, stderr}, name) {
}

function shutdown() {
if (webpackDevServer) {
webpackDevServer.kill(SIGINT);
}
if (docsServer) {
docsServer.kill(SIGINT);
for (let key in processMap) {
processMap[key].kill(SIGINT);
}
}

function catchExec(name, err) {
if (err.killed) {
console.log('Shutdown: '.cyan + name.green);
} else {
console.log(`${name} -- Failed`.red);
console.log(err.toString().red);
shutdown();
return false;
}
shutdown();

console.log(`${name} -- Failed`.red);
console.log(err.toString().red);
return true;
}

function runCmd(name, cmd, options) {
exec(cmd, options)
.progress(childProcess => {
listen(childProcess, name);
processMap[name] = childProcess;
return;
})
.then(() => console.log('Shutdown: '.cyan + name.green))
.catch(err => {
if (catchExec(name, err)) {
// Restart if not explicitly shutdown
runCmd(name, cmd, options);
}
});
}

console.log('Starting docs in Development mode'.cyan);
Expand All @@ -51,28 +65,14 @@ portfinder.getPorts(2, {}, (portFinderErr, [docsPort, webpackPort]) => {
process.exit(1);
}

exec(`webpack-dev-server --quiet --config webpack.docs.js --color --port ${webpackPort}`)
.progress(childProcess => {
listen(childProcess, 'webpack-dev-server');
webpackDevServer = childProcess;
return;
})
.then(() => console.log('Shutdown: '.cyan + 'webpack-dev-server'.green))
.catch(err => catchExec('webpack-dev-server', err));
runCmd('webpack-dev-server', `nodemon --watch webpack --watch webpack.config.js --watch node_modules --exec webpack-dev-server -- --config webpack.docs.js --color --port ${webpackPort} --debug --hot`);

exec('nodemon --exec babel-node docs/server.js', {
env: {
PORT: docsPort,
WEBPACK_DEV_PORT: webpackPort,
...process.env
}
})
.progress(childProcess => {
listen(childProcess, 'docs-server');
docsServer = childProcess;
return;
})
.then(() => console.log('Shutdown: '.cyan + 'docs-server'.green))
.catch(err => catchExec('docs-server', err));
runCmd('docs-server', 'nodemon --watch docs --watch src --watch node_modules --exec babel-node docs/server.js', {
env: {
PORT: docsPort,
WEBPACK_DEV_PORT: webpackPort,
...process.env
}
});
});

5 changes: 4 additions & 1 deletion docs/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@ if (development) {
});

app.use(function renderApp(req, res) {
res.header('Access-Control-Allow-Origin', target);
res.header('Access-Control-Allow-Headers', 'X-Requested-With');

Router.run(routes, req.url, Handler => {
let html = React.renderToString(<Handler />);
let html = React.renderToString(<Handler assetBaseUrl={target} />);
res.send(html);
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'codemirror/mode/javascript/javascript';

import 'codemirror/theme/solarized.css';
import 'codemirror/lib/codemirror.css';
import './CodeMirror.css';
import './CodeMirrorWrapper.css';

export default {
IS_NODE: false,
Expand Down
File renamed without changes.
8 changes: 3 additions & 5 deletions docs/src/HomePage.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import Grid from '../../src/Grid';
import Alert from '../../src/Alert';
import Glyphicon from '../../src/Glyphicon';

const HomePage = React.createClass({
render: function () {
export default class HomePage extends React.Component{
render() {
return (
<div>
<NavMain activePage="home" />
Expand All @@ -34,6 +34,4 @@ const HomePage = React.createClass({
</div>
);
}
});

export default HomePage;
}
3 changes: 1 addition & 2 deletions docs/src/ReactPlayground.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ import * as modTabPane from '../../src/TabPane';
import * as modTooltip from '../../src/Tooltip';
import * as modWell from '../../src/Well';

import {CodeMirror, IS_NODE} from './CodeMirror';
import babel from 'babel-core/browser';

const classNames = modClassNames.default;
Expand Down Expand Up @@ -103,7 +102,7 @@ const IS_MOBILE = typeof navigator !== 'undefined' && (

const CodeMirrorEditor = React.createClass({
componentDidMount() {
if (IS_MOBILE || IS_NODE) {
if (IS_MOBILE || CodeMirror === undefined) {
return;
}

Expand Down
12 changes: 9 additions & 3 deletions docs/src/Root.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ const Root = React.createClass({
}
},

getDefaultProps() {
return {
assetBaseUrl: ''
};
},

render() {
// Dump out our current props to a global object via a script tag so
// when initialising the browser environment we can bootstrap from the
Expand All @@ -66,8 +72,8 @@ const Root = React.createClass({
__html: `<title>React Bootstrap</title>
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<link href='assets/bundle.css' rel='stylesheet' />
<link href='assets/favicon.ico' type='image/x-icon' rel='icon' />
<link href='${this.props.assetBaseUrl}/assets/bundle.css' rel='stylesheet' />
<link href='${this.props.assetBaseUrl}/assets/favicon.ico' type='image/x-icon' rel='icon' />
<!--[if lt IE 9]>
<script src='https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js'></script>
<script src='https://oss.maxcdn.com/respond/1.4.2/respond.min.js'></script>
Expand All @@ -85,7 +91,7 @@ const Root = React.createClass({
<Router.RouteHandler />

<script dangerouslySetInnerHTML={browserInitScriptObj} />
<script src='assets/bundle.js' />
<script src={`${this.props.assetBaseUrl}/assets/bundle.js`} />
</body>
</html>
);
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
"brfs": "^1.4.0",
"chai": "^2.2.0",
"child-process-promise": "^1.0.1",
"client-loader": "0.0.1",
"codemirror": "^5.0.0",
"colors": "^1.0.3",
"css-loader": "^0.12.0",
Expand Down Expand Up @@ -73,6 +72,7 @@
"nodemon": "^1.3.7",
"portfinder": "^0.4.0",
"react": "^0.13.1",
"react-hot-loader": "^1.2.7",
"react-router": "^0.13.1",
"rf-changelog": "^0.4.0",
"rimraf": "^2.3.2",
Expand Down
18 changes: 17 additions & 1 deletion webpack/docs.config.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
import config from './webpack.config';
export default config({docs: true});
import yargs from 'yargs';

const argv = yargs
.alias('p', 'optimize-minimize')
.alias('d', 'debug')
.option('port', {
default: '8080',
type: 'string'
})
.argv;

export default config({
docs: true,
development: argv.debug,
optimize: argv.optimizeMinimize,
port: parseInt(argv.port)
});
2 changes: 1 addition & 1 deletion webpack/strategies/development.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import _ from 'lodash';

export default (config, options) => {
if (options.development) {
if (options.development && !options.docs) {
config = _.extend({}, config, {
devtool: 'sourcemap'
});
Expand Down
40 changes: 40 additions & 0 deletions webpack/strategies/docs-development.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import _ from 'lodash';
import webpack from 'webpack';

function addWebpackDevServerScripts(entries, webpackDevServerAddress) {
let clientScript = `webpack-dev-server/client?${webpackDevServerAddress}`;
let webpackScripts = ['webpack/hot/dev-server', clientScript];
return _.mapValues(entries, entry => webpackScripts.concat(entry));
}

export default (config, options) => {
if (options.development && options.docs) {
let webpackDevServerAddress = `http://localhost:${options.port}`;
config = _.extend({}, config, {
entry: addWebpackDevServerScripts(config.entry, webpackDevServerAddress),
output: _.extend({}, config.output, {
publicPath: `${webpackDevServerAddress}/assets/`
}),
module: _.extend({}, config.module, {
loaders: config.module.loaders.map(value => {
if (/js/.test(value.test.toString())) {
return _.extend({}, value, {
loader: 'react-hot!' + value.loader
});
}
else {
return value;
}
})
}),
// Remove extract text plugin from dev workflow so hot reload works on css.
plugins: config.plugins.concat([
new webpack.NoErrorsPlugin()
])
});

return config;
}

return config;
};
1 change: 0 additions & 1 deletion webpack/strategies/docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export default (config, options) => {
jsLoader = value.loader;

return _.extend({}, value, {
loader: jsLoader + '!client',
exclude: /node_modules|Samples\.js/
});
}
Expand Down
2 changes: 2 additions & 0 deletions webpack/strategies/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import development from './development';
import docs from './docs';
import docsDevelopment from './docs-development';
import ie8 from './ie8';
import optimize from './optimize';
import test from './test';
Expand All @@ -9,5 +10,6 @@ export default [
docs,
ie8,
development,
docsDevelopment,
test
];

0 comments on commit 0f90799

Please sign in to comment.