Ver código fonte

Integrate Universal Router, React App, History modules

...update project structure, add /routes folder with page/screen components along with the routing information; integrate markdown-it and front-matter; make automation scripts compatible with Node.js v6 (without transpilation step); update all npm dependencies; move app.js to core/app.js; integrate Redux.
Konstantin Tarkus 9 anos atrás
pai
commit
34525ee820
67 arquivos alterados com 973 adições e 1036 exclusões
  1. 0 3
      .babelrc
  2. 0 10
      .eslintrc
  3. 1 4
      .gitattributes
  4. 12 7
      .travis.yml
  5. 3 1
      LICENSE.txt
  6. 38 26
      README.md
  7. 0 41
      app.js
  8. 0 23
      components/GoogleAnalytics/GoogleAnalytics.js
  9. 0 5
      components/GoogleAnalytics/package.json
  10. 0 39
      components/Html/Html.js
  11. 0 5
      components/Html/package.json
  12. 0 24
      components/Layout/Layout.js
  13. 0 37
      components/Layout/Layout.scss
  14. 0 5
      components/Layout/package.json
  15. 0 61
      components/Link/Link.js
  16. 0 9
      components/Link/Link.scss
  17. 0 5
      components/Link/package.json
  18. 0 24
      components/Navigation/Navigation.js
  19. 0 32
      components/Navigation/Navigation.scss
  20. 0 5
      components/Navigation/package.json
  21. 28 0
      components/content/Content.js
  22. 12 0
      components/index.js
  23. 38 0
      components/layout/Header.js
  24. 15 0
      components/layout/Layout.css
  25. 37 0
      components/layout/Layout.js
  26. 36 0
      components/layout/Navigation.js
  27. 0 27
      components/variables.scss
  28. 0 11
      config.js
  29. 0 14
      core/Location.js
  30. 30 0
      core/app.js
  31. 27 0
      core/store.js
  32. 93 47
      package.json
  33. 0 20
      pages/404.js
  34. 0 28
      pages/500.js
  35. 0 20
      pages/about.js
  36. 0 20
      pages/blog/index.js
  37. 0 20
      pages/blog/test-article-one.js
  38. 0 20
      pages/blog/test-article-two.js
  39. 0 20
      pages/index.js
  40. 34 0
      routes/about/index.js
  41. 68 0
      routes/about/index.md
  42. 53 0
      routes/error/index.js
  43. 34 0
      routes/home/index.js
  44. 10 0
      routes/home/index.md
  45. 27 0
      routes/index.js
  46. 2 2
      static/humans.txt
  47. 25 0
      static/index.html
  48. 5 1
      test/.eslintrc
  49. 0 21
      test/routes-loader-spec.js
  50. 19 0
      test/spec.js
  51. 1 0
      tools/.eslintrc
  52. 11 8
      tools/build.js
  53. 20 18
      tools/bundle.js
  54. 8 8
      tools/clean.js
  55. 8 6
      tools/copy.js
  56. 51 0
      tools/deploy.gh.js
  57. 0 42
      tools/deploy.js
  58. 27 0
      tools/deploy.s3.js
  59. 0 11
      tools/lib/copy.js
  60. 0 19
      tools/lib/fs.js
  61. 0 52
      tools/lib/routes-loader.js
  62. 0 18
      tools/lib/task.js
  63. 0 54
      tools/render.js
  64. 35 17
      tools/start.js
  65. 25 0
      tools/task.js
  66. 100 146
      tools/webpack.config.js
  67. 40 0
      tools/webpack.markdown-loader.js

+ 0 - 3
.babelrc

@@ -1,3 +0,0 @@
-{
-  "stage": 0
-}

+ 0 - 10
.eslintrc

@@ -1,10 +0,0 @@
-{
-  "extends": "airbnb",
-  "globals": {
-    "__DEV__": true
-  },
-  "rules": {
-    "react/jsx-quotes": 0,
-    "jsx-quotes": [2, "prefer-double"]
-  }
-}

+ 1 - 4
.gitattributes

@@ -9,11 +9,8 @@
 .*      text eol=lf
 *.css   text eol=lf
 *.html  text eol=lf
-*.jade  text eol=lf
 *.js    text eol=lf
 *.json  text eol=lf
-*.less  text eol=lf
 *.md    text eol=lf
-*.sh    text eol=lf
 *.txt   text eol=lf
-*.xml   text eol=lf
+

+ 12 - 7
.travis.yml

@@ -1,9 +1,14 @@
-sudo: false
 language: node_js
 node_js:
-  - iojs
-  - '0.12'
-  - '0.10'
-matrix:
-  allow_failures:
-    - node_js: iojs
+  - '6'
+env:
+  - CXX=g++-4.8
+addons:
+  apt:
+    sources:
+      - ubuntu-toolchain-r-test
+    packages:
+      - g++-4.8
+script:
+  - npm run lint
+  - npm run test

+ 3 - 1
LICENSE.txt

@@ -1,4 +1,6 @@
-Copyright (c) Konstantin Tarkus (@koistya) | MIT License
+                            The MIT License
+
+Copyright (c) 2015-2016 Konstantin Tarkus (@koistya). All rights reserved.
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 38 - 26
README.md

