├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── components ├── Button │ ├── Button.js │ ├── README.md │ └── package.json ├── Footer │ ├── Footer.js │ └── package.json ├── Layout │ ├── Header.css │ ├── Header.js │ ├── Layout.css │ ├── Layout.js │ ├── Navigation.js │ └── package.json └── Link │ ├── Link.js │ └── package.json ├── core ├── history.js ├── router.js └── store.js ├── database.rules.json ├── docs ├── README.md ├── recipes │ ├── deploy-to-amazon-s3.md │ ├── deploy-to-github-pages.md │ ├── how-to-integrate-material-design-lite.md │ └── how-to-use-sass.md └── routing-and-navigation.md ├── firebase.json ├── main.js ├── package.json ├── pages ├── about │ ├── index.js │ ├── index.md │ └── styles.css ├── error │ ├── index.js │ └── styles.css └── home │ ├── index.js │ ├── index.md │ └── styles.css ├── public ├── apple-touch-icon.png ├── browserconfig.xml ├── crossdomain.xml ├── favicon.ico ├── humans.txt ├── index.ejs ├── robots.txt ├── sitemap.ejs ├── tile-wide.png └── tile.png ├── routes.json ├── run.js ├── test ├── .eslintrc └── spec.js ├── utils ├── markdown-loader.js └── routes-loader.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = space 11 | indent_size = 2 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 3 | * text=auto 4 | 5 | # For the following file types, normalize line endings to LF on 6 | # checkin and prevent conversion to CRLF when they are checked out 7 | # (this is required in order to prevent newline related issues like, 8 | # for example, after the build script is run) 9 | .* text eol=lf 10 | *.css text eol=lf 11 | *.html text eol=lf 12 | *.js text eol=lf 13 | *.json text eol=lf 14 | *.md text eol=lf 15 | *.txt text eol=lf 16 | 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Include your project-specific ignores in this file 2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files 3 | 4 | # Compiled output 5 | public/dist 6 | public/index.html 7 | public/sitemap.xml 8 | 9 | # Node.js and NPM 10 | node_modules 11 | npm-debug.log 12 | 13 | # Firebase 14 | firebase-debug.log 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | env: 5 | - CXX=g++-4.8 6 | addons: 7 | apt: 8 | sources: 9 | - ubuntu-toolchain-r-test 10 | packages: 11 | - g++-4.8 12 | script: 13 | - npm run lint 14 | - npm run test 15 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to React Static Boilerplate 2 | 3 | ♥ **React Static Boilerplate** and want to get involved? Thanks! There are plenty of ways you can 4 | help! 5 | 6 | Please take a moment to review this document in order to make the contribution process easy and 7 | effective for everyone involved. 8 | 9 | Following these guidelines helps to communicate that you respect the time of the developers managing 10 | and developing this open source project. In return, they should reciprocate that respect in 11 | addressing your issue or assessing patches and features. 12 | 13 | 14 | ## Using the issue tracker 15 | 16 | The [issue tracker](https://github.com/kriasoft/react-static-boilerplate/issues) is the preferred 17 | channel for [bug reports](#bugs), [features requests](#features) and [submitting pull 18 | requests](#pull-requests), but please respect the following restrictions: 19 | 20 | * Please **do not** use the issue tracker for personal support requests (use [Stack 21 | Overflow](https://stackoverflow.com/questions/tagged/react-starter-kit)). 22 | 23 | * Please **do not** derail or troll issues. Keep the discussion on topic and respect the opinions 24 | of others. 25 | 26 | * Please **do not** open issues or pull requests regarding the code in 27 | [`React`](https://github.com/facebook/react), 28 | [`Redux`](https://github.com/reactjs/redux), 29 | [`Babel`](https://github.com/babel/babel) or 30 | [`Webpack`](https://github.com/webpack/webpack) (open them in their respective repositories). 31 | 32 | 33 | 34 | ## Bug reports 35 | 36 | A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are 37 | extremely helpful - thank you! 38 | 39 | Guidelines for bug reports: 40 | 41 | 1. **Use the GitHub issue search** — check if the issue has already been reported. 42 | 43 | 2. **Check if the issue has been fixed** — try to reproduce it using the latest `master` or 44 | development branch in the repository. 45 | 46 | 3. **Isolate the problem** — ideally create a [reduced test 47 | case](https://css-tricks.com/reduced-test-cases/) and a live example. 48 | 49 | A good bug report shouldn't leave others needing to chase you up for more information. Please try to 50 | be as detailed as possible in your report. What is your environment? What steps will reproduce the 51 | issue? What browser(s) and OS experience the problem? What would you expect to be the outcome? All 52 | these details will help people to fix any potential bugs. 53 | 54 | Example: 55 | 56 | > Short and descriptive example bug report title 57 | > 58 | > A summary of the issue and the browser/OS environment in which it occurs. If suitable, include the 59 | > steps required to reproduce the bug. 60 | > 61 | > 1. This is the first step 62 | > 2. This is the second step 63 | > 3. Further steps, etc. 64 | > 65 | > `` - a link to the reduced test case 66 | > 67 | > Any other information you want to share that is relevant to the issue being reported. This might 68 | > include the lines of code that you have identified as causing the bug, and potential solutions 69 | > (and your opinions on their merits). 70 | 71 | 72 | 73 | ## Feature requests 74 | 75 | Feature requests are welcome. But take a moment to find out whether your idea fits with the scope 76 | and aims of the project. It's up to *you* to make a strong case to convince the project's developers 77 | of the merits of this feature. Please provide as much detail and context as possible. 78 | 79 | 80 | 81 | ## Pull requests 82 | 83 | Good pull requests - patches, improvements, new features - are a fantastic help. They should remain 84 | focused in scope and avoid containing unrelated commits. 85 | 86 | **Please ask first** before embarking on any significant pull request (e.g. implementing features, 87 | refactoring code, porting to a different language), otherwise you risk spending a lot of time 88 | working on something that the project's developers might not want to merge into the project. 89 | 90 | Please adhere to the coding conventions used throughout a project (indentation, accurate comments, 91 | etc.) and any other requirements (such as test coverage). 92 | 93 | Adhering to the following process is the best way to get your work included in the project: 94 | 95 | 1. [Fork](https://help.github.com/articles/fork-a-repo/) the project, clone your fork, and configure 96 | the remotes: 97 | 98 | ```bash 99 | # Clone your fork of the repo into the current directory 100 | git clone https://github.com//react-static-boilerplate.git 101 | # Navigate to the newly cloned directory 102 | cd react-static-boilerplate 103 | # Assign the original repo to a remote called "upstream" 104 | git remote add upstream https://github.com/kriasoft/react-static-boilerplate.git 105 | ``` 106 | 107 | 2. If you cloned a while ago, get the latest changes from upstream: 108 | 109 | ```bash 110 | git checkout master 111 | git pull upstream master 112 | ``` 113 | 114 | 3. Create a new topic branch (off the main project development branch) to contain your feature, 115 | change, or fix: 116 | 117 | ```bash 118 | git checkout -b 119 | ``` 120 | 121 | 4. Commit your changes in logical chunks. Please adhere to these [git commit message 122 | guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) or your code is 123 | unlikely be merged into the main project. Use Git's [interactive 124 | rebase](https://help.github.com/articles/about-git-rebase/) feature to tidy up your commits 125 | before making them public. 126 | 127 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 128 | 129 | ```bash 130 | git pull [--rebase] upstream master 131 | ``` 132 | 133 | 6. Push your topic branch up to your fork: 134 | 135 | ```bash 136 | git push origin 137 | ``` 138 | 139 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) with a clear title 140 | and description. 141 | 142 | **IMPORTANT**: By submitting a patch, you agree to allow the project owners to license your work 143 | under the terms of the [MIT License](LICENSE.txt). 144 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015-present Kriasoft, LLC. All rights reserved. 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Static Boilerplate   [![Build Status](http://img.shields.io/travis/kriasoft/react-static-boilerplate/master.svg?style=flat-square)](https://travis-ci.org/kriasoft/react-static-boilerplate) [![To-do](https://img.shields.io/waffle/label/kriasoft/react-static-boilerplate/to-do.svg?style=flat-square)](https://waffle.io/kriasoft/react-static-boilerplate) [![Online Chat](http://img.shields.io/badge/chat_room-%23react--static--boilerplate-blue.svg?style=flat-square)](https://gitter.im/kriasoft/react-static-boilerplate) 2 | 3 | > [**React Static Boilerplate**](https://github.com/kriasoft/react-static-boilerplate) (RSB) is an 4 | > opinionated boilerplate and tooling for creating modern stand-alone web applications (aka 5 | > [SPA](https://en.wikipedia.org/wiki/Single-page_application)s) for a serverless architecture. RSB 6 | > significantly reduces cost by eliminating the need for servers such as EC2 instances because the 7 | > entire site can be hosted directly from CDN ([Firebase](https://www.firebase.com/), [GitHub 8 | > Pages](https://pages.github.com/), [Amazon S3](http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html), 9 | > or other similar cloud storage). Sites built with RSB can be fully functional with REST API or 10 | > GraphQL calls to micro-services such as [Amazon Lambda](https://aws.amazon.com/lambda/), 11 | > [Azure Functions](https://azure.microsoft.com/services/functions/), or dynamic Docker endpoints 12 | > hosted on [DigitalOcean](https://www.digitalocean.com/?refcode=eef302dbae9f&utm_source=github&utm_medium=oss_sponsorships&utm_campaign=opencollective). 13 | > RSB demonstrates how to use component-based UI development approach with best of breed 14 | > technologies including [React](http://facebook.github.io/react/), [Redux](http://redux.js.org/), 15 | > [Babel](http://babeljs.io/), [Webpack](https://webpack.github.io/), [Browsersync](https://browsersync.io/), 16 | > [React Hot Loader](http://gaearon.github.io/react-hot-loader/) and more. 17 | 18 | **The work is being sponsored by:** 19 | 20 | 21 | 22 |   23 | 24 | 25 |   26 | 27 | 28 |   29 | 30 | 31 |   32 | 33 | 34 | 35 | 36 | 37 | ### Features 38 | 39 |     ✓ Modern JavaScript syntax ([ES2015](http://babeljs.io/docs/learn-es2015/)+) via [Babel](http://babeljs.io/), modern CSS syntax via [PostCSS](https://github.com/postcss/postcss)
40 |     ✓ Component-based UI architecture via [React](http://facebook.github.io/react/), [Webpack](https://webpack.github.io/) and [CSS Modules](https://github.com/css-modules/css-modules)
41 |     ✓ Application state management /w time-travel debugging via [Redux](http://redux.js.org/) (see [`main.js`](main.js), [`core/store.js`](core/store.js))
42 |     ✓ Routing and navigation via [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp) and [`history`](https://github.com/mjackson/history) (see [`main.js`](main.js), [`core/router.js`](core/router.js), [`utils/routes-loader.js`](utils/routes-loader.js))
43 |     ✓ [Code-splitting](https://github.com/webpack/docs/wiki/code-splitting) and async chunk loading via [Webpack](https://webpack.github.io/) and [ES6 System.import()](http://www.2ality.com/2014/09/es6-modules-final.html)
44 |     ✓ Hot Module Replacement ([HMR](https://webpack.github.io/docs/hot-module-replacement.html)) /w [React Hot Loader](http://gaearon.github.io/react-hot-loader/)
45 |     ✓ Cross-device testing with [Browsersync](https://browsersync.io/) (see [`run.js#start`](run.js))
46 |     ✓ **24/7** community support on [Gitter](https://gitter.im/kriasoft/react-static-boilerplate); customization requests on [Codementor](https://www.codementor.io/koistya)
47 | 48 | **Demo**: https://rsb.kriasoft.com  |  **View** [docs](./docs)  |  **Follow us** on 49 | [Gitter](https://gitter.im/kriasoft/react-static-boilerplate), [Twitter](https://twitter.com/ReactStatic), 50 | or [ProductHunt](https://www.producthunt.com/tech/react-static-boilerplate)  |  51 | **Learn** to [React.js and ES6](#learn-reactjs-and-es6) 52 | 53 | 54 | ### Directory Layout 55 | 56 | ```shell 57 | . 58 | ├── /components/ # Shared or generic UI components 59 | │ ├── /Button/ # Button component 60 | │ ├── /Layout/ # Website layout component 61 | │ ├── /Link / # Link component to be used insted of 62 | │ └── /... # etc. 63 | ├── /core/ # Core framework 64 | │ ├── /history.js # Handles client-side navigation 65 | │ ├── /router.js # Handles routing and data fetching 66 | │ └── /store.js # Application state manager (Redux) 67 | ├── /node_modules/ # 3rd-party libraries and utilities 68 | ├── /pages/ # React components for web pages 69 | │ ├── /about/ # About page 70 | │ ├── /error/ # Error page 71 | │ ├── /home/ # Home page 72 | │ └── /... # etc. 73 | ├── /public/ # Static files such as favicon.ico etc. 74 | │ ├── /dist/ # The folder for compiled output 75 | │ ├── favicon.ico # Application icon to be displayed in bookmarks 76 | │ ├── robots.txt # Instructions for search engine crawlers 77 | │ └── /... # etc. 78 | ├── /test/ # Unit and integration tests 79 | ├── /utils/ # Utility and helper classes 80 | │── main.js # React application entry point 81 | │── package.json # The list of project dependencies and NPM scripts 82 | │── routes.json # This list of application routes 83 | │── run.js # Build automation script, e.g. `node run build` 84 | └── webpack.config.js # Bundling and optimization settings for Webpack 85 | ``` 86 | 87 | 88 | ### Getting Started 89 | 90 | **Step 1**. Make sure that you have [Node.js](https://nodejs.org/) v6 or newer installed on your 91 | machine. 92 | 93 | **Step 2**. Clone this repository or use [Yeoman 94 | generator](https://github.com/kriasoft/react-static-boilerplate/tree/generator-react-static) to 95 | bootstrap your project: 96 | 97 | ```shell 98 | $ git clone -o react-static-boilerplate -b master --single-branch \ 99 | https://github.com/kriasoft/react-static-boilerplate.git MyApp 100 | $ cd MyApp 101 | $ npm install # Install project dependencies listed in package.json 102 | ``` 103 | 104 |

——— or ———

105 | 106 | ```shell 107 | $ npm install -g yo 108 | $ npm install -g generator-react-static 109 | $ mkdir MyApp 110 | $ cd MyApp 111 | $ yo react-static 112 | ``` 113 | 114 | **Step 3**. Compile and launch your app by running: 115 | 116 | ```shell 117 | $ node run # Same as `npm start` or `node run start` 118 | ``` 119 | 120 | You can also test your app in release (production) mode by running `node run start --release` or 121 | with HMR and React Hot Loader disabled by running `node run start --no-hmr`. The app should become 122 | available at [http://localhost:3000/](http://localhost:3000/). 123 | 124 | 125 | ### How to Test 126 | 127 | The unit tests are powered by [chai](http://chaijs.com/) and [mocha](http://mochajs.org/). 128 | 129 | ```shell 130 | $ npm run lint # Check JavaScript and CSS code for potential issues 131 | $ npm run test # Run unit tests. Or, `npm run test:watch` 132 | ``` 133 | 134 | 135 | ### How to Deploy 136 | 137 | Update `publish` script in the [`run.js`](run.js) file with your full Firebase project name as found 138 | in your [Firebase console](https://console.firebase.google.com/). Note that this may have an 139 | additional identifier suffix than the shorter name you've provided. Then run: 140 | 141 | ```shell 142 | $ node run publish # Build and publish the website to Firebase, same as `npm run publish` 143 | ``` 144 | 145 | The first time you publish, you will be prompted to authenticate with Google and generate an 146 | authentication token in order for the publish script to continue. 147 | 148 | ![publish](https://koistya.github.io/files/react-static-boilerplate-publish.gif) 149 | 150 | If you need just to build the project without publishing it, run: 151 | 152 | ```shell 153 | $ node run build # Or, `node run build --release` for production build 154 | ``` 155 | 156 | 157 | ### How to Update 158 | 159 | You can always fetch and merge the recent changes from this repo back into your own project: 160 | 161 | ```shell 162 | $ git checkout master 163 | $ git fetch react-static-boilerplate 164 | $ git merge react-static-boilerplate/master 165 | $ npm install 166 | ``` 167 | 168 | 169 | ### Learn React.js and ES6 170 | 171 | :mortar_board:   **[React.js Training Program](http://www.reactjsprogram.com/?asdf=36750_q0pu0tfa)** by Tyler McGinnis
172 | :mortar_board:   **[React for Beginners](https://reactforbeginners.com/friend/konstantin)** and **[ES6 Training Course](https://es6.io/friend/konstantin)** by Wes Bos
173 | :green_book:   **[React: Up & Running: Building Web Applications](http://amzn.to/2bBgqhl)** by Stoyan Stefanov (Aug, 2016)
174 | :green_book:   **[Getting Started with React](http://amzn.to/2bmwP5V)** by Doel Sengupta and Manu Singhal (Apr, 2016)
175 | :green_book:   **[You Don't Know JS: ES6 & Beyond](http://amzn.to/2bBfVnp)** by Kyle Simpson (Dec, 2015)
176 | 177 | 178 | ### Related Projects 179 | 180 | * [React App SDK](https://github.com/kriasoft/react-app) — Create React apps with just a single dev dependency and zero configuration 181 | * [React Starter Kit](https://github.com/kriasoft/react-starter-kit) — Isomorphic web app boilerplate (Node.js, React, GraphQL, Webpack, CSS Modules) 182 | * [ASP.NET Core Starter Kit](https://github.com/kriasoft/aspnet-starter-kit) — Cross-platform single-page application boilerplate (ASP.NET Core, React, Redux) 183 | * [Babel Starter Kit](https://github.com/kriasoft/babel-starter-kit) — JavaScript library boilerplate (ES2015, Babel, Rollup, Mocha, Chai, Sinon, Rewire) 184 | * [Universal Router](https://github.com/kriasoft/universal-router) — Isomorphic router for web and single-page applications (SPA) 185 | * [History](https://github.com/mjackson/history) — HTML5 History API wrapper library that handle navigation in single-page apps 186 | 187 | 188 | ### How to Contribute 189 | 190 | Anyone and everyone is welcome to [contribute](CONTRIBUTING.md) to this project. The best way to 191 | start is by checking our [open issues](https://github.com/kriasoft/react-static-boilerplate/issues), 192 | [submit a new issues](https://github.com/kriasoft/react-static-boilerplate/issues/new?labels=bug) or 193 | [feature request](https://github.com/kriasoft/react-static-boilerplate/issues/new?labels=enhancement), 194 | participate in discussions, upvote or downvote the issues you like or dislike, send [pull 195 | requests](CONTRIBUTING.md#pull-requests). 196 | 197 | 198 | ### License 199 | 200 | Copyright © 2015-present Kriasoft, LLC. This source code is licensed under the MIT license found in 201 | the [LICENSE.txt](https://github.com/kriasoft/react-static-boilerplate/blob/master/LICENSE.txt) file. 202 | 203 | --- 204 | Made with ♥ by Konstantin Tarkus ([@koistya](https://twitter.com/koistya)) and [contributors](https://github.com/kriasoft/react-static-boilerplate/graphs/contributors) 205 | -------------------------------------------------------------------------------- /components/Button/Button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React, { PropTypes } from 'react'; 12 | import cx from 'classnames'; 13 | import Link from '../Link'; 14 | 15 | class Button extends React.Component { 16 | 17 | static propTypes = { 18 | component: PropTypes.oneOf([ 19 | PropTypes.string, 20 | PropTypes.element, 21 | PropTypes.func, 22 | ]), 23 | type: PropTypes.oneOf(['raised', 'fab', 'mini-fab', 'icon']), 24 | to: PropTypes.oneOf([PropTypes.string, PropTypes.object]), 25 | href: PropTypes.string, 26 | className: PropTypes.string, 27 | colored: PropTypes.bool, 28 | primary: PropTypes.bool, 29 | accent: PropTypes.bool, 30 | ripple: PropTypes.bool, 31 | children: PropTypes.node, 32 | }; 33 | 34 | componentDidMount() { 35 | window.componentHandler.upgradeElement(this.root); 36 | } 37 | 38 | componentWillUnmount() { 39 | window.componentHandler.downgradeElements(this.root); 40 | } 41 | 42 | render() { 43 | const { component, type, className, colored, to, href, 44 | primary, accent, ripple, children, ...other } = this.props; 45 | return React.createElement( 46 | component || (to ? Link : (href ? 'a' : 'button')), // eslint-disable-line no-nested-ternary 47 | { 48 | ref: node => (this.root = node), 49 | className: cx( 50 | 'mdl-button mdl-js-button', 51 | type && `mdl-button--${type}`, 52 | { 53 | 'mdl-button--colored': colored, 54 | 'mdl-button--primary': primary, 55 | 'mdl-button--accent': accent, 56 | 'mdl-js-ripple-effect': ripple, 57 | }, 58 | className 59 | ), 60 | to, 61 | href, 62 | ...other, 63 | }, 64 | children 65 | ); 66 | } 67 | 68 | } 69 | 70 | export default Button; 71 | -------------------------------------------------------------------------------- /components/Button/README.md: -------------------------------------------------------------------------------- 1 | ## Button Component 2 | 3 | An enhanced version of the standard HTML ` 13 | 14 | 15 | ``` 16 | 17 | ### Options 18 | 19 | | Prop | Type | Default | Possible Values 20 | | ------------- | -------- | ----------- | --------------------------------------------- 21 | | **component** | | `button` | React component to use, e.g. `a` 22 | | **type** | `string` | `flat` | `raised`, `fab`, `mini-fab`, `icon` 23 | | **to** | `string` | `undefined` | A URL string 24 | | **colored** | `bool` | `false` | `true`, `false` 25 | | **primary** | `bool` | `false` | `true`, `false` 26 | | **accent** | `bool` | `false` | `true`, `false` 27 | | **ripple** | `bool` | `false` | `true`, `false` 28 | -------------------------------------------------------------------------------- /components/Button/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Button", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Button.js" 6 | } 7 | -------------------------------------------------------------------------------- /components/Footer/Footer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React from 'react'; 12 | import Link from '../Link'; 13 | 14 | function Footer() { 15 | return ( 16 |
63 | ); 64 | } 65 | 66 | export default Footer; 67 | -------------------------------------------------------------------------------- /components/Footer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Footer", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Footer.js" 6 | } 7 | -------------------------------------------------------------------------------- /components/Layout/Header.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | .row { 12 | padding: 40px; 13 | } 14 | 15 | .title { 16 | color: #fff; 17 | text-decoration: none; 18 | } 19 | 20 | @media screen and (max-width: 1024px) { 21 | 22 | .header { 23 | display: flex; 24 | } 25 | 26 | .row { 27 | padding: 0 16px; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /components/Layout/Header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React from 'react'; 12 | import Navigation from './Navigation'; 13 | import Link from '../Link'; 14 | import s from './Header.css'; 15 | 16 | class Header extends React.Component { 17 | 18 | componentDidMount() { 19 | window.componentHandler.upgradeElement(this.root); 20 | } 21 | 22 | componentWillUnmount() { 23 | window.componentHandler.downgradeElements(this.root); 24 | } 25 | 26 | render() { 27 | return ( 28 |
(this.root = node)}> 29 |
30 | 31 | React Static Boilerplate 32 | 33 |
34 | 35 |
36 |
37 | ); 38 | } 39 | 40 | } 41 | 42 | export default Header; 43 | -------------------------------------------------------------------------------- /components/Layout/Layout.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | .content { 12 | margin: 0 auto; 13 | max-width: 1000px; 14 | width: 100%; 15 | } 16 | -------------------------------------------------------------------------------- /components/Layout/Layout.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React, { PropTypes } from 'react'; 12 | import cx from 'classnames'; 13 | import Header from './Header'; 14 | import Footer from '../Footer'; 15 | import s from './Layout.css'; 16 | 17 | class Layout extends React.Component { 18 | 19 | static propTypes = { 20 | className: PropTypes.string, 21 | }; 22 | 23 | componentDidMount() { 24 | window.componentHandler.upgradeElement(this.root); 25 | } 26 | 27 | componentWillUnmount() { 28 | window.componentHandler.downgradeElements(this.root); 29 | } 30 | 31 | render() { 32 | return ( 33 |
(this.root = node)}> 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | ); 43 | } 44 | } 45 | 46 | export default Layout; 47 | -------------------------------------------------------------------------------- /components/Layout/Navigation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React from 'react'; 12 | import Link from '../Link'; 13 | 14 | class Navigation extends React.Component { 15 | 16 | componentDidMount() { 17 | window.componentHandler.upgradeElement(this.root); 18 | } 19 | 20 | componentWillUnmount() { 21 | window.componentHandler.downgradeElements(this.root); 22 | } 23 | 24 | render() { 25 | return ( 26 | 30 | ); 31 | } 32 | 33 | } 34 | 35 | export default Navigation; 36 | -------------------------------------------------------------------------------- /components/Layout/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Layout", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Layout.js" 6 | } 7 | -------------------------------------------------------------------------------- /components/Link/Link.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React, { PropTypes } from 'react'; 12 | import history from '../../core/history'; 13 | 14 | class Link extends React.Component { 15 | 16 | static propTypes = { 17 | to: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, 18 | onClick: PropTypes.func, 19 | }; 20 | 21 | handleClick = (event) => { 22 | if (this.props.onClick) { 23 | this.props.onClick(event); 24 | } 25 | 26 | if (event.button !== 0 /* left click */) { 27 | return; 28 | } 29 | 30 | if (event.metaKey || event.altKey || event.ctrlKey || event.shiftKey) { 31 | return; 32 | } 33 | 34 | if (event.defaultPrevented === true) { 35 | return; 36 | } 37 | 38 | event.preventDefault(); 39 | 40 | if (this.props.to) { 41 | history.push(this.props.to); 42 | } else { 43 | history.push({ 44 | pathname: event.currentTarget.pathname, 45 | search: event.currentTarget.search, 46 | }); 47 | } 48 | }; 49 | 50 | render() { 51 | const { to, ...props } = this.props; // eslint-disable-line no-use-before-define 52 | return ; 53 | } 54 | 55 | } 56 | 57 | export default Link; 58 | -------------------------------------------------------------------------------- /components/Link/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Link", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "./Link.js" 6 | } 7 | -------------------------------------------------------------------------------- /core/history.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import createBrowserHistory from 'history/lib/createBrowserHistory'; 12 | import useQueries from 'history/lib/useQueries'; 13 | 14 | export default useQueries(createBrowserHistory)(); 15 | -------------------------------------------------------------------------------- /core/router.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React from 'react'; 12 | 13 | function decodeParam(val) { 14 | if (!(typeof val === 'string' || val.length === 0)) { 15 | return val; 16 | } 17 | 18 | try { 19 | return decodeURIComponent(val); 20 | } catch (err) { 21 | if (err instanceof URIError) { 22 | err.message = `Failed to decode param '${val}'`; 23 | err.status = 400; 24 | } 25 | 26 | throw err; 27 | } 28 | } 29 | 30 | // Match the provided URL path pattern to an actual URI string. For example: 31 | // matchURI({ path: '/posts/:id' }, '/dummy') => null 32 | // matchURI({ path: '/posts/:id' }, '/posts/123') => { id: 123 } 33 | function matchURI(route, path) { 34 | const match = route.pattern.exec(path); 35 | 36 | if (!match) { 37 | return null; 38 | } 39 | 40 | const params = Object.create(null); 41 | 42 | for (let i = 1; i < match.length; i++) { 43 | params[route.keys[i - 1].name] = match[i] !== undefined ? decodeParam(match[i]) : undefined; 44 | } 45 | 46 | return params; 47 | } 48 | 49 | // Find the route matching the specified location (context), fetch the required data, 50 | // instantiate and return a React component 51 | function resolve(routes, context) { 52 | for (const route of routes) { 53 | const params = matchURI(route, context.error ? '/error' : context.pathname); 54 | 55 | if (!params) { 56 | continue; 57 | } 58 | 59 | // Check if the route has any data requirements, for example: 60 | // { path: '/tasks/:id', data: { task: 'GET /api/tasks/$id' }, page: './pages/task' } 61 | if (route.data) { 62 | // Load page component and all required data in parallel 63 | const keys = Object.keys(route.data); 64 | return Promise.all([ 65 | route.load(), 66 | ...keys.map(key => { 67 | const query = route.data[key]; 68 | const method = query.substring(0, query.indexOf(' ')); // GET 69 | const url = query.substr(query.indexOf(' ') + 1); // /api/tasks/$id 70 | // TODO: Replace query parameters with actual values coming from `params` 71 | return fetch(url, { method }).then(resp => resp.json()); 72 | }), 73 | ]).then(([Page, ...data]) => { 74 | const props = keys.reduce((result, key, i) => ({ ...result, [key]: data[i] }), {}); 75 | return ; 76 | }); 77 | } 78 | 79 | return route.load().then(Page => ); 80 | } 81 | 82 | const error = new Error('Page not found'); 83 | error.status = 404; 84 | return Promise.reject(error); 85 | } 86 | 87 | export default { resolve }; 88 | -------------------------------------------------------------------------------- /core/store.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import { createStore } from 'redux'; 12 | 13 | // Centralized application state 14 | // For more information visit http://redux.js.org/ 15 | const store = createStore((state, action) => { 16 | // TODO: Add action handlers (aka "reducers") 17 | switch (action) { 18 | case 'COUNT': 19 | return { ...state, count: (state.count || 0) + 1 }; 20 | default: 21 | return state; 22 | } 23 | }); 24 | 25 | export default store; 26 | -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | ".read": "auth != null", 4 | ".write": "auth != null" 5 | } 6 | } -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Table of Contents 2 | 3 | - [Routing and Navigation](routing-and-navigation.md) 4 | - Recipes 5 | - [How to Publish Website to Amazon S3](recipes/deploy-to-amazon-s3.md) 6 | - [How to Publish Website to GitHub Pages](recipes/deploy-to-github-pages.md) 7 | - [How to Integrate Material Design Lite (MDL)](recipes/how-to-integrate-material-design-lite.md) 8 | - [How to Use Sass/SCSS](recipes/how-to-use-sass.md) 9 | -------------------------------------------------------------------------------- /docs/recipes/deploy-to-amazon-s3.md: -------------------------------------------------------------------------------- 1 | ## How to Publish Website to Amazon S3 2 | 3 | ### Step 1 4 | 5 | Configure S3 bucket for hosting a static site: 6 | 7 | http://docs.aws.amazon.com/AmazonS3/latest/dev/WebsiteHosting.html 8 | 9 | ### Step 2 10 | 11 | Install [`s3`](https://github.com/andrewrk/node-s3-client) npm module: 12 | 13 | ```sh 14 | $ npm install s3 --save-dev 15 | ``` 16 | 17 | ### Step 3 18 | 19 | Add deployment script to `run.js`: 20 | 21 | ```js 22 | tasks.set('publish', () => { 23 | global.DEBUG = process.argv.includes('--debug') || false; 24 | const s3 = require('s3'); 25 | return run('build').then(() => new Promise((resolve, reject) => { 26 | const client = s3.createClient({ 27 | s3Options: { 28 | region: 'us-east-1', 29 | sslEnabled: true, 30 | }, 31 | }); 32 | const uploader = client.uploadDir({ 33 | localDir: 'public', 34 | deleteRemoved: true, 35 | s3Params: { Bucket: 'www.example.com' }, // TODO: Update deployment URL 36 | }); 37 | uploader.on('error', reject); 38 | uploader.on('end', resolve); 39 | })); 40 | }); 41 | ``` 42 | 43 | Step 4 44 | 45 | Whenever you need to compile and publish your site to Amazon S3 simply run: 46 | 47 | ```sh 48 | $ node run publish 49 | ``` 50 | 51 | ![publish](https://koistya.github.io/files/react-static-boilerplate-publish.gif) 52 | -------------------------------------------------------------------------------- /docs/recipes/deploy-to-github-pages.md: -------------------------------------------------------------------------------- 1 | ## How to Publish Website to [GitHub Pages](https://pages.github.com/) 2 | 3 | ### Step 1 4 | 5 | Add deployment script to `run.js`: 6 | 7 | ```js 8 | tasks.set('publish', () => { 9 | const remote = { 10 | url: 'https://github.com//.git', // TODO: Update deployment URL 11 | branch: 'gh-pages', 12 | }; 13 | global.DEBUG = process.argv.includes('--debug') || false; 14 | const spawn = require('child_process').spawn; 15 | const opts = { cwd: path.resolve(__dirname, './public'), stdio: ['ignore', 'inherit', 'inherit'] }; 16 | const git = (...args) => new Promise((resolve, reject) => { 17 | spawn('git', args, opts).on('close', code => { 18 | if (code === 0) { 19 | resolve(); 20 | } else { 21 | reject(new Error(`git ${args.join(' ')} => ${code} (error)`)); 22 | } 23 | }); 24 | }); 25 | 26 | return Promise.resolve() 27 | .then(() => run('clean')) 28 | .then(() => git('init', '--quiet')) 29 | .then(() => git('config', '--get', 'remote.origin.url') 30 | .then(() => git('remote', 'set-url', 'origin', remote.url)) 31 | .catch(() => git('remote', 'add', 'origin', remote.url)) 32 | ) 33 | .then(() => git('ls-remote', '--exit-code', remote.url, 'master') 34 | .then(() => Promise.resolve() 35 | .then(() => git('fetch', 'origin')) 36 | .then(() => git('reset', `origin/${remote.branch}`, '--hard')) 37 | .then(() => git('clean', '--force')) 38 | ) 39 | .catch(() => Promise.resolve()) 40 | ) 41 | .then(() => run('build')) 42 | .then(() => git('add', '.', '--all')) 43 | .then(() => git('commit', '--message', new Date().toUTCString()) 44 | .catch(() => Promise.resolve())) 45 | .then(() => git('push', 'origin', `HEAD:${remote.branch}`, '--force', '--set-upstream')); 46 | }); 47 | ``` 48 | 49 | ### Step 2 50 | 51 | Whenever you need to compile and publish your site to GitHub Pages simply run: 52 | 53 | ```sh 54 | $ node run publish 55 | ``` 56 | 57 | ![publish](https://koistya.github.io/files/react-static-boilerplate-publish.gif) 58 | -------------------------------------------------------------------------------- /docs/recipes/how-to-integrate-material-design-lite.md: -------------------------------------------------------------------------------- 1 | ## How to Integrate Material Design Lite (MDL) 2 | 3 | ### Step 1 4 | 5 | Install [`react-mdl`](http://www.npmjs.com/package/react-mdl) npm package: 6 | 7 | ```sh 8 | $ npm install react-mdl --save 9 | ``` 10 | 11 | Add [Material Design Lite](https://getmdl.io) (MDL) CSS and JavaScript files as entry points 12 | in [`webpack.config.js`](../../webpack.config.js): 13 | 14 | ```js 15 | const config = { 16 | 17 | entry: [ 18 | '!!style!css!react-mdl/extra/material.min.css', // <== 19 | 'react-mdl/extra/material.min.js', // <== 20 | './main.js', 21 | ], 22 | 23 | ... 24 | 25 | }; 26 | ``` 27 | 28 | **Note**: Due to compatibility issues of the Layout component in MDL `v1.1.x` with React, you must use 29 | the the patched version of MDL from `react-mdl` npm package (as opposed to 30 | [`material-design-lite`](https://www.npmjs.com/package/material-design-lite)). This is a [known 31 | issue](https://github.com/google/material-design-lite/pull/1357), which will be fixed in `v2.x`. 32 | 33 | ### Step 2 34 | 35 | Decorate your UI elements with MDL classes, for example: 36 | 37 | #### Badge 38 | 39 | ```jsx 40 | Inbox 41 | ``` 42 | 43 | #### Grid 44 | 45 | ```jsx 46 |
47 |
Content
48 |
goes
49 |
here
50 |
51 | ``` 52 | 53 | ### List 54 | 55 | ```jsx 56 |
    57 |
  • 58 |
  • 59 |
  • 60 |
61 | ``` 62 | 63 | ### Step 3 64 | 65 | Create stand-alone React components for MDL elements that rely on JavaScript code to operate (see 66 | MDL [source code](https://github.com/google/material-design-lite/tree/mdl-1.x/src)). After such 67 | component mounts into the DOM, it need to notify MDL runtime that the underlying DOM elements can be 68 | directly manipulated by MDL; likewise right before the React component is being removed from the DOM 69 | it needs to notify MDL so it could do proper clean up. MDL provides `upgradeElement(node)` and 70 | `downgradeElements(nodes)` API methods for that. For example, to implement a [Button](../../components/Button) 71 | component you would write code similar to this: 72 | 73 | #### `components/Button/Button.js` 74 | 75 | ```js 76 | import React, { PropTypes } from 'react'; 77 | import classNames from 'classnames'; 78 | 79 | class Button extends React.Component { 80 | 81 | static propTypes = { 82 | className: PropTypes.string, 83 | primary: PropTypes.bool, 84 | }; 85 | 86 | componentDidMount() { 87 | window.componentHandler.upgradeElement(this.root); // <== 88 | } 89 | 90 | componentWillUnmount() { 91 | window.componentHandler.downgradeElements(this.root); // <== 92 | } 93 | 94 | render() { 95 | const { className, href, primary, children, ...other } = this.props; 96 | return React.createElement( 97 | href ? 'a' : 'button', 98 | { 99 | ref: node => (this.root = node), // <== 100 | className: classNames({ 101 | 'mdl-button mdl-js-button': true, 102 | 'mdl-button--primary': primary, 103 | }), 104 | href, 105 | ...other 106 | }, 107 | children 108 | ); 109 | } 110 | 111 | } 112 | 113 | export default Button; 114 | ``` 115 | 116 | #### Usage Example: 117 | 118 | ```js 119 | import Button from './components/Button'; 120 | 121 | function MyComponent() { 122 | return ( 123 |
124 | 125 | 126 |
127 | ); 128 | } 129 | 130 | export default MyComponent; 131 | ``` 132 | 133 | ### Step 4 134 | 135 | Extend MDL components with your own styles (via [CSS Modules](https://github.com/css-modules/css-modules) 136 | or [inline styles](https://facebook.github.io/react/tips/inline-styles.html)): 137 | 138 | #### `components/Spinner/Spinner.css` 139 | 140 | ```css 141 | .spinner { 142 | border: 1px solid red; 143 | } 144 | ``` 145 | 146 | #### `components/Spinner/Spinner.js` 147 | 148 | ```js 149 | import React, { PropTypes } from 'react'; 150 | import classNames from 'classnames'; 151 | import s from './Spinner.css'; 152 | 153 | class Spinner extends React.Component { 154 | 155 | static propTypes = { 156 | isActive: PropTypes.bool, 157 | }; 158 | 159 | componentDidMount() { 160 | window.componentHandler.upgradeElement(this.root); 161 | } 162 | 163 | componentWillUnmount() { 164 | window.componentHandler.downgradeElements(this.root); 165 | } 166 | 167 | render() { 168 | const { className, isActive, ...other } = this.props; 169 | return ( 170 |
(this.root = node)} 172 | className={classNames({ 173 | 'mdl-spinner mdl-js-spinner': true, 174 | 'is-active': isActive, 175 | s.spinner, 176 | className, 177 | })} 178 | {...other} 179 | /> 180 | ); 181 | } 182 | 183 | } 184 | 185 | export default Spinner; 186 | ``` 187 | -------------------------------------------------------------------------------- /docs/recipes/how-to-use-sass.md: -------------------------------------------------------------------------------- 1 | ## How to Use Sass/SCSS 2 | 3 | > **Note**: Using plain CSS via [PostCSS](http://postcss.org/) is recommended approach because it 4 | reduces the size of the tech stack used in the project, enforces you to learn vanilla CSS syntax 5 | with modern CSS Level 3+ features that allow you doing everything you would normally do with 6 | Sass/SCSS. Also compilation of plain `.css` files should work faster with `postcss` pre-processor 7 | than `node-sass`. 8 | 9 | ### Step 1 10 | 11 | Install [`node-sass`](https://github.com/sass/node-sass) and 12 | [`sass-loader`](https://github.com/jtangelder/sass-loader) modules as dev dependencies: 13 | 14 | ```sh 15 | $ npm install node-sass --save-dev 16 | $ npm install sass-loader --save-dev 17 | ``` 18 | 19 | ### Step 2 20 | 21 | Update [`webpack.config.js`](../../webpack.config.js) file to use `sass-loader` for `.scss` files: 22 | 23 | ```js 24 | const config = { 25 | ... 26 | module: { 27 | loaders: [ 28 | ... 29 | { 30 | test: /\.scss$/, 31 | loaders: [ 32 | 'style-loader', 33 | `css-loader?${JSON.stringify({ sourceMap: isDebug, minimize: !isDebug })}`, 34 | 'postcss-loader?pack=sass', 35 | 'sass-loader', 36 | ], 37 | }, 38 | ... 39 | ] 40 | } 41 | ... 42 | } 43 | ``` 44 | 45 | ### Step 3 46 | 47 | Add one more configuration (pack) for [PostCSS](https://github.com/postcss/postcss) named `sass` to 48 | enable [Autoprefixer](https://github.com/postcss/autoprefixer) for your `.scss` files: 49 | 50 | ```js 51 | const config = { 52 | ... 53 | postcss(bundler) { 54 | return { 55 | defaults: [ 56 | ... 57 | ], 58 | sass: [ 59 | require('autoprefixer')(), 60 | ], 61 | }; 62 | } 63 | ... 64 | } 65 | ``` 66 | 67 | For more information visit https://github.com/jtangelder/sass-loader and https://github.com/sass/node-sass 68 | -------------------------------------------------------------------------------- /docs/routing-and-navigation.md: -------------------------------------------------------------------------------- 1 | ## Routing and Navigation 2 | 3 | [React Static Boilerplate](https://github.com/kriasoft/react-static-boilerplate) (RSB) uses a 4 | custom minimalistic (under 100 LOC) declarative routing approach that is easy to customize. It's 5 | comprised of five major parts: 6 | 7 | * **Routes** — the list of application routes in JSON format (see [`routes.json`](../routes.json)) 8 | * **Routes Loader** — a custom loader for Webpack that converts routes from JSON to JavaScript on 9 | build (see [`utils/routes-loader.js`](../utils/routes-loader.js)) 10 | * **URL Matcher** — a function that checks if a given URI matches to the route's `path` string (see 11 | `matchURI()` method in [`core/router.js`](../core/router.js)) 12 | * **Route Resolver** — a function just resolves a URI string to the first matched route, fetches 13 | all the required data and returns a React component to render (see `resolve()` method in 14 | [`core/router.js`](../core/router.js)) 15 | * **History** — client-side navigation library powered by [`history`](https://github.com/ReactJSTraining/history) 16 | npm module (the same one used in `react-router`) that helps with transitioning between pages 17 | (screens) in the browser without causing full-page refresh (see [`core/history.js`](../core/history.js)) 18 | 19 | The list of routes is just an array where each item contains a `path` - parametrized URL path string 20 | and a `page` field that points to a corresponding UI (page or screen) component within the project's 21 | file structure. For a simple to-do app, this list of routes may look like this (`routes.json`): 22 | 23 | ```json 24 | [ 25 | { 26 | "path": "/", 27 | "page": "./pages/home" 28 | }, 29 | { 30 | "path": "/tasks/:status(pending|completed)?", 31 | "page": "./pages/tasks/list" 32 | }, 33 | { 34 | "path": "/tasks/new", 35 | "page": "./pages/tasks/new" 36 | }, 37 | { 38 | "path": "/tasks/:id", 39 | "page": "./pages/tasks/details" 40 | } 41 | ] 42 | ``` 43 | 44 | This list of routes is referenced inside the main application file (where the React app is beeing 45 | bootstrapped) by using [`routes-loader`](../utils/routes-loader.js) (see [`main.js`](../main.js)): 46 | 47 | ```js 48 | import routes from '!!./utils/routes-loader!./routes.json'; 49 | ``` 50 | 51 | If you're new to Webpack's "loader" concept, please refer to https://webpack.github.io/docs/loaders 52 | 53 | The [`routes-loader`](../utils/routes-loader.js) performs three tasks: 54 | 55 | * Converts JSON-based routes into JavaScript 56 | * Converts parametrized URL path strings into regular expressions by using 57 | [`path-to-regexp`](https://github.com/pillarjs/path-to-regexp) 58 | * Wraps page/screen UI components' path strings into either `require.ensure(..)` (Webpack 1.x) or 59 | `System.import(..)` (Webpack 2.x). For more information see 60 | [code-splitting](https://webpack.github.io/docs/code-splitting) in Webpack docs. 61 | 62 | For example, a route like this: 63 | 64 | ```json 65 | { 66 | "path": "/tasks/:id", 67 | "page": "./pages/tasks/details" 68 | } 69 | ``` 70 | 71 | Will become: 72 | 73 | ```js 74 | { 75 | path: '/tasks/:id', 76 | pattern: /^\/tasks\/((?:[^\/]+?))(?:\/(?=$))?$/i, 77 | keys: [{ name: 'id', pattern: '[^\\/]+?', ... }], 78 | page: './pages/tasks/details', 79 | load: function() { return System.import('./pages/tasks/details'); } 80 | } 81 | ``` 82 | 83 | Given the list of routes you can ask the router to "resolve" the given URI string to a React 84 | component. The code for that may look something like this: 85 | 86 | ```js 87 | router.resolve(routes, { pathname: '/tasks/123' }).then(component => { 88 | ReactDOM.render(component, container); 89 | }); 90 | ``` 91 | 92 | The `resolve(routes, context)` method will find the first route from the list matching to the 93 | `/tasks/123` URI string, execute its `load()` method, and return corresponding React component as a 94 | result wrapped into ES6 Promise (see [`core/router.js`](../core/router.js). 95 | 96 | If a route contains some REST API or GraphQL endpoints as data requirements for the given route, 97 | the `resolve(..)` method can also fetch the required data from these endpoints. For example, a 98 | route that needs to fetch a task by its ID may look like this: 99 | 100 | ```json 101 | { 102 | "path": "/tasks/:id", 103 | "page": "./pages/tasks/details", 104 | "fetch": { 105 | "task": "GET /api/tasks/$id", 106 | } 107 | } 108 | ``` 109 | 110 | Finally, you can hook the router's `resolve(..)` method to be called each time when a user navigates 111 | (transitions) between pages. The code for that may look something like this: 112 | 113 | ```js 114 | function render(location) { 115 | router.resolve(routes, location) 116 | .then(renderComponent) 117 | .catch(error => router.resolve(routes, { ...location, error }).then(renderComponent)); 118 | } 119 | 120 | history.listen(render); 121 | render(history.getCurrentLocation()); 122 | ``` 123 | 124 | For more information about how the `history` npm module works please visit: 125 | 126 | https://github.com/ReactJSTraining/history/tree/master/docs 127 | 128 | All transitions between pages must be performed by using this module, for example: 129 | 130 | ```js 131 | import React from 'react'; 132 | import history from '../../core/history'; 133 | 134 | class HomePage extends React.Component { 135 | 136 | transition = event => { 137 | event.preventDefault(); 138 | history.push({ pathname: event.currentTarget.pathname }); 139 | }; 140 | 141 | render() { 142 | return ( 143 | 147 | ); 148 | } 149 | 150 | } 151 | ``` 152 | 153 | The `transition(event)` method above cancels default behavior of the `` element that causes 154 | full-page refresh and instead redirects a user to the `/tasks/123` page by using HTML5 History API. 155 | This transition is then handled by `history.listen(render)` listener inside the 156 | [`main.js`](../main.js) file. 157 | 158 | RSB comes with a helper component that can be used instead of `` elements, see 159 | [`components/Link/Link.js`](../components/Link/Link.js). So, instead of writing `Show task #123` you can have `Show task #123`. 161 | 162 | ### Related Articles 163 | 164 | * [You might not need React Router](https://medium.com/@tarkus/you-might-not-need-react-router-38673620f3d) by Konstantin Tarkus 165 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "hosting": { 6 | "public": "public", 7 | "rewrites": [ 8 | { 9 | "source": "**", 10 | "destination": "/index.html" 11 | } 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import 'babel-polyfill'; 12 | import 'whatwg-fetch'; 13 | 14 | import React from 'react'; 15 | import ReactDOM from 'react-dom'; 16 | import FastClick from 'fastclick'; 17 | import { Provider } from 'react-redux'; 18 | 19 | import store from './core/store'; 20 | import router from './core/router'; 21 | import history from './core/history'; 22 | 23 | let routes = require('./routes.json'); // Loaded with utils/routes-loader.js 24 | const container = document.getElementById('container'); 25 | 26 | function renderComponent(component) { 27 | ReactDOM.render({component}, container); 28 | } 29 | 30 | // Find and render a web page matching the current URL path, 31 | // if such page is not found then render an error page (see routes.json, core/router.js) 32 | function render(location) { 33 | router.resolve(routes, location) 34 | .then(renderComponent) 35 | .catch(error => router.resolve(routes, { ...location, error }).then(renderComponent)); 36 | } 37 | 38 | // Handle client-side navigation by using HTML5 History API 39 | // For more information visit https://github.com/ReactJSTraining/history/tree/master/docs#readme 40 | history.listen(render); 41 | render(history.getCurrentLocation()); 42 | 43 | // Eliminates the 300ms delay between a physical tap 44 | // and the firing of a click event on mobile browsers 45 | // https://github.com/ftlabs/fastclick 46 | FastClick.attach(document.body); 47 | 48 | // Enable Hot Module Replacement (HMR) 49 | if (module.hot) { 50 | module.hot.accept('./routes.json', () => { 51 | routes = require('./routes.json'); // eslint-disable-line global-require 52 | render(history.getCurrentLocation()); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "engines": { 6 | "node": ">=6", 7 | "npm": ">=3.8" 8 | }, 9 | "dependencies": { 10 | "babel-polyfill": "^6.9.1", 11 | "classnames": "^2.2.5", 12 | "fastclick": "^1.0.6", 13 | "history": "^3.0.0", 14 | "react": "^15.2.1", 15 | "react-dom": "^15.2.1", 16 | "react-mdl": "^1.6.1", 17 | "react-redux": "^4.4.5", 18 | "redux": "^3.5.2", 19 | "whatwg-fetch": "^1.0.0" 20 | }, 21 | "devDependencies": { 22 | "assets-webpack-plugin": "^3.4.0", 23 | "autoprefixer": "^6.3.7", 24 | "babel-core": "^6.11.4", 25 | "babel-eslint": "^6.1.2", 26 | "babel-loader": "^6.2.4", 27 | "babel-plugin-transform-runtime": "^6.9.0", 28 | "babel-preset-es2015": "^6.9.0", 29 | "babel-preset-react": "^6.11.1", 30 | "babel-preset-stage-1": "^6.5.0", 31 | "babel-register": "^6.11.6", 32 | "babel-runtime": "^6.11.6", 33 | "browser-sync": "^2.13.0", 34 | "chai": "^3.5.0", 35 | "connect-history-api-fallback": "^1.2.0", 36 | "css-loader": "^0.23.1", 37 | "del": "^2.2.1", 38 | "ejs": "^2.5.1", 39 | "eslint": "^3.1.1", 40 | "eslint-config-airbnb": "^9.0.1", 41 | "eslint-plugin-import": "^1.12.0", 42 | "eslint-plugin-jsx-a11y": "^2.0.1", 43 | "eslint-plugin-react": "^5.2.2", 44 | "file-loader": "^0.9.0", 45 | "firebase-tools": "^3.0.4", 46 | "front-matter": "^2.1.0", 47 | "highlight.js": "^9.5.0", 48 | "json-loader": "^0.5.4", 49 | "markdown-it": "^7.0.0", 50 | "mocha": "^2.5.3", 51 | "path-to-regexp": "^1.5.3", 52 | "pixrem": "^3.0.1", 53 | "pleeease-filters": "^3.0.0", 54 | "postcss": "^5.1.1", 55 | "postcss-calc": "^5.3.0", 56 | "postcss-color-function": "^2.0.1", 57 | "postcss-custom-media": "^5.0.1", 58 | "postcss-custom-properties": "^5.0.1", 59 | "postcss-custom-selectors": "^3.0.0", 60 | "postcss-flexbugs-fixes": "^2.0.0", 61 | "postcss-import": "^8.1.2", 62 | "postcss-loader": "^0.9.1", 63 | "postcss-media-minmax": "^2.1.2", 64 | "postcss-nesting": "^2.3.1", 65 | "postcss-selector-matches": "^2.0.1", 66 | "postcss-selector-not": "^2.0.0", 67 | "react-hot-loader": "^3.0.0-beta.2", 68 | "s3": "^4.4.0", 69 | "style-loader": "^0.13.1", 70 | "stylelint": "^7.0.3", 71 | "stylelint-config-standard": "^11.0.0", 72 | "url-loader": "^0.5.7", 73 | "webpack": "^1.13.1", 74 | "webpack-dev-middleware": "^1.6.1", 75 | "webpack-hot-middleware": "^2.12.2" 76 | }, 77 | "babel": { 78 | "presets": [ 79 | "react", 80 | "es2015", 81 | "stage-1" 82 | ], 83 | "plugins": [ 84 | "transform-runtime" 85 | ] 86 | }, 87 | "eslintConfig": { 88 | "parser": "babel-eslint", 89 | "extends": "airbnb" 90 | }, 91 | "stylelint": { 92 | "extends": "stylelint-config-standard", 93 | "rules": { 94 | "string-quotes": "single" 95 | } 96 | }, 97 | "scripts": { 98 | "eslint": "eslint components core pages test utils main.js run.js webpack.config.js", 99 | "stylelint": "stylelint \"components/**/*.css\" \"pages/**/*.css\"", 100 | "lint": "npm run eslint && npm run stylelint", 101 | "test": "mocha --compilers js:babel-register", 102 | "test:watch": "mocha --compilers js:babel-register --reporter min --watch", 103 | "clean": "node run clean", 104 | "build": "node run build", 105 | "build:debug": "node run build --debug", 106 | "publish": "node run publish", 107 | "publish:debug": "node run publish --debug", 108 | "start": "node run" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /pages/about/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React from 'react'; 12 | import Layout from '../../components/Layout'; 13 | import s from './styles.css'; 14 | import { title, html } from './index.md'; 15 | 16 | class AboutPage extends React.Component { 17 | 18 | componentDidMount() { 19 | document.title = title; 20 | } 21 | 22 | render() { 23 | return ( 24 | 25 |

{title}

26 |
27 | 28 | ); 29 | } 30 | 31 | } 32 | 33 | export default AboutPage; 34 | -------------------------------------------------------------------------------- /pages/about/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About Us 3 | --- 4 | 5 | ## Cadme comitum fecere 6 | 7 | Lorem markdownum velis auras figuram spes solebat spectabat, cum alium, 8 | plenissima aratri visae herbarum in corpore silvas consumpta. Subito virgae nec 9 | paratae flexit et niveae repperit erat paratu cum albis steterat conclamat hic! 10 | 11 | Nocte suae ligat! *Si* nitidum pervia, illa tua, ab minimo pasci dabitur? In 12 | fictus concurreret pennis, illis cum accipe rogavi in et nostro cum lacertis 13 | hostibus ab saxo ne. Genibusque vixque; sine videt terribili lucos ipsum vobis 14 | resque, et suum pietatis fulvis, est velle. Semele oscula ferat frigidus mactata 15 | montes, es me parari, piae. 16 | 17 | ## Inflataque ait leves frigida 18 | 19 | Letum per ipsa nostro animae, mari illuc in levi corpus aestibus excussam 20 | deflentem sic cuius. Venere dedit illa cui in quo senecta artus bella inficit, 21 | Achaica. Videbatur crinem resonantia alto dea umida dicitur igne; meus signa 22 | habet; est. Cognovit coepta: similes fugis: habuissem votivi liquida: ictus visi 23 | nostra me Adoni. 24 | 25 | ## Laedar cum margine quoque 26 | 27 | Quam dato ullis, acer venturi volantes! Tuam non non cursu acta hic, novem 28 | nutrit, in sidera viscera iam fontes tempora, omnes. Saturnius artus inquit, 29 | conatoque erectos lenius, carinae, ora est infamia elige per Medusaei induitur. 30 | Quem quem ab postquam tunc frondescere nodis capiam labique. Voluere luce 31 | Semeles. 32 | 33 | ``` 34 | if (delete(digital, hibernateSoft, dynamicExcelVpn) > io_secondary_led / 35 | 84) { 36 | disk = load; 37 | orientationPci.matrix_laptop(modelSsdTweet); 38 | } else { 39 | kdeEmoticonLed.mebibyte_algorithm_domain(2, 40 | hackerCtr.rom_iso_desktop.scarewarePrimaryBankruptcy(station, 41 | disk_mask_matrix, restore_crt)); 42 | cameraSpyware(4, multitasking(-3, log_dfs_controller)); 43 | menuCisc.swappable -= w(mount_vle_unicode, 5); 44 | } 45 | var optic_spider = newbieFunctionThick(-3, esportsKbpsUnix); 46 | var dvd_ctp_resolution = dithering; 47 | ``` 48 | 49 | ## Usus fixurus illi petunt 50 | 51 | Domosque tune amas mihi adhuc et *alter per* suasque versavitque iners 52 | crescentemque nomen verba nunc. Acervos hinc natus si habet. Et cervix imago 53 | quod! Arduus dolet! 54 | 55 | ``` 56 | cpcDdrCommand.window(moodleAlpha, im, server_alpha.doubleVrmlMonochrome( 57 | iosBar - -2, white_dual, ad(2, 94, 83))); 58 | mbps_typeface_publishing.bit.host_flash_capacity(click(90, 59 | cyberspace_srgb_pup - mpeg, marketing_trackback + 60 | table_plagiarism_domain)); 61 | syn_e = powerExtension * defragmentNntpOsd(alertOutputNode(pop, 62 | pageResponsiveDrive)); 63 | method -= switch_newsgroup_flaming; 64 | ``` 65 | 66 | Aliquid mansura arida altismunera **in illi**. Dignus vir pontum *crimen 67 | versabat* carpunt omnes rotis Canentem erant in Oebalio, et manu senecta 68 | iungere. Prima diurnis! 69 | -------------------------------------------------------------------------------- /pages/about/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | @media screen and (max-width: 1024px) { 12 | 13 | .content { 14 | padding: 0 16px; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /pages/error/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React from 'react'; 12 | import history from '../../core/history'; 13 | import Link from '../../components/Link'; 14 | import s from './styles.css'; 15 | 16 | class ErrorPage extends React.Component { 17 | 18 | static propTypes = { 19 | error: React.PropTypes.object, 20 | }; 21 | 22 | componentDidMount() { 23 | document.title = this.props.error && this.props.error.status === 404 ? 24 | 'Page Not Found' : 'Error'; 25 | } 26 | 27 | goBack = event => { 28 | event.preventDefault(); 29 | history.goBack(); 30 | }; 31 | 32 | render() { 33 | if (this.props.error) console.error(this.props.error); // eslint-disable-line no-console 34 | 35 | const [code, title] = this.props.error && this.props.error.status === 404 ? 36 | ['404', 'Page not found'] : 37 | ['Error', 'Oups, something went wrong']; 38 | 39 | return ( 40 |
41 |
42 |

{code}

43 |

{title}

44 | {code === '404' && 45 |

46 | The page you're looking for does not exist or an another error occurred. 47 |

48 | } 49 |

50 | Go back, or head over to the  51 | home page to choose a new direction. 52 |

53 |
54 |
55 | ); 56 | } 57 | 58 | } 59 | 60 | export default ErrorPage; 61 | -------------------------------------------------------------------------------- /pages/error/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | :root { 12 | --color: #607d8b; 13 | } 14 | 15 | .container { 16 | position: absolute; 17 | top: 0; 18 | left: 0; 19 | display: flex; 20 | width: 100%; 21 | height: 100%; 22 | text-align: center; 23 | justify-content: center; 24 | align-items: center; 25 | } 26 | 27 | .content { 28 | padding-bottom: 80px; 29 | } 30 | 31 | .code { 32 | margin: 0; 33 | color: var(--color); 34 | letter-spacing: 0.02em; 35 | font-weight: 300; 36 | font-size: 15em; 37 | line-height: 1; 38 | } 39 | 40 | .title { 41 | padding-bottom: 0.5em; 42 | color: var(--color); 43 | letter-spacing: 0.02em; 44 | font-weight: 400; 45 | font-size: 2em; 46 | font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif; 47 | line-height: 1em; 48 | } 49 | 50 | .text { 51 | padding-bottom: 0; 52 | color: color(var(--color) alpha(50%)); 53 | font-size: 1.125em; 54 | line-height: 1.5em; 55 | } 56 | 57 | @media only screen and (max-width: 280px) { 58 | 59 | .container, 60 | .text { 61 | width: 95%; 62 | } 63 | 64 | .title { 65 | margin: 0 0 0.3em; 66 | font-size: 1.5em; 67 | } 68 | 69 | } 70 | 71 | @media screen and (max-width: 1024px) { 72 | 73 | .content { 74 | padding: 0 16px; 75 | } 76 | 77 | .code { 78 | font-size: 10em; 79 | } 80 | 81 | .title { 82 | font-size: 1.5em; 83 | } 84 | 85 | } 86 | -------------------------------------------------------------------------------- /pages/home/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import React, { PropTypes } from 'react'; 12 | import Layout from '../../components/Layout'; 13 | import s from './styles.css'; 14 | import { title, html } from './index.md'; 15 | 16 | class HomePage extends React.Component { 17 | 18 | static propTypes = { 19 | articles: PropTypes.array.isRequired, 20 | }; 21 | 22 | componentDidMount() { 23 | document.title = title; 24 | } 25 | 26 | render() { 27 | return ( 28 | 29 |
30 |

Articles

31 |
    32 | {this.props.articles.map((article, i) => 33 |
  • {article.title} by {article.author}
  • 34 | )} 35 |
36 |

37 |

38 |

39 | 40 | ); 41 | } 42 | 43 | } 44 | 45 | export default HomePage; 46 | -------------------------------------------------------------------------------- /pages/home/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: React Static Boilerplate 3 | --- 4 | 5 | ## Welcome! 6 | 7 | This is a single-page application powered by React and Material Design Lite (MDL). 8 | 9 | https://github.com/kriasoft/react-static-boilerplate 10 | 11 | -------------------------------------------------------------------------------- /pages/home/styles.css: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/koistya/react-static-boilerplate 4 | * 5 | * Copyright © 2015-2016 Konstantin Tarkus (@koistya) 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | @media screen and (max-width: 1024px) { 12 | 13 | .content { 14 | padding: 0 16px; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koistya/react-static-boilerplate/1e347267683cb8c3071bc9ffefe427ccc7966b15/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /public/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koistya/react-static-boilerplate/1e347267683cb8c3071bc9ffefe427ccc7966b15/public/favicon.ico -------------------------------------------------------------------------------- /public/humans.txt: -------------------------------------------------------------------------------- 1 | # humanstxt.org/ 2 | # The humans responsible & technology colophon 3 | 4 | # TEAM 5 | 6 | -- -- 7 | 8 | # THANKS 9 | 10 | 11 | 12 | # TECHNOLOGY COLOPHON 13 | 14 | JavaScript, HTML5, CSS3 15 | React, Redux, Material Design Lite (MDL) 16 | Babel, Webpack, Node.js 17 | React Static Boilerplate 18 | -------------------------------------------------------------------------------- /public/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= config.title %> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | <%_ if (!debug && config.trackingID) { _%> 17 | 21 | 22 | <%_ } _%> 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /public/sitemap.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%_ urls.forEach(url => { _%> 4 | 5 | <%= config.url %><%= url.loc %> 6 | 7 | <%_ }); _%> 8 | 9 | -------------------------------------------------------------------------------- /public/tile-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koistya/react-static-boilerplate/1e347267683cb8c3071bc9ffefe427ccc7966b15/public/tile-wide.png -------------------------------------------------------------------------------- /public/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koistya/react-static-boilerplate/1e347267683cb8c3071bc9ffefe427ccc7966b15/public/tile.png -------------------------------------------------------------------------------- /routes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "/", 4 | "page": "./pages/home", 5 | "chunk": "main", 6 | "data": { 7 | "articles": "GET https://gist.githubusercontent.com/koistya/a32919e847531320675764e7308b796a/raw/articles.json" 8 | } 9 | }, 10 | { 11 | "path": "/error", 12 | "page": "./pages/error", 13 | "chunk": "main" 14 | }, 15 | { 16 | "path": "/about", 17 | "page": "./pages/about" 18 | }, 19 | { 20 | "path": "/tasks/:status(pending|completed)?", 21 | "page": "./pages/home" 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /run.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | /* eslint-disable no-console, global-require */ 12 | 13 | const fs = require('fs'); 14 | const del = require('del'); 15 | const ejs = require('ejs'); 16 | const webpack = require('webpack'); 17 | 18 | // TODO: Update configuration settings 19 | const config = { 20 | title: 'React Static Boilerplate', // Your website title 21 | url: 'https://rsb.kriasoft.com', // Your website URL 22 | project: 'react-static-boilerplate', // Firebase project. See README.md -> How to Deploy 23 | trackingID: 'UA-XXXXX-Y', // Google Analytics Site's ID 24 | }; 25 | 26 | const tasks = new Map(); // The collection of automation tasks ('clean', 'build', 'publish', etc.) 27 | 28 | function run(task) { 29 | const start = new Date(); 30 | console.log(`Starting '${task}'...`); 31 | return Promise.resolve().then(() => tasks.get(task)()).then(() => { 32 | console.log(`Finished '${task}' after ${new Date().getTime() - start.getTime()}ms`); 33 | }, err => console.error(err.stack)); 34 | } 35 | 36 | // 37 | // Clean up the output directory 38 | // ----------------------------------------------------------------------------- 39 | tasks.set('clean', () => del(['public/dist/*', '!public/dist/.git'], { dot: true })); 40 | 41 | // 42 | // Copy ./index.html into the /public folder 43 | // ----------------------------------------------------------------------------- 44 | tasks.set('html', () => { 45 | const webpackConfig = require('./webpack.config'); 46 | const assets = JSON.parse(fs.readFileSync('./public/dist/assets.json', 'utf8')); 47 | const template = fs.readFileSync('./public/index.ejs', 'utf8'); 48 | const render = ejs.compile(template, { filename: './public/index.ejs' }); 49 | const output = render({ debug: webpackConfig.debug, bundle: assets.main.js, config }); 50 | fs.writeFileSync('./public/index.html', output, 'utf8'); 51 | }); 52 | 53 | // 54 | // Generate sitemap.xml 55 | // ----------------------------------------------------------------------------- 56 | tasks.set('sitemap', () => { 57 | const urls = require('./routes.json') 58 | .filter(x => !x.path.includes(':')) 59 | .map(x => ({ loc: x.path })); 60 | const template = fs.readFileSync('./public/sitemap.ejs', 'utf8'); 61 | const render = ejs.compile(template, { filename: './public/sitemap.ejs' }); 62 | const output = render({ config, urls }); 63 | fs.writeFileSync('public/sitemap.xml', output, 'utf8'); 64 | }); 65 | 66 | // 67 | // Bundle JavaScript, CSS and image files with Webpack 68 | // ----------------------------------------------------------------------------- 69 | tasks.set('bundle', () => { 70 | const webpackConfig = require('./webpack.config'); 71 | return new Promise((resolve, reject) => { 72 | webpack(webpackConfig).run((err, stats) => { 73 | if (err) { 74 | reject(err); 75 | } else { 76 | console.log(stats.toString(webpackConfig.stats)); 77 | resolve(); 78 | } 79 | }); 80 | }); 81 | }); 82 | 83 | // 84 | // Build website into a distributable format 85 | // ----------------------------------------------------------------------------- 86 | tasks.set('build', () => { 87 | global.DEBUG = process.argv.includes('--debug') || false; 88 | return Promise.resolve() 89 | .then(() => run('clean')) 90 | .then(() => run('bundle')) 91 | .then(() => run('html')) 92 | .then(() => run('sitemap')); 93 | }); 94 | 95 | // 96 | // Build and publish the website 97 | // ----------------------------------------------------------------------------- 98 | tasks.set('publish', () => { 99 | const firebase = require('firebase-tools'); 100 | return run('build') 101 | .then(() => firebase.login({ nonInteractive: false })) 102 | .then(() => firebase.deploy({ 103 | project: config.project, 104 | cwd: __dirname, 105 | })) 106 | .then(() => { setTimeout(() => process.exit()); }); 107 | }); 108 | 109 | // 110 | // Build website and launch it in a browser for testing (default) 111 | // ----------------------------------------------------------------------------- 112 | tasks.set('start', () => { 113 | let count = 0; 114 | global.HMR = !process.argv.includes('--no-hmr'); // Hot Module Replacement (HMR) 115 | return run('clean').then(() => new Promise(resolve => { 116 | const bs = require('browser-sync').create(); 117 | const webpackConfig = require('./webpack.config'); 118 | const compiler = webpack(webpackConfig); 119 | // Node.js middleware that compiles application in watch mode with HMR support 120 | // http://webpack.github.io/docs/webpack-dev-middleware.html 121 | const webpackDevMiddleware = require('webpack-dev-middleware')(compiler, { 122 | publicPath: webpackConfig.output.publicPath, 123 | stats: webpackConfig.stats, 124 | }); 125 | compiler.plugin('done', stats => { 126 | // Generate index.html page 127 | const bundle = stats.compilation.chunks.find(x => x.name === 'main').files[0]; 128 | const template = fs.readFileSync('./public/index.ejs', 'utf8'); 129 | const render = ejs.compile(template, { filename: './public/index.ejs' }); 130 | const output = render({ debug: true, bundle: `/dist/${bundle}`, config }); 131 | fs.writeFileSync('./public/index.html', output, 'utf8'); 132 | 133 | // Launch Browsersync after the initial bundling is complete 134 | // For more information visit https://browsersync.io/docs/options 135 | if (++count === 1) { 136 | bs.init({ 137 | port: process.env.PORT || 3000, 138 | ui: { port: Number(process.env.PORT || 3000) + 1 }, 139 | server: { 140 | baseDir: 'public', 141 | middleware: [ 142 | webpackDevMiddleware, 143 | require('webpack-hot-middleware')(compiler), 144 | require('connect-history-api-fallback')(), 145 | ], 146 | }, 147 | }, resolve); 148 | } 149 | }); 150 | })); 151 | }); 152 | 153 | // Execute the specified task or default one. E.g.: node run build 154 | run(/^\w/.test(process.argv[2] || '') ? process.argv[2] : 'start' /* default */); 155 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "mocha": true 4 | }, 5 | "rules": { 6 | "no-console": 0, 7 | "no-unused-expressions": 0, 8 | "padded-blocks": 0 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /test/spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | import { expect } from 'chai'; 12 | 13 | describe('test suite', () => { 14 | 15 | it('test', () => { 16 | expect(true).to.be.equal.true; 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /utils/markdown-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | const MarkdownIt = require('markdown-it'); 12 | const hljs = require('highlight.js'); 13 | const fm = require('front-matter'); 14 | 15 | module.exports = function markdownLoader(source) { 16 | this.cacheable(); 17 | 18 | const md = new MarkdownIt({ 19 | html: true, 20 | linkify: true, 21 | highlight: (str, lang) => { 22 | if (lang && hljs.getLanguage(lang)) { 23 | try { 24 | return hljs.highlight(lang, str).value; 25 | } catch (err) { console.error(err.stack); } // eslint-disable-line no-console 26 | } 27 | 28 | try { 29 | return hljs.highlightAuto(str).value; 30 | } catch (err) { console.error(err.stack); } // eslint-disable-line no-console 31 | 32 | return ''; 33 | }, 34 | }); 35 | 36 | const frontmatter = fm(source); 37 | frontmatter.attributes.html = md.render(frontmatter.body); 38 | 39 | return `module.exports = ${JSON.stringify(frontmatter.attributes)};`; 40 | }; 41 | -------------------------------------------------------------------------------- /utils/routes-loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | const toRegExp = require('path-to-regexp'); 12 | 13 | function escape(text) { 14 | return text.replace('\'', '\\\'').replace('\\', '\\\\'); 15 | } 16 | 17 | /** 18 | * Converts application routes from JSON to JavaScript. For example, a route like 19 | * 20 | * { 21 | * "path": "/about", 22 | * "page": "./pages/about" 23 | * } 24 | * 25 | * becomes 26 | * 27 | * { 28 | * path: '/about', 29 | * pattern: /^\\/about(?:\/(?=$))?$/i, 30 | * keys: [], 31 | * page: './pages/about', 32 | * load: function () { return new Promise(resolve => require(['./pages/about'], resolve)); } 33 | * } 34 | */ 35 | module.exports = function routesLoader(source) { 36 | this.cacheable(); 37 | 38 | const output = ['[\n']; 39 | const routes = JSON.parse(source); 40 | 41 | for (const route of routes) { 42 | const keys = []; 43 | const pattern = toRegExp(route.path, keys); 44 | const require = route.chunk && route.chunk === 'main' ? 45 | module => `Promise.resolve(require('${escape(module)}').default)` : 46 | module => `new Promise(function (resolve, reject) { 47 | try { 48 | require.ensure(['${escape(module)}'], function (require) { 49 | resolve(require('${escape(module)}').default); 50 | }${typeof route.chunk === 'string' ? `, '${escape(route.chunk)}'` : ''}); 51 | } catch (err) { 52 | reject(err); 53 | } 54 | })`; 55 | output.push(' {\n'); 56 | output.push(` path: '${escape(route.path)}',\n`); 57 | output.push(` pattern: ${pattern.toString()},\n`); 58 | output.push(` keys: ${JSON.stringify(keys)},\n`); 59 | output.push(` page: '${escape(route.page)}',\n`); 60 | if (route.data) { 61 | output.push(` data: ${JSON.stringify(route.data)},\n`); 62 | } 63 | output.push(` load() {\n return ${require(route.page)};\n },\n`); 64 | output.push(' },\n'); 65 | } 66 | 67 | output.push(']'); 68 | 69 | return `module.exports = ${output.join('')};`; 70 | }; 71 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * React Static Boilerplate 3 | * https://github.com/kriasoft/react-static-boilerplate 4 | * 5 | * Copyright © 2015-present Kriasoft, LLC. All rights reserved. 6 | * 7 | * This source code is licensed under the MIT license found in the 8 | * LICENSE.txt file in the root directory of this source tree. 9 | */ 10 | 11 | /* eslint-disable global-require */ 12 | 13 | const path = require('path'); 14 | const webpack = require('webpack'); 15 | const AssetsPlugin = require('assets-webpack-plugin'); 16 | const pkg = require('./package.json'); 17 | 18 | const isDebug = global.DEBUG === false ? false : !process.argv.includes('--release'); 19 | const isVerbose = process.argv.includes('--verbose') || process.argv.includes('-v'); 20 | const useHMR = !!global.HMR; // Hot Module Replacement (HMR) 21 | const babelConfig = Object.assign({}, pkg.babel, { 22 | babelrc: false, 23 | cacheDirectory: useHMR, 24 | }); 25 | 26 | // Webpack configuration (main.js => public/dist/main.{hash}.js) 27 | // http://webpack.github.io/docs/configuration.html 28 | const config = { 29 | 30 | // The base directory for resolving the entry option 31 | context: __dirname, 32 | 33 | // The entry point for the bundle 34 | entry: [ 35 | /* Material Design Lite (https://getmdl.io) */ 36 | '!!style!css!react-mdl/extra/material.min.css', 37 | 'react-mdl/extra/material.min.js', 38 | /* The main entry point of your JavaScript application */ 39 | './main.js', 40 | ], 41 | 42 | // Options affecting the output of the compilation 43 | output: { 44 | path: path.resolve(__dirname, './public/dist'), 45 | publicPath: '/dist/', 46 | filename: isDebug ? '[name].js?[hash]' : '[name].[hash].js', 47 | chunkFilename: isDebug ? '[id].js?[chunkhash]' : '[id].[chunkhash].js', 48 | sourcePrefix: ' ', 49 | }, 50 | 51 | // Switch loaders to debug or release mode 52 | debug: isDebug, 53 | 54 | // Developer tool to enhance debugging, source maps 55 | // http://webpack.github.io/docs/configuration.html#devtool 56 | devtool: isDebug ? 'source-map' : false, 57 | 58 | // What information should be printed to the console 59 | stats: { 60 | colors: true, 61 | reasons: isDebug, 62 | hash: isVerbose, 63 | version: isVerbose, 64 | timings: true, 65 | chunks: isVerbose, 66 | chunkModules: isVerbose, 67 | cached: isVerbose, 68 | cachedAssets: isVerbose, 69 | }, 70 | 71 | // The list of plugins for Webpack compiler 72 | plugins: [ 73 | new webpack.optimize.OccurrenceOrderPlugin(), 74 | new webpack.DefinePlugin({ 75 | 'process.env.NODE_ENV': isDebug ? '"development"' : '"production"', 76 | __DEV__: isDebug, 77 | }), 78 | // Emit a JSON file with assets paths 79 | // https://github.com/sporto/assets-webpack-plugin#options 80 | new AssetsPlugin({ 81 | path: path.resolve(__dirname, './public/dist'), 82 | filename: 'assets.json', 83 | prettyPrint: true, 84 | }), 85 | ], 86 | 87 | // Options affecting the normal modules 88 | module: { 89 | loaders: [ 90 | { 91 | test: /\.jsx?$/, 92 | include: [ 93 | path.resolve(__dirname, './actions'), 94 | path.resolve(__dirname, './components'), 95 | path.resolve(__dirname, './core'), 96 | path.resolve(__dirname, './pages'), 97 | path.resolve(__dirname, './main.js'), 98 | ], 99 | loader: `babel-loader?${JSON.stringify(babelConfig)}`, 100 | }, 101 | { 102 | test: /\.css$/, 103 | loaders: [ 104 | 'style-loader', 105 | `css-loader?${JSON.stringify({ 106 | sourceMap: isDebug, 107 | // CSS Modules https://github.com/css-modules/css-modules 108 | modules: true, 109 | localIdentName: isDebug ? '[name]_[local]_[hash:base64:3]' : '[hash:base64:4]', 110 | // CSS Nano http://cssnano.co/options/ 111 | minimize: !isDebug, 112 | })}`, 113 | 'postcss-loader', 114 | ], 115 | }, 116 | { 117 | test: /\.json$/, 118 | exclude: [ 119 | path.resolve(__dirname, './routes.json'), 120 | ], 121 | loader: 'json-loader', 122 | }, 123 | { 124 | test: /\.json$/, 125 | include: [ 126 | path.resolve(__dirname, './routes.json'), 127 | ], 128 | loaders: [ 129 | `babel-loader?${JSON.stringify(babelConfig)}`, 130 | path.resolve(__dirname, './utils/routes-loader.js'), 131 | ], 132 | }, 133 | { 134 | test: /\.md$/, 135 | loader: path.resolve(__dirname, './utils/markdown-loader.js'), 136 | }, 137 | { 138 | test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/, 139 | loader: 'url-loader?limit=10000', 140 | }, 141 | { 142 | test: /\.(eot|ttf|wav|mp3)$/, 143 | loader: 'file-loader', 144 | }, 145 | ], 146 | }, 147 | 148 | // The list of plugins for PostCSS 149 | // https://github.com/postcss/postcss 150 | postcss(bundler) { 151 | return [ 152 | // Transfer @import rule by inlining content, e.g. @import 'normalize.css' 153 | // https://github.com/postcss/postcss-import 154 | require('postcss-import')({ addDependencyTo: bundler }), 155 | // W3C variables, e.g. :root { --color: red; } div { background: var(--color); } 156 | // https://github.com/postcss/postcss-custom-properties 157 | require('postcss-custom-properties')(), 158 | // W3C CSS Custom Media Queries, e.g. @custom-media --small-viewport (max-width: 30em); 159 | // https://github.com/postcss/postcss-custom-media 160 | require('postcss-custom-media')(), 161 | // CSS4 Media Queries, e.g. @media screen and (width >= 500px) and (width <= 1200px) { } 162 | // https://github.com/postcss/postcss-media-minmax 163 | require('postcss-media-minmax')(), 164 | // W3C CSS Custom Selectors, e.g. @custom-selector :--heading h1, h2, h3, h4, h5, h6; 165 | // https://github.com/postcss/postcss-custom-selectors 166 | require('postcss-custom-selectors')(), 167 | // W3C calc() function, e.g. div { height: calc(100px - 2em); } 168 | // https://github.com/postcss/postcss-calc 169 | require('postcss-calc')(), 170 | // Allows you to nest one style rule inside another 171 | // https://github.com/jonathantneal/postcss-nesting 172 | require('postcss-nesting')(), 173 | // W3C color() function, e.g. div { background: color(red alpha(90%)); } 174 | // https://github.com/postcss/postcss-color-function 175 | require('postcss-color-function')(), 176 | // Convert CSS shorthand filters to SVG equivalent, e.g. .blur { filter: blur(4px); } 177 | // https://github.com/iamvdo/pleeease-filters 178 | require('pleeease-filters')(), 179 | // Generate pixel fallback for "rem" units, e.g. div { margin: 2.5rem 2px 3em 100%; } 180 | // https://github.com/robwierzbowski/node-pixrem 181 | require('pixrem')(), 182 | // W3C CSS Level4 :matches() pseudo class, e.g. p:matches(:first-child, .special) { } 183 | // https://github.com/postcss/postcss-selector-matches 184 | require('postcss-selector-matches')(), 185 | // Transforms :not() W3C CSS Level 4 pseudo class to :not() CSS Level 3 selectors 186 | // https://github.com/postcss/postcss-selector-not 187 | require('postcss-selector-not')(), 188 | // Postcss flexbox bug fixer 189 | // https://github.com/luisrudge/postcss-flexbugs-fixes 190 | require('postcss-flexbugs-fixes')(), 191 | // Add vendor prefixes to CSS rules using values from caniuse.com 192 | // https://github.com/postcss/autoprefixer 193 | require('autoprefixer')(), 194 | ]; 195 | }, 196 | 197 | }; 198 | 199 | // Optimize the bundle in release (production) mode 200 | if (!isDebug) { 201 | config.plugins.push(new webpack.optimize.DedupePlugin()); 202 | config.plugins.push(new webpack.optimize.UglifyJsPlugin({ compress: { warnings: isVerbose } })); 203 | config.plugins.push(new webpack.optimize.AggressiveMergingPlugin()); 204 | } 205 | 206 | // Hot Module Replacement (HMR) + React Hot Reload 207 | if (isDebug && useHMR) { 208 | babelConfig.plugins.unshift('react-hot-loader/babel'); 209 | config.entry.unshift('react-hot-loader/patch', 'webpack-hot-middleware/client'); 210 | config.plugins.push(new webpack.HotModuleReplacementPlugin()); 211 | config.plugins.push(new webpack.NoErrorsPlugin()); 212 | } 213 | 214 | module.exports = config; 215 | --------------------------------------------------------------------------------