@@ -3,7 +3,7 @@
 [![NPM version](http://img.shields.io/npm/v/generator-react-static.svg?style=flat-square)](https://www.npmjs.com/package/generator-react-static)
 [![NPM downloads](http://img.shields.io/npm/dm/generator-react-static.svg?style=flat-square)](https://www.npmjs.com/package/generator-react-static)
 [![Build Status](http://img.shields.io/travis/koistya/react-static-boilerplate/master.svg?style=flat-square)](https://travis-ci.org/koistya/react-static-boilerplate)
-[![Dependency Status](http://img.shields.io/david/dev/koistya/react-static-boilerplate.svg?branch=master&style=flat-square)](https://david-dm.org/koistya/react-static-boilerplate#info=devDependencies)
+[![Dependency Status](http://img.shields.io/david/koistya/react-static-boilerplate.svg?branch=master&style=flat-square)](https://david-dm.org/koistya/react-static-boilerplate)
 [![Sponsors](https://opencollective.com/react-static-boilerplate/badge/sponsors.svg?style=flat-square)](https://opencollective.com/react-static-boilerplate#support)
 [![Chat](https://img.shields.io/badge/chat-%23react--starter--kit-blue.svg?style=flat-square)](https://gitter.im/koistya/react-static-boilerplate)
 
@@ -12,16 +12,21 @@
 
 ### Features
 
-&nbsp; &nbsp; ✓ Generates static `.html` pages from [React](http://facebook.github.io/react/) components ([demo](http://react-static.tarkus.me))<br>
-&nbsp; &nbsp; ✓ Generates routes based on the list of files in the `/pages` folder<br>
-&nbsp; &nbsp; ✓ Next generation JavaScript with [Babel](https://github.com/babel/babel)<br>
-&nbsp; &nbsp; ✓ [Sass](http://sass-lang.com/) syntax for CSS via [postCSS](https://github.com/postcss/postcss) and [precss](https://github.com/jonathantneal/precss)<br>
-&nbsp; &nbsp; ✓ Development web server with [Browsersync](http://www.browsersync.io) and [React Transform](https://github.com/gaearon/babel-plugin-react-transform)<br>
-&nbsp; &nbsp; ✓ Bundling and optimization with [Webpack](http://webpack.github.io/)<br>
+&nbsp; &nbsp; ✓ Modern JavaScript syntax ([ES2015](http://babeljs.io/docs/learn-es2015/)+) via [Babel](http://babeljs.io/)<br>
+&nbsp; &nbsp; ✓ Modern CSS syntax (CSS3+) via [PostCSS](https://github.com/postcss/postcss)<br>
+&nbsp; &nbsp; ✓ Application state management via [Redux](http://redux.js.org/)<br>
+&nbsp; &nbsp; ✓ Routing and navigation via [Universal Router](https://github.com/kriasoft/universal-router)<br>
+&nbsp; &nbsp; ✓ Modular styles via [CSS Modules](https://github.com/css-modules/css-modules)<br>
 &nbsp; &nbsp; ✓ [Code-splitting](https://github.com/webpack/docs/wiki/code-splitting) and async chunk loading<br>
+&nbsp; &nbsp; ✓ Hot Module Replacement ([HMR](https://webpack.github.io/docs/hot-module-replacement.html)) /w [React Hot Loader](http://gaearon.github.io/react-hot-loader/)<br>
+&nbsp; &nbsp; ✓ Bundling and optimization with [Webpack](https://webpack.github.io/)<br>
+&nbsp; &nbsp; ✓ Cross-device testing with [Browsersync](https://browsersync.io/)<br>
 &nbsp; &nbsp; ✓ Easy deployment to [GitHub Pages](https://pages.github.com/), [Amazon S3](http://davidwalsh.name/hosting-website-amazon-s3) or [Firebase](https://www.firebase.com/)<br>
+&nbsp; &nbsp; ✓ Minimum dependencies (no Gulp/Grunt)<br>
 &nbsp; &nbsp; ✓ [Yeoman](http://yeoman.io/) generator ([generator-react-static](https://www.npmjs.com/package/generator-react-static))<br>
-&nbsp; &nbsp; ✓ 24/7 community support in [#react-static-boilerplate](https://gitter.im/koistya/react-static-boilerplate) chat room on Gitter<br>
+&nbsp; &nbsp; ✓ 24/7 community support on [Gitter](https://gitter.im/koistya/react-static-boilerplate) or [StackOverflow](http://stackoverflow.com/questions/tagged/react-starter-kit)<br>
+&nbsp; &nbsp; ✓ Customization requests on [Codementor](https://www.codementor.io/koistya)<br>
+
 
 ### Sponsors
 
@@ -44,26 +49,30 @@
 
 ### Directory Layout
 
+
 ```
 .
 ├── /build/                     # The folder for compiled output
 ├── /node_modules/              # 3rd-party libraries and utilities
-├── /components/                # React components
+├── /components/                # Shared/generic UI components
+│   ├── /layout/                # Layout component
+│   ├── /button/                # Button component
+│   └── /...                    # etc.
 ├── /core/                      # Core framework
-├── /pages/                     # React.js-based web pages
-│   ├── /blog/                  # Blog post entries example
-│   ├── /404.js                 # Not Found page
-│   ├── /500.js                 # Error page
-│   ├── /about.js               # About Us page
-│   └── /index.js               # Home page
+│   ├── /app.js                 # Application entry point (bootstrap)
+│   ├── /store.js               # Application state manager (Redux)
+│   └── /...                    # etc.
+├── /routes/                    # View/screen UI components + routing information
+│   ├── /about/                 # About page
+│   ├── /error/                 # Error page
+│   ├── /home/                  # Home page
+│   └── /...                    # etc.
 ├── /static/                    # Static files such as favicon.ico etc.
 ├── /test/                      # Unit and integration tests
 ├── /tools/                     # Build automation scripts and utilities
-│── app.js                      # The main JavaScript file (entry point)
-│── config.js                   # Website configuration / settings
-│── LICENSE.txt                 # License file
-│── package.json                # Dev dependencies and NPM scripts
-└── README.md                   # Project overview
+│── LICENSE.txt                 # Licensing information
+│── package.json                # The list of project dependencies and NPM scripts
+└── README.md                   # Project overview / getting started guide
 ```
 
 
@@ -75,12 +84,11 @@ Just clone the repo, install Node.js modules and run `npm start`:
 $ git clone -o react-static-boilerplate -b master --single-branch \
       https://github.com/koistya/react-static-boilerplate.git MyApp
 $ cd MyApp
-$ npm install
-$ npm start
+$ npm install           # Install project dependencies listed in package.json
+$ npm start             # Build and launch the app, same as "node tools/start.js"
 ```
 
-Then open [http://localhost:3000/](http://localhost:3000/) in your browser.
-
+**NODE**: Make sure that you have [Node.js](https://nodejs.org/) v6 installed on your local machine.
 
 ### How to Test
 
@@ -106,8 +114,7 @@ $ npm run build release         # Build production release
 
 ### How to Update
 
-You can always fetch and merge the recent changes from this repo back into
-your own project:
+You can always fetch and merge the recent changes from this repo back into your own project:
 
 ```shell
 $ git checkout master
@@ -179,5 +186,10 @@ Love **React Static Boilerplate** work and community? Help us keep it alive by [
 * [Learn ES6](https://babeljs.io/docs/learn-es6/), [ES6 Features](https://github.com/lukehoban/es6features#readme)
 
 
+### License
+
+Copyright © 2015-2016 Konstantin Tarkus. This source code is licensed under the MIT license found in the
+[LICENSE.txt](https://github.com/koistya/react-static-boilerplate/blob/master/LICENSE.txt) file.
+
 ---
 Made with ♥ by Konstantin Tarkus ([@koistya](https://twitter.com/koistya)) and [contributors](https://github.com/koistya/react-static-boilerplate/graphs/contributors) &nbsp;|&nbsp; MIT License

+ 0 - 41
app.js

@@ -1,41 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import 'babel/polyfill';
-import React from 'react';
-import ReactDOM from 'react-dom';
-import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
-import Location from './core/Location';
-import Layout from './components/Layout';
-
-const routes = {}; // Auto-generated on build. See tools/lib/routes-loader.js
-
-const route = async (path, callback) => {
-  const handler = routes[path] || routes['/404'];
-  const component = await handler();
-  await callback(<Layout>{React.createElement(component)}</Layout>);
-};
-
-function run() {
-  const container = document.getElementById('app');
-  Location.listen(location => {
-    route(location.pathname, async (component) => ReactDOM.render(component, container, () => {
-      // Track the page view event via Google Analytics
-      window.ga('send', 'pageview');
-    }));
-  });
-}
-
-if (canUseDOM) {
-  // Run the application when both DOM is ready and page content is loaded
-  if (['complete', 'loaded', 'interactive'].includes(document.readyState) && document.body) {
-    run();
-  } else {
-    document.addEventListener('DOMContentLoaded', run, false);
-  }
-}
-
-export default { route, routes };

+ 0 - 23
components/GoogleAnalytics/GoogleAnalytics.js

@@ -1,23 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React from 'react';
-import { googleAnalyticsId } from '../../config';
-
-const trackingCode = { __html:
-  `(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=` +
-  `function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;` +
-  `e=o.createElement(i);r=o.getElementsByTagName(i)[0];` +
-  `e.src='https://www.google-analytics.com/analytics.js';` +
-  `r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));` +
-  `ga('create','${googleAnalyticsId}','auto');`,
-};
-
-function GoogleAnalytics() {
-  return <script dangerouslySetInnerHTML={trackingCode} />;
-}
-
-export default GoogleAnalytics;

+ 0 - 5
components/GoogleAnalytics/package.json

@@ -1,5 +0,0 @@
-{
-  "private": true,
-  "name": "GoogleAnalytics",
-  "main": "./GoogleAnalytics.js"
-}

+ 0 - 39
components/Html/Html.js

@@ -1,39 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { PropTypes } from 'react';
-import GoogleAnalytics from '../GoogleAnalytics';
-import config from '../../config';
-
-function Html({ title, description, body, debug }) {
-  return (
-    <html className="no-js" lang="">
-      <head>
-        <meta charSet="utf-8" />
-        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
-        <title>{title || config.title}</title>
-        <meta name="description" content={description || config.description} />
-        <meta name="viewport" content="width=device-width, initial-scale=1" />
-        <link rel="apple-touch-icon" href="apple-touch-icon.png" />
-        <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto" />
-        <script src={'/app.js?' + new Date().getTime()} />
-      </head>
-      <body>
-        <div id="app" dangerouslySetInnerHTML={{ __html: body }} />
-        <GoogleAnalytics />
-      </body>
-    </html>
-  );
-}
-
-Html.propTypes = {
-  title: PropTypes.string,
-  description: PropTypes.string,
-  body: PropTypes.string.isRequired,
-  debug: PropTypes.bool.isRequired,
-};
-
-export default Html;

+ 0 - 5
components/Html/package.json

@@ -1,5 +0,0 @@
-{
-  "private": true,
-  "name": "Html",
-  "main": "./Html.js"
-}

+ 0 - 24
components/Layout/Layout.js

@@ -1,24 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { PropTypes } from 'react';
-import './Layout.scss';
-import Navigation from '../Navigation';
-
-function Layout({ children }) {
-  return (
-    <div className="Layout">
-      <Navigation />
-      {children}
-    </div>
-  );
-}
-
-Layout.propTypes = {
-  children: PropTypes.element.isRequired,
-};
-
-export default Layout;

+ 0 - 37
components/Layout/Layout.scss

@@ -1,37 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-@import '../variables.scss';
-
-html, body {
-  margin: 0;
-  padding: 0;
-  background-color: $body-bg;
-  color: $text-color;
-  font-family: $font-family-base;
-}
-
-.Layout {
-  margin: 0 auto;
-}
-
-@media (min-width: $screen-sm-min) {
-  .Layout {
-    width: calc($screen-sm-min - 18px);
-  }
-}
-
-@media (min-width: $screen-md-min) {
-  .Layout {
-    width: calc($screen-md-min - 22px);
-  }
-}
-
-@media (min-width: $screen-lg-min) {
-  .Layout {
-    width: calc($screen-lg-min - 30px);
-  }
-}

+ 0 - 5
components/Layout/package.json

@@ -1,5 +0,0 @@
-{
-  "private": true,
-  "name": "Layout",
-  "main": "./Layout.js"
-}

+ 0 - 61
components/Link/Link.js

@@ -1,61 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { Component, PropTypes } from 'react';
-import './Link.scss';
-import Location from '../../core/Location';
-
-function isLeftClickEvent(event) {
-  return event.button === 0;
-}
-
-function isModifiedEvent(event) {
-  return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
-}
-
-class Link extends Component {
-
-  static propTypes = {
-    to: PropTypes.string.isRequired,
-    children: PropTypes.element.isRequired,
-    state: PropTypes.object,
-    onClick: PropTypes.func,
-  };
-
-  static handleClick = event => {
-    let allowTransition = true;
-    let clickResult;
-
-    if (this.props && this.props.onClick) {
-      clickResult = this.props.onClick(event);
-    }
-
-    if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
-      return;
-    }
-
-    if (clickResult === false || event.defaultPrevented === true) {
-      allowTransition = false;
-    }
-
-    event.preventDefault();
-
-    if (allowTransition) {
-      const link = event.currentTarget;
-      Location.pushState(
-        this.props && this.props.state || null,
-        this.props && this.props.to || (link.pathname + link.search));
-    }
-  };
-
-  render() {
-    const { to, children, ...props } = this.props;
-    return <a {...props} onClick={Link.handleClick.bind(this)}>{children}</a>;
-  }
-
-}
-
-export default Link;

+ 0 - 9
components/Link/Link.scss

@@ -1,9 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-.Link {
-
-}

+ 0 - 5
components/Link/package.json

@@ -1,5 +0,0 @@
-{
-  "private": true,
-  "name": "Link",
-  "main": "./Link.js"
-}

+ 0 - 24
components/Navigation/Navigation.js

@@ -1,24 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React from 'react';
-import './Navigation.scss';
-import Link from '../Link';
-
-function Navigation() {
-  return (
-    <ul className="Navigation" role="menu">
-      <li className="Navigation-item">
-        <a className="Navigation-link" href="/" onClick={Link.handleClick}>Home</a>
-      </li>
-      <li className="Navigation-item">
-        <a className="Navigation-link" href="/about" onClick={Link.handleClick}>About</a>
-      </li>
-    </ul>
-  );
-}
-
-export default Navigation;

+ 0 - 32
components/Navigation/Navigation.scss

@@ -1,32 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-@import '../variables.scss';
-
-.Navigation {
-  display: flex;
-  flex-direction: row;
-  margin: 2em 0 5em 0;
-  list-style: none;
-  justify-content: flex-end;
-}
-
-.Navigation-item {
-  padding: 0 2em;
-}
-
-.Navigation-link {
-  padding: 0.5em 1em;
-  color: $brand-color;
-  text-decoration: none;
-  text-transform: uppercase;
-  cursor: pointer;
-
-  &:hover {
-    border-bottom: 3px solid $brand-color;
-    color: $text-color;
-  }
-}

+ 0 - 5
components/Navigation/package.json

@@ -1,5 +0,0 @@
-{
-  "private": true,
-  "name": "Navigation",
-  "main": "./Navigation.js"
-}

+ 28 - 0
components/content/Content.js

@@ -0,0 +1,28 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import Layout from '../layout/Layout.js';
+
+function Content({ title, html }) {
+  return (
+    <Layout>
+      <h1>{title}</h1>
+      <div dangerouslySetInnerHTML={{ __html: html }} />
+    </Layout>
+  );
+}
+
+Content.propTypes = {
+  title: React.PropTypes.string.isRequired,
+  html: React.PropTypes.string.isRequired,
+};
+
+export default Content;

+ 12 - 0
components/index.js

@@ -0,0 +1,12 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+export Content from './content/Content';
+export Layout from './layout/Layout';

+ 38 - 0
components/layout/Header.js

@@ -0,0 +1,38 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import Navigation from './Navigation';
+
+class Header extends React.Component {
+
+  componentDidMount() {
+    window.componentHandler.upgradeElement(this.refs.root);
+  }
+
+  componentWillUnmount() {
+    window.componentHandler.downgradeElements(this.refs.root);
+  }
+
+  render() {
+    return (
+      <header className="mdl-layout__header" ref="root">
+        <div className="mdl-layout__header-row">
+          <span className="mdl-layout-title">React Static Boilerplate</span>
+          <div className="mdl-layout-spacer"></div>
+          <Navigation />
+        </div>
+      </header>
+    );
+  }
+
+}
+
+export default Header;

+ 15 - 0
components/layout/Layout.css

@@ -0,0 +1,15 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+.content {
+  width: 100%;
+  max-width: 1000px;
+  margin: 0 auto;
+}

+ 37 - 0
components/layout/Layout.js

@@ -0,0 +1,37 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import Header from './Header';
+import s from './Layout.css';
+
+class Layout extends React.Component {
+
+  componentDidMount() {
+    window.componentHandler.upgradeElement(this.refs.root);
+  }
+
+  componentWillUnmount() {
+    window.componentHandler.downgradeElements(this.refs.root);
+  }
+
+  render() {
+    return (
+      <div className="mdl-layout mdl-js-layout" ref="root">
+        <div className="mdl-layout__inner-container">
+          <Header />
+          <main {...this.props} className={s.content} />
+        </div>
+      </div>
+    );
+  }
+}
+
+export default Layout;

+ 36 - 0
components/layout/Navigation.js

@@ -0,0 +1,36 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import { Link } from 'react-app';
+
+class Navigation extends React.Component {
+
+  componentDidMount() {
+    window.componentHandler.upgradeElement(this.refs.root);
+  }
+
+  componentWillUnmount() {
+    window.componentHandler.downgradeElements(this.refs.root);
+  }
+
+  render() {
+    return (
+      <nav className="mdl-navigation" ref="root">
+        <Link className="mdl-navigation__link" to="/">Home</Link>
+        <Link className="mdl-navigation__link" to="/about">About</Link>
+        <Link className="mdl-navigation__link" to="/not-found">Not Found</Link>
+      </nav>
+    );
+  }
+
+}
+
+export default Navigation;

+ 0 - 27
components/variables.scss

@@ -1,27 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-/*
- * Scaffolding
- * -------------------------------------------------------------------------- */
-
-$body-bg: #f7f7f7;
-$text-color: #333;
-$brand-color: #21ce99;
-
-/*
- * Typography
- * -------------------------------------------------------------------------- */
-
-$font-family-base: 'Roboto', 'Helvetica', sans-serif;
-
-/*
- * Media queries breakpoints
- * -------------------------------------------------------------------------- */
-
-$screen-sm-min: 768px;
-$screen-md-min: 992px;
-$screen-lg-min: 1200px;

+ 0 - 11
config.js

@@ -1,11 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-export default {
-  title: 'React Static Boilerplate',
-  description: 'Static website generator for React.js web applications.',
-  googleAnalyticsId: 'UA-XXXXX-X',
-};

+ 0 - 14
core/Location.js

@@ -1,14 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
-import createHistory from 'history/lib/createBrowserHistory';
-import createMemoryHistory from 'history/lib/createMemoryHistory';
-import useQueries from 'history/lib/useQueries';
-
-const location = useQueries(canUseDOM ? createHistory : createMemoryHistory)();
-
-export default location;

+ 30 - 0
core/app.js

@@ -0,0 +1,30 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import 'babel-polyfill';
+import { createApp } from 'react-app';
+import store from './store';
+import routes from '../routes';
+
+createApp({
+  routes,
+  context: { store },
+  container: document.getElementById('container'),
+});
+
+// if (module.hot) {
+//   module.hot.accept(() => {
+//     createApp({
+//       routes,
+//       context: { store },
+//       container: document.getElementById('container'),
+//     });
+//   });
+// }

+ 27 - 0
core/store.js

@@ -0,0 +1,27 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import { createStore } from 'redux';
+
+/**
+ * Centralized application state
+ * See http://redux.js.org/
+ */
+const store = createStore((state, action) => {
+  // TODO: Add action handlers (aka "reduces")
+  switch (action) {
+    case 'COUNT':
+      return { ...state, count: (state.count || 0) + 1 };
+    default:
+      return state;
+  }
+});
+
+export default store;

+ 93 - 47
package.json

@@ -1,52 +1,98 @@
 {
-  "devDependencies": {
-    "autoprefixer": "^6.0.3",
-    "babel": "^5.8.29",
-    "babel-core": "^5.8.29",
-    "babel-eslint": "^4.1.3",
-    "babel-loader": "^5.3.2",
-    "babel-plugin-react-transform": "^1.1.1",
-    "browser-sync": "^2.9.11",
-    "chai": "^3.4.0",
-    "css-loader": "^0.21.0",
-    "del": "^2.0.2",
-    "eslint": "^1.7.3",
-    "eslint-config-airbnb": "0.1.0",
-    "eslint-plugin-react": "^3.6.3",
-    "fbjs": "^0.4.0",
-    "file-loader": "^0.8.4",
-    "git-repository": "^0.1.1",
-    "glob": "^5.0.15",
-    "history": "^1.12.6",
-    "hygienist-middleware": "^0.1.0",
-    "lodash.merge": "^3.3.2",
-    "mkdirp": "^0.5.1",
-    "mocha": "^2.3.3",
-    "ncp": "^2.0.0",
-    "node-libs-browser": "^0.5.3",
-    "postcss": "^5.0.10",
-    "postcss-import": "^7.1.0",
-    "postcss-loader": "^0.7.0",
-    "precss": "^1.3.0",
-    "raw-loader": "^0.5.1",
-    "react": "^0.14.0",
-    "react-dom": "^0.14.0",
-    "react-transform-catch-errors": "^1.0.0",
-    "react-transform-hmr": "^1.0.1",
-    "redbox-react": "^1.1.1",
-    "style-loader": "^0.13.0",
-    "through2": "^2.0.0",
-    "url-loader": "^0.5.6",
-    "webpack": "^1.12.2",
-    "webpack-dev-middleware": "^1.2.0",
-    "webpack-hot-middleware": "^2.4.1"
+  "private": true,
+  "engines": {
+    "node": ">=6",
+    "npm": ">=3.8"
+  },
+  "dependencies": {
+    "autoprefixer": "^6.3.6",
+    "babel-cli": "^6.8.0",
+    "babel-core": "^6.8.0",
+    "babel-eslint": "^6.0.4",
+    "babel-loader": "^6.2.4",
+    "babel-plugin-transform-runtime": "^6.8.0",
+    "babel-polyfill": "^6.8.0",
+    "babel-preset-es2015": "^6.6.0",
+    "babel-preset-react": "^6.5.0",
+    "babel-preset-stage-1": "^6.5.0",
+    "babel-register": "^6.8.0",
+    "babel-runtime": "^6.6.1",
+    "browser-sync": "^2.12.8",
+    "chai": "^3.5.0",
+    "cpy": "^4.0.0",
+    "css-loader": "^0.23.1",
+    "del": "^2.2.0",
+    "eslint": "^2.9.0",
+    "eslint-config-airbnb": "^9.0.1",
+    "eslint-plugin-import": "^1.8.0",
+    "eslint-plugin-jsx-a11y": "^1.2.0",
+    "eslint-plugin-react": "^5.1.1",
+    "extend": "^3.0.0",
+    "fastclick": "^1.0.6",
+    "fbjs": "^0.8.2",
+    "file-loader": "^0.8.5",
+    "front-matter": "^2.0.7",
+    "git-repository": "^0.1.4",
+    "highlight.js": "^9.3.0",
+    "history": "^2.1.1",
+    "json-loader": "^0.5.4",
+    "markdown-it": "^6.0.1",
+    "mocha": "^2.4.5",
+    "pixrem": "^3.0.0",
+    "pleeease-filters": "^3.0.0",
+    "postcss": "^5.0.21",
+    "postcss-calc": "^5.2.1",
+    "postcss-color-function": "^2.0.1",
+    "postcss-custom-properties": "^5.0.1",
+    "postcss-import": "^8.1.2",
+    "postcss-loader": "^0.9.1",
+    "postcss-pseudoelements": "^3.0.0",
+    "postcss-selector-not": "^2.0.0",
+    "react": "^15.0.2",
+    "react-dom": "^15.0.2",
+    "react-hot-loader": "^3.0.0-beta.2",
+    "react-redux": "^4.4.5",
+    "redux": "^3.5.2",
+    "s3": "^4.4.0",
+    "style-loader": "^0.13.1",
+    "stylelint": "^6.3.3",
+    "stylelint-config-standard": "^7.0.0",
+    "url-loader": "^0.5.7",
+    "webpack": "^1.13.0",
+    "webpack-dev-middleware": "^1.6.1",
+    "webpack-hot-middleware": "^2.10.0",
+    "whatwg-fetch": "^1.0.0"
+  },
+  "babel": {
+    "presets": [
+      "react",
+      "es2015",
+      "stage-1"
+    ],
+    "plugins": [
+      "transform-runtime"
+    ]
+  },
+  "eslintConfig": {
+    "parser": "babel-eslint",
+    "extends": "airbnb"
+  },
+  "stylelint": {
+    "extends": "stylelint-config-standard",
+    "rules": {
+      "string-quotes": "single"
+    }
   },
   "scripts": {
-    "lint": "eslint src tools test",
-    "test": "npm run lint && mocha --compilers js:babel/register",
-    "clean": "babel-node --eval \"require('./tools/clean')().catch(err => console.log(err.stack))\"",
-    "build": "babel-node --eval \"require('./tools/build')().catch(err => console.log(err.stack))\"",
-    "start": "babel-node --eval \"require('./tools/start')().catch(err => console.log(err.stack))\"",
-    "deploy": "babel-node --eval \"require('./tools/deploy')().catch(err => console.log(err.stack))\""
+    "eslint": "eslint components core routes test tools",
+    "stylelint": "stylelint \"components/**/*.css\" \"routes/**/*.css\"",
+    "lint": "npm run eslint && npm run stylelint",
+    "test": "mocha --compilers js:babel-register",
+    "test:watch": "mocha --compilers js:babel-register --reporter min --watch",
+    "clean": "node tools/clean",
+    "build": "node tools/build --release",
+    "build:debug": "node tools/build",
+    "deploy": "node tools/deploy.gh --release",
+    "start": "node tools/start"
   }
 }

+ 0 - 20
pages/404.js

@@ -1,20 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { Component } from 'react';
-
-export default class extends Component {
-
-  render() {
-    return (
-      <div>
-        <h1>Not Found</h1>
-        <p>The page you're looking for was not found.</p>
-      </div>
-    );
-  }
-
-}

+ 0 - 28
pages/500.js

@@ -1,28 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { Component, PropTypes } from 'react';
-
-export default class extends Component {
-
-  static propTypes = {
-    error: PropTypes.instanceOf(Error),
-  };
-
-  render() {
-    return (
-      <div>
-        <h1>Error</h1>
-        <pre>{
-          this.props.error ?
-            this.props.error.message + '\n\n' + this.props.error.stack :
-            'A critical error occurred.'
-        }</pre>
-      </div>
-    );
-  }
-
-}

+ 0 - 20
pages/about.js

@@ -1,20 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { Component } from 'react';
-
-export default class extends Component {
-
-  render() {
-    return (
-      <div>
-        <h1>About Us</h1>
-        <p>Coming soon.</p>
-      </div>
-    );
-  }
-
-}

+ 0 - 20
pages/blog/index.js

@@ -1,20 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { Component } from 'react';
-
-export default class extends Component {
-
-  render() {
-    return (
-      <div>
-        <h1>Blog</h1>
-        <p>Coming soon.</p>
-      </div>
-    );
-  }
-
-}

+ 0 - 20
pages/blog/test-article-one.js

@@ -1,20 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { Component } from 'react';
-
-export default class extends Component {
-
-  render() {
-    return (
-      <div>
-        <h1>Test Article 1</h1>
-        <p>Coming soon.</p>
-      </div>
-    );
-  }
-
-}

+ 0 - 20
pages/blog/test-article-two.js

@@ -1,20 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { Component } from 'react';
-
-export default class extends Component {
-
-  render() {
-    return (
-      <div>
-        <h1>Test Article 2</h1>
-        <p>Coming soon.</p>
-      </div>
-    );
-  }
-
-}

+ 0 - 20
pages/index.js

@@ -1,20 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import React, { Component } from 'react';
-
-export default class extends Component {
-
-  render() {
-    return (
-      <div>
-        <h1>Home Page</h1>
-        <p>Coming soon.</p>
-      </div>
-    );
-  }
-
-}

+ 34 - 0
routes/about/index.js

@@ -0,0 +1,34 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import { Content } from '../../components';
+
+export default {
+
+  path: '/about',
+
+  async action() {
+    return new Promise((resolve, reject) => {
+      require.ensure([], require => {
+        try {
+          const content = require('./index.md');
+          resolve({
+            title: content.title,
+            component: Content,
+            props: content,
+          });
+        } catch (err) {
+          reject(err);
+        }
+      });
+    });
+  },
+
+};

+ 68 - 0
routes/about/index.md

@@ -0,0 +1,68 @@
+---
+title: About Us
+---
+
+## Cadme comitum fecere
+
+Lorem markdownum velis auras figuram spes solebat spectabat, cum alium,
+plenissima aratri visae herbarum in corpore silvas consumpta. Subito virgae nec
+paratae flexit et niveae repperit erat paratu cum albis steterat conclamat hic!
+
+Nocte suae ligat! *Si* nitidum pervia, illa tua, ab minimo pasci dabitur? In
+fictus concurreret pennis, illis cum accipe rogavi in et nostro cum lacertis
+hostibus ab saxo ne. Genibusque vixque; sine videt terribili lucos ipsum vobis
+resque, et suum pietatis fulvis, est velle. Semele oscula ferat frigidus mactata
+montes, es me parari, piae.
+
+## Inflataque ait leves frigida
+
+Letum per ipsa nostro animae, mari illuc in levi corpus aestibus excussam
+deflentem sic cuius. Venere dedit illa cui in quo senecta artus bella inficit,
+Achaica. Videbatur crinem resonantia alto dea umida dicitur igne; meus signa
+habet; est. Cognovit coepta: similes fugis: habuissem votivi liquida: ictus visi
+nostra me Adoni.
+
+## Laedar cum margine quoque
+
+Quam dato ullis, acer venturi volantes! Tuam non non cursu acta hic, novem
+nutrit, in sidera viscera iam fontes tempora, omnes. Saturnius artus inquit,
+conatoque erectos lenius, carinae, ora est infamia elige per Medusaei induitur.
+Quem quem ab postquam tunc frondescere nodis capiam labique. Voluere luce
+Semeles.
+
+```
+    if (delete(digital, hibernateSoft, dynamicExcelVpn) > io_secondary_led /
+            84) {
+        disk = load;
+        orientationPci.matrix_laptop(modelSsdTweet);
+    } else {
+        kdeEmoticonLed.mebibyte_algorithm_domain(2,
+                hackerCtr.rom_iso_desktop.scarewarePrimaryBankruptcy(station,
+                disk_mask_matrix, restore_crt));
+        cameraSpyware(4, multitasking(-3, log_dfs_controller));
+        menuCisc.swappable -= w(mount_vle_unicode, 5);
+    }
+    var optic_spider = newbieFunctionThick(-3, esportsKbpsUnix);
+    var dvd_ctp_resolution = dithering;
+```
+
+## Usus fixurus illi petunt
+
+Domosque tune amas mihi adhuc et *alter per* suasque versavitque iners
+crescentemque nomen verba nunc. Acervos hinc natus si habet. Et cervix imago
+quod! Arduus dolet!
+
+```
+    cpcDdrCommand.window(moodleAlpha, im, server_alpha.doubleVrmlMonochrome(
+            iosBar - -2, white_dual, ad(2, 94, 83)));
+    mbps_typeface_publishing.bit.host_flash_capacity(click(90,
+            cyberspace_srgb_pup - mpeg, marketing_trackback +
+            table_plagiarism_domain));
+    syn_e = powerExtension * defragmentNntpOsd(alertOutputNode(pop,
+            pageResponsiveDrive));
+    method -= switch_newsgroup_flaming;
+```
+
+Aliquid mansura arida altismunera **in illi**. Dignus vir pontum *crimen
+versabat* carpunt omnes rotis Canentem erant in Oebalio, et manu senecta
+iungere. Prima diurnis!

+ 53 - 0
routes/error/index.js

@@ -0,0 +1,53 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import { Layout } from '../../components';
+
+function ErrorPage({ title, message, stackTrace }) {
+  return (
+    <Layout>
+      <h1>{title}</h1>
+      <p>{message}</p>
+      {stackTrace && <pre>{stackTrace}</pre>}
+    </Layout>
+  );
+}
+
+ErrorPage.propTypes = {
+  title: React.PropTypes.string.isRequired,
+  message: React.PropTypes.string.isRequired,
+  stackTrace: React.PropTypes.string.isRequired,
+};
+
+export default {
+
+  path: '/error',
+
+  action({ error = {} }) {
+    const props = {
+      title: 'Error',
+      message: 'Oups, something went wrong!',
+      stackTrace: process.env.NODE_ENV === 'production' ? null : error.stack,
+    };
+
+    if (error.status === 404) {
+      props.title = 'Page Not Found';
+      props.message = 'This page does not exist.';
+    }
+
+    return {
+      title: props.title,
+      component: ErrorPage,
+      props,
+    };
+  },
+
+};

+ 34 - 0
routes/home/index.js

@@ -0,0 +1,34 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import { Content } from '../../components';
+
+export default {
+
+  path: '/',
+
+  async action() {
+    return new Promise((resolve, reject) => {
+      require.ensure([], require => {
+        try {
+          const content = require('./index.md');
+          resolve({
+            title: content.title,
+            component: Content,
+            props: content,
+          });
+        } catch (err) {
+          reject(err);
+        }
+      });
+    });
+  },
+
+};

+ 10 - 0
routes/home/index.md

@@ -0,0 +1,10 @@
+---
+title: React Static Boilerplate
+---
+
+## Welcome!
+
+This is a single-page application powered by React and Material Design Lite (MDL).
+
+https://github.com/koistya/react-static-boilerplate
+

+ 27 - 0
routes/index.js

@@ -0,0 +1,27 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import home from './home';
+import about from './about';
+import error from './error';
+
+const routes = {
+
+  path: '/',
+
+  children: [
+    home,
+    about,
+    error,
+  ],
+
+};
+
+export default routes;

+ 2 - 2
static/humans.txt

@@ -11,5 +11,5 @@
 
 # TECHNOLOGY COLOPHON
 
-    ES2015, HTML5, CSS3
-    React, Normalize.css
+    JavaScript, HTML5, CSS3
+    React, Babel, Material Design Lite (MDL)

+ 25 - 0
static/index.html

@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="no-js" lang="">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="x-ua-compatible" content="ie=edge">
+    <title></title>
+    <meta name="description" content="">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
+    <link rel="stylesheet" href="https://cdn.rawgit.com/tleunen/react-mdl/master/extra/material.min.css">
+    <link rel="stylesheet" href="https://cdn.rawgit.com/isagalaev/highlight.js/master/src/styles/default.css">
+    <link rel="apple-touch-icon" href="apple-touch-icon.png">
+  </head>
+  <body>
+    <div id="container"></div>
+    <script src="https://cdn.rawgit.com/tleunen/react-mdl/master/extra/material.min.js"></script>
+    <script src="/bundle.js"></script>
+    <!-- Google Analytics: change UA-XXXXX-Y to be your site's ID. -->
+    <script>
+      window.ga=function(){ga.q.push(arguments)};ga.q=[];ga.l=+new Date;
+      ga('create','UA-XXXXX-Y','auto');ga('send','pageview')
+    </script>
+    <script src="https://www.google-analytics.com/analytics.js" async defer></script>
+  </body>
+</html>

+ 5 - 1
test/.eslintrc

@@ -1,6 +1,10 @@
 {
+  "env": {
+    "mocha": true
+  },
   "rules": {
     "no-console": 0,
-    "no-unused-expressions": 0
+    "no-unused-expressions": 0,
+    "padded-blocks": 0
   }
 }

+ 0 - 21
test/routes-loader-spec.js

@@ -1,21 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import { describe, it } from 'mocha';
-import { expect } from 'chai';
-
-describe('routes-loader', () => {
-  it('Should load a list of routes', function test(done) {
-    this.cacheable = () => {};
-    this.async = () => (err, result) => {
-      expect(err).to.be.null;
-      expect(result).to.not.to.be.empty.and.have.all.keys('/', '/404', '/500');
-      done();
-    };
-
-    require('../tools/lib/routes-loader').call(this, 'const routes = {};');
-  });
-});

+ 19 - 0
test/spec.js

@@ -0,0 +1,19 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+import { expect } from 'chai';
+
+describe('test suite', () => {
+
+  it('test', () => {
+    expect(true).to.be.equal.true;
+  });
+
+});

+ 1 - 0
tools/.eslintrc

@@ -1,5 +1,6 @@
 {
   "rules": {
+    "global-require": 0,
     "no-console": 0
   }
 }

+ 11 - 8
tools/build.js

@@ -1,14 +1,17 @@
 /**
  * React Static Boilerplate
  * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
  */
 
-import task from './lib/task';
+const task = require('./task');
 
-export default task(async function build() {
-  await require('./clean')();
-  await require('./copy')();
-  await require('./bundle')();
-  await require('./render')();
-});
+module.exports = task('build', () => Promise.resolve()
+  .then(() => require('./clean'))
+  .then(() => require('./copy'))
+  .then(() => require('./bundle'))
+);

+ 20 - 18
tools/bundle.js

@@ -1,24 +1,26 @@
 /**
  * React Static Boilerplate
  * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
  */
 
-import webpack from 'webpack';
-import task from './lib/task';
-import webpackConfig from './webpack.config';
+const webpack = require('webpack');
+const task = require('./task');
+const config = require('./webpack.config');
 
-export default task(function bundle() {
-  return new Promise((resolve, reject) => {
-    const bundler = webpack(webpackConfig);
-    const run = (err, stats) => {
-      if (err) {
-        reject(err);
-      } else {
-        console.log(stats.toString(webpackConfig[0].stats));
-        resolve();
-      }
-    };
-    bundler.run(run);
-  });
-});
+module.exports = task('bundle', new Promise((resolve, reject) => {
+  const bundler = webpack(config);
+  const run = (err, stats) => {
+    if (err) {
+      reject(err);
+    } else {
+      console.log(stats.toString(config.stats));
+      resolve();
+    }
+  };
+  bundler.run(run);
+}));

+ 8 - 8
tools/clean.js

@@ -1,14 +1,14 @@
 /**
  * React Static Boilerplate
  * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
  */
 
-import del from 'del';
-import task from './lib/task';
-import fs from './lib/fs';
+const del = require('del');
+const task = require('./task');
 
-export default task(async function clean() {
-  await del(['build/*', '!build/.git'], { dot: true });
-  await fs.mkdir('build');
-});
+module.exports = task('clean', () => del(['build/*', '!build/.git'], { dot: true }));

+ 8 - 6
tools/copy.js

@@ -1,16 +1,18 @@
 /**
  * React Static Boilerplate
  * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
  */
 
-import task from './lib/task';
-import cp from './lib/copy';
+const cpy = require('cpy');
+const task = require('./task');
 
 /**
  * Copies static files such as robots.txt, favicon.ico to the
  * output (build) folder.
  */
-export default task(async function copy() {
-  await cp('static', 'build');
-});
+module.exports = task('copy', cpy(['static/**/*'], 'build'));

+ 51 - 0
tools/deploy.gh.js

@@ -0,0 +1,51 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+const GitRepo = require('git-repository');
+const task = require('./task');
+
+const remote = {
+  name: 'github',
+  url: 'https://github.com/{user}/{repo}.git',
+  branch: 'gh-pages',
+};
+
+/**
+ * Deploy the contents of the `/build` folder to GitHub Pages.
+ */
+module.exports = task('deploy', () => new Promise((resolve, reject) => {
+  // Initialize a new Git repository inside the `/build` folder
+  // if it doesn't exist yet
+  let p = GitRepo.open('build', { init: true });
+  p = p.then(repo => {
+    p = p.then(() => repo.setRemote(remote.name, remote.url));
+    p = p.then(() => repo.hasRef(remote.url, remote.branch).then(exists => {
+      if (exists) {
+        p = p.then(() => repo.fetch(remote.name));
+        p = p.then(() => repo.reset(`${remote.name}/${remote.branch}`, { hard: true }));
+        p = p.then(() => repo.clean({ force: true }));
+      }
+    }));
+
+    // Build the project in RELEASE mode which
+    // generates optimized and minimized bundles
+    process.argv.push('release');
+    p = p.then(() => require('./build').default);
+
+    // Push the contents of the build folder to the remote server via Git
+    p = p.then(() => repo.add('--all .'));
+    p = p.then(() => repo.commit(`Update ${new Date().toISOString()}`));
+    p = p.then(() => repo.push(remote.name, `master:${remote.branch}`));
+
+    resolve(p);
+  });
+
+  p.catch(reject);
+}));

+ 0 - 42
tools/deploy.js

@@ -1,42 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import GitRepo from 'git-repository';
-import task from './lib/task';
-
-// TODO: Update deployment URL
-const remote = {
-  name: 'github',
-  url: 'https://github.com/{user}/{repo}.git',
-  branch: 'gh-pages',
-};
-
-/**
- * Deploy the contents of the `/build` folder to GitHub Pages.
- */
-export default task(async function deploy() {
-  // Initialize a new Git repository inside the `/build` folder
-  // if it doesn't exist yet
-  const repo = await GitRepo.open('build', { init: true });
-  await repo.setRemote(remote.name, remote.url);
-
-  // Fetch the remote repository if it exists
-  if ((await repo.hasRef(remote.url, remote.branch))) {
-    await repo.fetch(remote.name);
-    await repo.reset(`${remote.name}/${remote.branch}`, { hard: true });
-    await repo.clean({ force: true });
-  }
-
-  // Build the project in RELEASE mode which
-  // generates optimized and minimized bundles
-  process.argv.push('release');
-  await require('./build')();
-
-  // Push the contents of the build folder to the remote server via Git
-  await repo.add('--all .');
-  await repo.commit('Update ' + new Date().toISOString());
-  await repo.push(remote.name, 'master:' + remote.branch);
-});

+ 27 - 0
tools/deploy.s3.js

@@ -0,0 +1,27 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
+ */
+
+const s3 = require('s3');
+const task = require('./task');
+
+module.exports = task('deploy', () => Promise.resolve()
+  .then(() => require('./build'))
+  .then(() => new Promise((resolve, reject) => {
+    const client = s3.createClient({
+      s3Options: {
+        region: 'us-east-1',
+        sslEnabled: true,
+      },
+    });
+    const uploader = client.uploadDir({
+      localDir: 'build',
+      deleteRemoved: true,
+      s3Params: { Bucket: 'www.example.com' },
+    });
+    uploader.on('error', reject);
+    uploader.on('end', resolve);
+  }))
+);

+ 0 - 11
tools/lib/copy.js

@@ -1,11 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import ncp from 'ncp';
-
-export default (source, dest) => new Promise((resolve, reject) => {
-  ncp(source, dest, err => err ? reject(err) : resolve());
-});

+ 0 - 19
tools/lib/fs.js

@@ -1,19 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import fs from 'fs';
-import mkdirp from 'mkdirp';
-
-const writeFile = (filename, contents) => new Promise((resolve, reject) => {
-  fs.writeFile(filename, contents, 'utf8', err =>
-    err ? reject(err) : resolve());
-});
-
-const mkdir = (name) => new Promise((resolve, reject) => {
-  mkdirp(name, err => err ? reject(err) : resolve());
-});
-
-export default { writeFile, mkdir };

+ 0 - 52
tools/lib/routes-loader.js

@@ -1,52 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import glob from 'glob';
-import { join } from 'path';
-
-export default function(source) {
-  this.cacheable();
-  const target = this.target;
-  const callback = this.async();
-
-  if (target === 'node') {
-    source = source.replace('import \'babel/polyfill\';', ''); // eslint-disable-line no-param-reassign
-  }
-
-  glob('**/*.{js,jsx}', { cwd: join(__dirname, '../../pages') }, (err, files) => {
-    if (err) {
-      return callback(err);
-    }
-
-    const lines = files.map(file => {
-      let path = '/' + file;
-
-      if (path === '/index.js' || path === '/index.jsx') {
-        path = '/';
-      } else if (path.endsWith('/index.js')) {
-        path = path.substr(0, path.length - 9);
-      } else if (path.endsWith('/index.jsx')) {
-        path = path.substr(0, path.length - 10);
-      } else if (path.endsWith('.js')) {
-        path = path.substr(0, path.length - 3);
-      } else if (path.endsWith('.jsx')) {
-        path = path.substr(0, path.length - 4);
-      }
-
-      if (target === 'node' || path === '/404' || path === '/500') {
-        return `  '${path}': () => require('./pages/${file}'),`;
-      }
-
-      return `  '${path}': () => new Promise(resolve => require(['./pages/${file}'], resolve)),`;
-    });
-
-    if (lines.length) {
-      return callback(null, source.replace(' routes = {', ' routes = {\n' + lines.join('')));
-    }
-
-    return callback(new Error('Cannot find any routes.'));
-  });
-}

+ 0 - 18
tools/lib/task.js

@@ -1,18 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-function format(time) {
-  return time.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1');
-}
-
-export default (fn) => async () => {
-  const start = new Date();
-  console.log(`[${format(start)}] Starting '${fn.name}'...`);
-  await fn();
-  const end = new Date();
-  const time = end.getTime() - start.getTime();
-  console.log(`[${format(end)}] Finished '${fn.name}' after ${time}ms`);
-};

+ 0 - 54
tools/render.js

@@ -1,54 +0,0 @@
-/**
- * React Static Boilerplate
- * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
- */
-
-import glob from 'glob';
-import { join, dirname } from 'path';
-import React from 'react';
-import ReactDOM from 'react-dom/server';
-import Html from '../components/Html';
-import task from './lib/task';
-import fs from './lib/fs';
-
-const DEBUG = !process.argv.includes('release');
-
-function getPages() {
-  return new Promise((resolve, reject) => {
-    glob('**/*.js', { cwd: join(__dirname, '../pages') }, (err, files) => {
-      if (err) {
-        reject(err);
-      } else {
-        const result = files.map(file => {
-          let path = '/' + file.substr(0, file.lastIndexOf('.'));
-          if (path === '/index') {
-            path = '/';
-          } else if (path.endsWith('/index')) {
-            path = path.substr(0, path.lastIndexOf('/index'));
-          }
-          return { path, file };
-        });
-        resolve(result);
-      }
-    });
-  });
-}
-
-async function renderPage(page, component) {
-  const data = {
-    body: ReactDOM.renderToString(component),
-  };
-  const file = join(__dirname, '../build', page.file.substr(0, page.file.lastIndexOf('.')) + '.html');
-  const html = '<!doctype html>\n' + ReactDOM.renderToStaticMarkup(<Html debug={DEBUG} {...data} />);
-  await fs.mkdir(dirname(file));
-  await fs.writeFile(file, html);
-}
-
-export default task(async function render() {
-  const pages = await getPages();
-  const { route } = require('../build/app.node');
-  for (const page of pages) {
-    await route(page.path, renderPage.bind(undefined, page));
-  }
-});

+ 35 - 17
tools/start.js

@@ -1,36 +1,44 @@
 /**
  * React Static Boilerplate
  * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
  */
 
-import browserSync from 'browser-sync';
-import webpack from 'webpack';
-import hygienistMiddleware from 'hygienist-middleware';
-import webpackDevMiddleware from 'webpack-dev-middleware';
-import webpackHotMiddleware from 'webpack-hot-middleware';
+const browserSync = require('browser-sync');
+const webpack = require('webpack');
+const webpackDevMiddleware = require('webpack-dev-middleware');
+const webpackHotMiddleware = require('webpack-hot-middleware');
+const task = require('./task');
+const config = require('./webpack.config');
 
-global.watch = true;
-const webpackConfig = require('./webpack.config')[0];
-const bundler = webpack(webpackConfig);
+task('start', () => new Promise(resolve => {
+  // Hot Module Replacement (HMR) + React Hot Reload
+  if (config.debug) {
+    config.entry.unshift('react-hot-loader/patch', 'webpack-hot-middleware/client');
+    config.module.loaders.find(x => x.loader === 'babel-loader')
+      .query.plugins.unshift('react-hot-loader/babel');
+    config.plugins.push(new webpack.HotModuleReplacementPlugin());
+    config.plugins.push(new webpack.NoErrorsPlugin());
+  }
 
-export default async () => {
-  await require('./build')();
+  const bundler = webpack(config);
 
   browserSync({
     server: {
-      baseDir: 'build',
+      baseDir: 'static',
 
       middleware: [
-        hygienistMiddleware('build'),
-
         webpackDevMiddleware(bundler, {
           // IMPORTANT: dev middleware can't access config, so we should
           // provide publicPath by ourselves
-          publicPath: webpackConfig.output.publicPath,
+          publicPath: config.output.publicPath,
 
           // pretty colored output
-          stats: webpackConfig.stats,
+          stats: config.stats,
 
           // for other settings see
           // http://webpack.github.io/docs/webpack-dev-middleware.html
@@ -38,6 +46,14 @@ export default async () => {
 
         // bundler should be the same as above
         webpackHotMiddleware(bundler),
+
+        // Serve index.html for all unknown requests
+        (req, res, next) => {
+          if (req.headers.accept.startsWith('text/html')) {
+            req.url = '/index.html'; // eslint-disable-line no-param-reassign
+          }
+          next();
+        },
       ],
     },
 
@@ -48,4 +64,6 @@ export default async () => {
       'build/**/*.html',
     ],
   });
-};
+
+  resolve();
+}));

+ 25 - 0
tools/task.js

@@ -0,0 +1,25 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+function format(time) {
+  return time.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1');
+}
+
+function task(name, action) {
+  const start = new Date();
+  console.log(`[${format(start)}] Starting '${name}'...`);
+  return Promise.resolve(action instanceof Function ? action() : action).then(() => {
+    const end = new Date();
+    const time = end.getTime() - start.getTime();
+    console.log(`[${format(end)}] Finished '${name}' after ${time}ms`);
+  }, err => console.error(err.stack));
+}
+
+module.exports = task;

+ 100 - 146
tools/webpack.config.js

@@ -1,186 +1,140 @@
 /**
  * React Static Boilerplate
  * https://github.com/koistya/react-static-boilerplate
- * Copyright (c) Konstantin Tarkus (@koistya) | MIT license
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
  */
 
-import path from 'path';
-import webpack from 'webpack';
-import merge from 'lodash.merge';
-
-const DEBUG = !process.argv.includes('release');
-const VERBOSE = process.argv.includes('verbose');
-const WATCH = global.watch;
-const AUTOPREFIXER_BROWSERS = [
-  'Android 2.3',
-  'Android >= 4',
-  'Chrome >= 35',
-  'Firefox >= 31',
-  'Explorer >= 9',
-  'iOS >= 7',
-  'Opera >= 12',
-  'Safari >= 7.1',
-];
-const JS_LOADER = {
-  test: /\.jsx?$/,
-  include: [
-    path.resolve(__dirname, '../components'),
-    path.resolve(__dirname, '../core'),
-    path.resolve(__dirname, '../pages'),
-    path.resolve(__dirname, '../app.js'),
-    path.resolve(__dirname, '../config.js'),
-  ],
-  loader: 'babel-loader',
-};
+const path = require('path');
+const webpack = require('webpack');
+const extend = require('extend');
+const pkg = require('../package.json');
 
+const isDebug = !(process.argv.includes('--release') || process.argv.includes('-r'));
+const isVerbose = process.argv.includes('--verbose') || process.argv.includes('-v');
 
-// Base configuration
+/**
+ * Webpack configuration (core/main.js => build/bundle.js)
+ * http://webpack.github.io/docs/configuration.html
+ */
 const config = {
+
+  // The base directory
+  context: path.resolve(__dirname, '../'),
+
+  // The entry point for the bundle
+  entry: ['./core/app.js'],
+
+  // Options affecting the output of the compilation
   output: {
-    path: path.join(__dirname, '../build'),
+    path: path.resolve(__dirname, '../build'),
     publicPath: '/',
+    file: 'build/[name].js',
     sourcePrefix: '  ',
   },
-  cache: false,
-  debug: DEBUG,
+
+  // Switch loaders to debug or release mode
+  debug: isDebug,
+
+  // Developer tool to enhance debugging, source maps
+  // http://webpack.github.io/docs/configuration.html#devtool
+  devtool: isDebug ? 'source-map' : false,
+
+  // What information should be printed to the console
   stats: {
     colors: true,
-    reasons: DEBUG,
-    hash: VERBOSE,
-    version: VERBOSE,
+    reasons: isDebug,
+    hash: isVerbose,
+    version: isVerbose,
     timings: true,
-    chunks: VERBOSE,
-    chunkModules: VERBOSE,
-    cached: VERBOSE,
-    cachedAssets: VERBOSE,
+    chunks: isVerbose,
+    chunkModules: isVerbose,
+    cached: isVerbose,
+    cachedAssets: isVerbose,
   },
+
+  // The list of plugins for Webpack compiler
   plugins: [
     new webpack.optimize.OccurenceOrderPlugin(),
     new webpack.DefinePlugin({
-      'process.env.NODE_ENV': DEBUG ? '"development"' : '"production"',
-      '__DEV__': DEBUG,
+      'process.env.NODE_ENV': isDebug ? '"development"' : '"production"',
+      __DEV__: isDebug,
     }),
   ],
+
+  // Options affecting the normal modules
   module: {
     loaders: [
       {
-        test: /[\\\/]app\.js$/,
-        loader: path.join(__dirname, './lib/routes-loader.js'),
-      }, {
+        test: /\.jsx?$/,
+        include: [
+          path.resolve(__dirname, '../components'),
+          path.resolve(__dirname, '../core'),
+          path.resolve(__dirname, '../routes'),
+        ],
+        loader: 'babel-loader',
+        query: extend({}, pkg.babel, { babelrc: false }),
+      },
+      {
+        test: /\.css/,
+        loaders: [
+          'style-loader',
+          `css-loader?${JSON.stringify({
+            sourceMap: isDebug,
+            // CSS Modules https://github.com/css-modules/css-modules
+            modules: true,
+            localIdentName: isDebug ? '[name]_[local]_[hash:base64:3]' : '[hash:base64:4]',
+            // CSS Nano http://cssnano.co/options/
+            minimize: !isDebug,
+          })}`,
+          'postcss-loader',
+        ],
+      },
+      {
         test: /\.json$/,
         loader: 'json-loader',
-      }, {
-        test: /\.txt$/,
-        loader: 'raw-loader',
-      }, {
+      },
+      {
+        test: /\.md$/,
+        loader: path.resolve(__dirname, './webpack.markdown-loader.js'),
+      },
+      {
         test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/,
         loader: 'url-loader?limit=10000',
-      }, {
+      },
+      {
         test: /\.(eot|ttf|wav|mp3)$/,
         loader: 'file-loader',
       },
     ],
   },
-  postcss: function plugins(bundler) {
+
+  // The list of plugins for PostCSS
+  // https://github.com/postcss/postcss
+  postcss(bundler) {
     return [
       require('postcss-import')({ addDependencyTo: bundler }),
-      require('precss')(),
-      require('autoprefixer')({
-        browsers: AUTOPREFIXER_BROWSERS,
-      }),
+      require('postcss-custom-properties')(),
+      require('postcss-calc')(),
+      require('postcss-color-function')(),
+      require('pleeease-filters')(),
+      require('pixrem')(),
+      require('postcss-pseudoelements')(),
+      require('postcss-selector-not')(),
+      require('autoprefixer')(),
     ];
   },
-};
 
-// Configuration for the client-side bundle
-const appConfig = merge({}, config, {
-  entry: [
-    ...(WATCH ? ['webpack-hot-middleware/client'] : []),
-    './app.js',
-  ],
-  output: {
-    filename: 'app.js',
-  },
-  // http://webpack.github.io/docs/configuration.html#devtool
-  devtool: DEBUG ? 'cheap-module-eval-source-map' : false,
-  plugins: [
-    ...config.plugins,
-    ...(DEBUG ? [] : [
-      new webpack.optimize.DedupePlugin(),
-      new webpack.optimize.UglifyJsPlugin({
-        compress: {
-          warnings: VERBOSE,
-        },
-      }),
-      new webpack.optimize.AggressiveMergingPlugin(),
-    ]),
-    ...(WATCH ? [
-      new webpack.HotModuleReplacementPlugin(),
-      new webpack.NoErrorsPlugin(),
-    ] : []),
-  ],
-  module: {
-    loaders: [
-      WATCH ? Object.assign({}, JS_LOADER, {
-        query: {
-          // Wraps all React components into arbitrary transforms
-          // https://github.com/gaearon/babel-plugin-react-transform
-          plugins: ['react-transform'],
-          extra: {
-            'react-transform': {
-              transforms: [
-                {
-                  transform: 'react-transform-hmr',
-                  imports: ['react'],
-                  locals: ['module'],
-                }, {
-                  transform: 'react-transform-catch-errors',
-                  imports: ['react', 'redbox-react'],
-                },
-              ],
-            },
-          },
-        },
-      }) : JS_LOADER,
-      ...config.module.loaders,
-      {
-        test: /\.scss$/,
-        loaders: ['style-loader', 'css-loader', 'postcss-loader'],
-      },
-    ],
-  },
-});
+};
 
-// Configuration for server-side pre-rendering bundle
-const pagesConfig = merge({}, config, {
-  entry: './app.js',
-  output: {
-    filename: 'app.node.js',
-    libraryTarget: 'commonjs2',
-  },
-  target: 'node',
-  node: {
-    console: false,
-    global: false,
-    process: false,
-    Buffer: false,
-    __filename: false,
-    __dirname: false,
-  },
-  externals: /^[a-z][a-z\.\-\/0-9]*$/i,
-  plugins: config.plugins.concat([
-    new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
-  ]),
-  module: {
-    loaders: [
-      JS_LOADER,
-      ...config.module.loaders,
-      {
-        test: /\.scss$/,
-        loaders: ['css-loader', 'postcss-loader'],
-      },
-    ],
-  },
-});
+// Optimize the bundle in release (production) mode
+if (!isDebug) {
+  config.plugins.push(new webpack.optimize.DedupePlugin());
+  config.plugins.push(new webpack.optimize.UglifyJsPlugin({ compress: { warnings: isVerbose } }));
+  config.plugins.push(new webpack.optimize.AggressiveMergingPlugin());
+}
 
-export default [appConfig, pagesConfig];
+module.exports = config;

+ 40 - 0
tools/webpack.markdown-loader.js

@@ -0,0 +1,40 @@
+/**
+ * React Static Boilerplate
+ * https://github.com/koistya/react-static-boilerplate
+ *
+ * Copyright © 2015-2016 Konstantin Tarkus (@koistya)
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE.txt file in the root directory of this source tree.
+ */
+
+const MarkdownIt = require('markdown-it');
+const hljs = require('highlight.js');
+const fm = require('front-matter');
+
+module.exports = function markdown(source) {
+  this.cacheable();
+
+  const md = new MarkdownIt({
+    html: true,
+    linkify: true,
+    highlight: (str, lang) => {
+      if (lang && hljs.getLanguage(lang)) {
+        try {
+          return hljs.highlight(lang, str).value;
+        } catch (err) { console.error(err.stack); }
+      }
+
+      try {
+        return hljs.highlightAuto(str).value;
+      } catch (err) { console.error(err.stack); }
+
+      return '';
+    },
+  });
+
+  const frontmatter = fm(source);
+  frontmatter.attributes.html = md.render(frontmatter.body);
+
+  return `module.exports = ${JSON.stringify(frontmatter.attributes)};`;
+};