From 9cad542a7f0039eaf9f5530eb271d6a003d0d7c8 Mon Sep 17 00:00:00 2001 From: Michele Memoli Date: Thu, 10 Mar 2016 16:56:21 +0000 Subject: [PATCH] initial commit --- .babelrc | 4 + .editorconfig | 30 ++ .env | 2 + .eslintignore | 5 + .eslintrc | 19 ++ .gitignore | 7 + .travis.yml | 15 + CHANGELOG.md | 341 +++++++++++++++++++ CONTRIBUTING.md | 59 ++++ LICENSE | 21 ++ README.md | 256 ++++++++++++++ bin/compile.js | 37 ++ bin/karma.js | 3 + bin/server.js | 12 + build/karma.conf.js | 53 +++ build/webpack/_base.js | 147 ++++++++ build/webpack/_development.js | 58 ++++ build/webpack/_production.js | 46 +++ build/webpack/index.js | 4 + config/_base.js | 115 +++++++ config/_development.js | 20 ++ config/_production.js | 11 + config/index.js | 17 + jsconfig.json | 5 + package.json | 100 ++++++ server/app.js | 38 +++ server/middleware/webpack-dev.js | 20 ++ server/middleware/webpack-hmr.js | 9 + src/app.js | 18 + src/assets/favicon.ico | Bin 0 -> 24838 bytes src/components/.gitkeep | 0 src/containers/DevTools.js | 12 + src/containers/DevToolsWindow.js | 7 + src/containers/Root.js | 41 +++ src/index.html | 12 + src/layouts/CoreLayout.js | 27 ++ src/redux/configureStore.js | 34 ++ src/redux/modules/counter.js | 37 ++ src/redux/modules/index.js | 8 + src/redux/utils/createDevToolsWindow.js | 30 ++ src/routes/index.js | 12 + src/styles/_base.scss | 12 + src/styles/core.scss | 21 ++ src/styles/vendor/_normalize.scss | 427 ++++++++++++++++++++++++ src/views/AboutView.js | 16 + src/views/HomeView.js | 45 +++ src/views/HomeView.scss | 8 + tests/.eslintrc | 11 + tests/framework.spec.js | 21 ++ tests/layouts/CoreLayout.spec.js | 33 ++ tests/views/HomeView.spec.js | 102 ++++++ 51 files changed, 2388 insertions(+) create mode 100755 .babelrc create mode 100755 .editorconfig create mode 100755 .env create mode 100755 .eslintignore create mode 100755 .eslintrc create mode 100755 .gitignore create mode 100755 .travis.yml create mode 100755 CHANGELOG.md create mode 100755 CONTRIBUTING.md create mode 100755 LICENSE create mode 100755 README.md create mode 100755 bin/compile.js create mode 100755 bin/karma.js create mode 100755 bin/server.js create mode 100755 build/karma.conf.js create mode 100755 build/webpack/_base.js create mode 100755 build/webpack/_development.js create mode 100755 build/webpack/_production.js create mode 100755 build/webpack/index.js create mode 100755 config/_base.js create mode 100755 config/_development.js create mode 100755 config/_production.js create mode 100755 config/index.js create mode 100755 jsconfig.json create mode 100755 package.json create mode 100755 server/app.js create mode 100755 server/middleware/webpack-dev.js create mode 100755 server/middleware/webpack-hmr.js create mode 100755 src/app.js create mode 100755 src/assets/favicon.ico create mode 100755 src/components/.gitkeep create mode 100755 src/containers/DevTools.js create mode 100755 src/containers/DevToolsWindow.js create mode 100755 src/containers/Root.js create mode 100755 src/index.html create mode 100755 src/layouts/CoreLayout.js create mode 100755 src/redux/configureStore.js create mode 100755 src/redux/modules/counter.js create mode 100755 src/redux/modules/index.js create mode 100755 src/redux/utils/createDevToolsWindow.js create mode 100755 src/routes/index.js create mode 100755 src/styles/_base.scss create mode 100755 src/styles/core.scss create mode 100755 src/styles/vendor/_normalize.scss create mode 100755 src/views/AboutView.js create mode 100755 src/views/HomeView.js create mode 100755 src/views/HomeView.scss create mode 100755 tests/.eslintrc create mode 100755 tests/framework.spec.js create mode 100755 tests/layouts/CoreLayout.spec.js create mode 100755 tests/views/HomeView.spec.js diff --git a/.babelrc b/.babelrc new file mode 100755 index 0000000..d5d19f0 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015", "react", "stage-0"], + "plugins": ["transform-runtime", "add-module-exports"] +} diff --git a/.editorconfig b/.editorconfig new file mode 100755 index 0000000..90913b4 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,30 @@ +# http://editorconfig.org + +# A special property that should be specified at the top of the file outside of +# any sections. Set to true to stop .editor config file search on current file +root = true + +[*] +# Indentation style +# Possible values - tab, space +indent_style = space + +# Indentation size in single-spaced characters +# Possible values - an integer, tab +indent_size = 2 + +# Line ending file format +# Possible values - lf, crlf, cr +end_of_line = lf + +# File character encoding +# Possible values - latin1, utf-8, utf-16be, utf-16le +charset = utf-8 + +# Denotes whether to trim whitespace at the end of lines +# Possible values - true, false +trim_trailing_whitespace = true + +# Denotes whether file should end with a newline +# Possible values - true, false +insert_final_newline = true diff --git a/.env b/.env new file mode 100755 index 0000000..a3e9d88 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +NODE_ENV=development +DEBUG=app:* diff --git a/.eslintignore b/.eslintignore new file mode 100755 index 0000000..223f0dd --- /dev/null +++ b/.eslintignore @@ -0,0 +1,5 @@ +coverage/** +node_modules/** +dist/** +*.spec.js +src/index.html diff --git a/.eslintrc b/.eslintrc new file mode 100755 index 0000000..d8888aa --- /dev/null +++ b/.eslintrc @@ -0,0 +1,19 @@ +{ + "parser" : "babel-eslint", + "extends" : [ + "standard", + "standard-react" + ], + "env" : { + "browser" : true + }, + "globals" : { + "__DEV__" : false, + "__PROD__" : false, + "__DEBUG__" : false, + "__DEBUG_NEW_WINDOW__" : false + }, + "rules": { + "semi" : [2, "never"] + } +} diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..e01809d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_STORE +*.log +.idea +node_modules + +dist +coverage diff --git a/.travis.yml b/.travis.yml new file mode 100755 index 0000000..08c664b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +sudo: false +language: node_js +node_js: + - "5.0" + +cache: + directories: + - node_modules + +install: + - npm install + +script: + - NODE_ENV=development npm run deploy + - NODE_ENV=production npm run deploy diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..42274c6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,341 @@ +Changelog +========= + +1.0.0 +----- + +### Features +* Upgraded from Babel 5 to Babel 6 :tada: +* Added script to copy static assets from ~src/assets to ~/dist during compilation +* Added CSS Modules (can be toggled on/off in config file) +* Enabled source maps for CSS +* Added `postcss-loader` +* Added `debug` module to replace `console.log` +* Added `json-loader` +* Added `url-loader` for `(png|jpg)` files +* Added `redux-actions` with demo +* Upgraded `redux-devtools` from `^3.0.0-beta` to `^3.0.0` +* Upgraded `redux-simple-router` from `^0.0.10` to `^1.0.0` +* Upgraded `isparta` from `^2.0.0` to `^3.0.0` +* Replaced `karma-sinon-chai` with `karma-chai-sinon` for peerDependencies fix +* Added sample asynchronous action +* Added example `composes` style to demo CSS modules in `HomeView` +* Added `lint:fix` npm script +* Added CONTRIBUTING document +* Added placeholder favicon + +### Improvements +* Refactored application to follow ducks-like architecture +* Improved how configuration determines when to apply HMR-specific Babel transforms +* Replaced explicit aliases with `resolve.root` +* Renamed karma configuration file to more widely-known `karma.conf` +* Made `CoreLayout` a pure (stateless) component +* Renamed debug namespace from `kit:*` to `app:*` +* Standardized coding conventions +* Added ability to easily specify environment-specific configuration overrides +* Extended available configuration options +* Improved miscellaneous documentation +* Refactored webpack middleware in express server into separate files + +### Fixes +* Fixed DevTools imports so they are longer included in production builds +* Added CSS best practices to root tag, node, and `core.scss` file +* Disabled manifest extraction due to broken production builds +* Updated Webpack dev server uses explicit publicPath during live development +* Fixed Karma running tests twice after file change during watch mode + +### Deprecations +* Removed `eslint-config-airbnb` +* Deprecated support for Node `^4.0.0` + +0.18.0 +----- + +### Features +* Replaces `webpack-dev-server` with `Express` and webpack middleware +* Replaces `redux-router` with `redux-simple-router` +* Use `postcss-loader` for autoprefixing rather than autoprefixer-loader +* Configuration will now warn you of missing dependencies for vendor bundle +* Upgrade `react-router` from `1.0.0-rc1` -> `^1.0.0` +* Upgrade `css-loader` from `0.21.0` -> `0.23.0` +* Upgrade `eslint-config-airbnb` from `0.1.0` to `^1.0.0` +* Upgrade `karma-spec-reporter` from `0.0.21` to `0.0.22` +* Upgrade `extract-text-webpack-plugin` from `^0.8.0` to `^0.9.0` + +### Improvements +* Compiled `index.html` is now minified +* Content hashes are now injected directly into the filename rather than appended as query strings +* Better reporting of webpack build status +* Use object-style configuration for `sass-loader` rather than inline query string +* Rename `test:lint` task to `lint:tests` +* Various documentation improvements + +### Fixes +* Content hash is now longer duplicated in CSS bundle +* Karma plugins are autoloaded now, rather than explicitly defined +* Removes extraneous wrapping div in `Root` container +* Fixes awkwardly named arguments to `createReducer` utility +* Add missing alias to `~/src/store` + +0.17.0 +------ + +### Features +* Karma coverage now generates proper coverage reports +* Added chai-as-promised +* Added `npm run lint` script to lint all `~/src` code +* Added `npm run test:lint` script to lint all `*.spec.js` files in `~/tests` +* Updated `npm run deploy` to explicitly run linter on source code +* Added `dotenv` (thanks [dougvk](https://github.com/dougvk)) + +### Improvements +* Renamed application entry point from `index.js` -> `app.js` (clarifies intent and helps with coverage reports) +* Refactored sample counter constants and actions to their appropriate locations (thanks [kyleect](https://github.com/kyleect)) +* Devtools in `npm run dev:nw` now take up the full window (thanks [jhgg](https://github.com/jhgg)) +* Webpack no longer runs an eslint pre-loader (cleans up console messages while developing) +* Moved tests into their own directory (alleviates lint/organization issues) +* Renamed `stores` to `store` to be more intuitive +* Webpack-dev-server now uses a configurable host (thanks [waynelkh](https://github.com/waynelkh)) +* Sass-loader is now configured independently of its loader definition +* Upgraded `redux-devtools` from `^2.0.0` -> `^3.0.0` +* Upgraded `react-transform-catch-errors` from `^0.1.0` -> `^1.0.0` + +### Fixes +* Fix .editorconfig missing a setting that caused it to not be picked up in all IDE's +* Cleans up miscellaneous lint errors. + + +0.16.0 +------ + +### Features +* Adds redux-router (thanks to [dougvk](https://github.com/dougvk)) +* Adds redux-thunk middleware +* Adds loaders for font files (thanks to [nodkz](https://github.com/nodkz)) +* Adds url loader +* Upgrades React dependencies to stable `^0.14.0` +* Upgrades react-redux to `^4.0.0` + +### Improvements +* Cleans up unused configuration settings +* configureStore no longer relies on a global variable to determine whether or not to enable devtool middleware +* Removes unused invariant and ImmutableJS vendor dependencies +* Removes unused webpack-clean plugin +* Tweaks .js loader configuration to make it easier to add json-loader +* Updates counter example to demonstrate `mapDispatchToProps` +* Force `components` directory inclusion +* Documentation improvements + +0.15.2 +------ + +### Fixes +* Remove unused/broken "minify" property provided to HtmlWebpackPlugin configuration. + +0.15.1 +------ + +### Fixes +* Dev server now loads the correct Webpack configuration with HMR enabled. +* Redbox-React error catcher is now loaded correctly in development. + +0.15.0 +------ + +### Fixes +* HMR is now longer enabled for simple compilations. You can now compile development builds that won't constantly ping a non-existent dev server. +* react-transform-hmr now only runs when HMR is enabled. + +### Improvements +* Unit tests now only run in watch mode when explicitly requested. This makes it much more convenient to run tests on any environment without having to struggle with the `singleRun` flag in Karma. +* There is now only a single webpack configuration (rather than one for the client and one for the server). As a result, configuration has once again been split into a base configuration which is then extended based on the current `NODE_ENV`. + +### Deprecations +* Removed Koa server (sad days). + +0.14.0 +------ + +#### Features +* Replaces `react-transform-webpack-hmr` with its replacement `react-transform-hmr`. Thanks to [daviferreira](https://github.com/daviferreira). +* Replaces `delicate-error-reporter` with `redbox-react`. Thanks to [bulby97](https://github.com/bulby97). +* Created a `no-server` branch [here](https://github.com/davezuko/react-redux-starter-kit/tree/no-server) to make it easier for users who don't care about Koa. + +#### Improvements +* Renames `client` directory to `src` to be more intuitive. +* `inline-source-map` has been replaced by `source-map` as the default webpack devTool to reduce build sizes. +* Refactors configuration file to focus on commonly-configured options rather than mixing them with internal configuration. +* Swaps `dev` and `dev:debug` so debug tools are now enabled by default and can be disabled instead with `dev:no-debug`. +* Repositions Redux devtools so they no longer block errors displayed by `redbox-react`. +* Adds explicit directory references to some `import` statements to clarify which are from from `npm` and which are local. + +#### Fixes +* Fixes naming in `HomeView` where `mapStateToProps` was incorrectly written as `mapDispatchToProps`. + +#### Deprecations +* Removes local test utilities (in `~/src/utils/test`). + +0.13.0 +------ + +#### Features +* Adds `react-transform-catch-errors` along with `delicate-error-reporter`. Thanks [bulby97](https://github.com/bulby97) for this! + +#### Fixes +* ExtractTextPlugin is once again production only. This fixes an issue where styles wouldn't be hot reloaded with Webpack. + +0.12.0 +------ + +#### Features +* Upgrades react-router to `^3.0.0`. This is the only reason for the minor-level version bump. +* Webpack now uses OccurrenceOrderPlugin to produce consistent bundle hashes. + +#### Fixes +* Adds `history` to vendor dependencies to fix HMR caused by upgrade to react-router `1.0.0-rc` + +#### Improvements +* Server no longer modifies initial counter state by default. +* Adds invariant error in route rendering method to enforce router state definition through props. + +0.11.0 +------ + +#### Features +* Upgrades all React dependencies to `0.14.0-rc1` +* Upgrades react-router to `1.0.0-rc` + * Updates client and server rendering accordingly +* Adds Sinon-Chai for improved assertions and function spies +* Adds option to disable eslint when in development + +#### Improvements +* Improved example unit tests using react-addons-test-utils and Sinon Chai + +0.10.0 +------ + +#### Features +* Initial state can now be injected from the server (still WIP). +* Adds react-addons-test-utils as a devDependency. + +#### Improvements +* Eslint no longer prevents webpack from bundling in development mode if an error is emitted. + * See: https://github.com/MoOx/eslint-loader/issues/23 +* Updates all `.jsx` files to `.js`. (https://github.com/davezuko/react-redux-starter-kit/issues/37) +* Updates all React component file names to be ProperCased. + +0.9.0 +----- + +#### Features +* Koa server now uses gzip middleware. + +#### Improvements +* Switches out react-hot-loader in favor of [react-transform-webpack-hmr](https://github.com/gaearon/react-transform-webpack-hmr). +* Eslint configuration now uses Airbnb's configuration (slightly softened). +* Migrates all actual development dependencies to devDependencies in `package.json`. +* Example store and view are now more intuitive (simple counter display). +* CSS-loader dependency upgraded from `0.16.0` to `0.17.0`. + +#### Deprecations +* Removes unnecessary object-assign dependency. + +0.8.0 +----- + +#### Improvements +* All build-, server-, and client-related code is now ES6. +* Significantly refactors how client and server webpack configs are built. +* `reducers/index.js` now exports combined root reducer. +* Client application code now lives in `~/client` instead of `~/src` in order to conform to Redux standards. + +#### Fixes +* Redux store now explicitly handles HMR. + +#### Changes +* Webpack compiler configurations are no longer merged on top of a base default configuration. This can become unwieldy and even though explicitly writing each configuration file out is more verbose, it ends up being more maintainable. + +#### Deprecations +* Quiet mode has been removed (`npm run dev:quiet`). + +0.7.0 +----- +#### New Features +* Support for redux-devtools in separate window with `dev:debugnw` + - Thanks to [mlusetti](https://github.com/mlusetti) + +#### Improvements +* Upgrades react to `0.14.0-beta3` +* Upgrades react to `0.14.0-beta3` +* Upgrades redux to `^2.0.0` +* Upgrades redux-devtools to `^2.0.0` +* Upgrades react-redux to `^2.0.0` + +#### Fixes +* Configuration file name trimming on Windows machines + - Thanks to [nuragic](https://github.com/nuragic) + +0.6.0 +----- + +#### Fixes +* Fixes potential spacing issues when Webpack tries to load a config file. + - Thanks to [nuragic](https://github.com/nuragic) for his [PR](https://github.com/davezuko/react-redux-starter-kit/pull/32) + +#### Improvements +* Upgrades koa to `1.0.0` +* Upgrades react-redux to `1.0.0` +* Upgrades object-assign to `0.4.0` + +0.5.0 +----- + +#### Improvements +* Restructures src directory so filenames are more identifiable. + +#### Breaking Changes +* Removes action-creators alias as it's unlikely to be used. + +0.4.0 +----- + +#### Improvements +* Cleans up/removes example code per https://github.com/davezuko/react-redux-starter-kit/issues/20 + +0.3.1 +----- + +#### Fixes +* https://github.com/davezuko/react-redux-starter-kit/issues/19 + - Invalid initialStates from server-side router will now yield to the next middleware. + +0.3.0 +----- + +#### Improvements +* Bumps Redux version to first major release. +* Bumps Redux-devtools version to first major release. + +#### Fixes +* Fixes broken hot-reload in `:debug` mode. + - Temporarily fixed by moving `redux-devtools` into the vendor bundle. + +0.2.0 +----- + +#### Improvements +* Weakens various eslint rules that were too opinionated. + - notable: `one-var` and `key-spacing`. + +Thanks to [StevenLangbroek](https://github.com/StevenLangbroek) for the following: +* Adds alias `utils` to reference `~/src/utils` +* Adds `createConstants` utility. +* Adds `createReducer` utility. +* Refactors `todos` reducer to use a function map rather than switch statements. + +#### Fixes +* Nested routes are now loaded correctly in react-router when using BrowserHistory. +* Bundle compilation now fails if an eslint error is encountered when running a production build. + - Thanks [clearjs](https://github.com/clearjs) +* Upgrades all outdated dependencies. + - Karma, eslint, babel, sass-loader, and a handful more. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100755 index 0000000..ce41e1a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,59 @@ +# Contributing Guidelines + +Some basic conventions for contributing to this project. + +### General + +Please make sure that there aren't existing pull requests attempting to address the issue mentioned. Likewise, please check for issues related to update, as someone else may be working on the issue in a branch or fork. + +* Non-trivial changes should be discussed in an issue first +* Develop in a topic branch, not master +* Squash your commits + +### Linting + +Please check your code using `npm run lint` before submitting your pull requests, as the CI build will fail if `eslint` fails. + +### Commit Message Format + +Each commit message should include a **type**, a **scope** and a **subject**: + +``` + (): +``` + +Lines should not exceed 100 characters. This allows the message to be easier to read on github as well as in various git tools and produces a nice, neat commit log ie: + +``` + #271 feat(standard): add style config and refactor to match + #270 fix(config): only override publicPath when served by webpack + #269 feat(eslint-config-defaults): replace eslint-config-airbnb + #268 feat(config): allow user to configure webpack stats output +``` + +#### Type + +Must be one of the following: + +* **feat**: A new feature +* **fix**: A bug fix +* **docs**: Documentation only changes +* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing + semi-colons, etc) +* **refactor**: A code change that neither fixes a bug or adds a feature +* **test**: Adding missing tests +* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation + generation + +#### Scope + +The scope could be anything specifying place of the commit change. For example `webpack`, +`babel`, `redux` etc... + +#### Subject + +The subject contains succinct description of the change: + +* use the imperative, present tense: "change" not "changed" nor "changes" +* don't capitalize first letter +* no dot (.) at the end diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..9daa340 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 David Zukowski + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..ecd69d0 --- /dev/null +++ b/README.md @@ -0,0 +1,256 @@ +React Redux Starter Kit +======================= + +[![Join the chat at https://gitter.im/davezuko/react-redux-starter-kit](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/davezuko/react-redux-starter-kit?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Build Status](https://travis-ci.org/davezuko/react-redux-starter-kit.svg?branch=master)](https://travis-ci.org/davezuko/react-redux-starter-kit?branch=master) +[![dependencies](https://david-dm.org/davezuko/react-redux-starter-kit.svg)](https://david-dm.org/davezuko/react-redux-starter-kit) +[![devDependency Status](https://david-dm.org/davezuko/react-redux-starter-kit/dev-status.svg)](https://david-dm.org/davezuko/react-redux-starter-kit#info=devDependencies) +[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) + +> ### This Project Recently Upgraded to Babel 6! +> Woohoo! If you'd like to try it out, you're welcome to build directly from the master branch. However, if troubleshooting issues with Babel isn't quite your thing, just pull the [stable v0.18.0 release](https://github.com/davezuko/react-redux-starter-kit/tree/v0.18.0) and continue on your way with Babel 5. + +> ### Want Semicolons? +> After installing npm dependencies, open `.eslintrc`, change the `semi` rule from `never` to `always`, and then run `npm run lint:fix` -- Easy as that! Alternatively, use the same npm script after installing and extending your preferred ESLint configuration; it's easy to customize the project's code style to suit your team's needs. See, we can coexist peacefully. + +This starter kit is designed to get you up and running with a bunch of awesome new front-end technologies, all on top of a configurable, feature-rich webpack build system that's already setup to provide hot reloading, CSS modules with Sass support, unit testing, code coverage reports, bundle splitting, and a whole lot more. + +The primary goal of this project is to remain as **unopinionated** as possible. Its purpose is not to dictate your project structure or to demonstrate a complete sample application, but to provide a set of tools intended to make front-end development robust, easy, and, most importantly, fun. Check out the full feature list below! + +Table of Contents +----------------- +1. [Requirements](#requirements) +1. [Features](#features) +1. [Getting Started](#getting-started) +1. [Usage](#usage) +1. [Structure](#structure) +1. [Webpack](#webpack) +1. [Server](#server) +1. [Styles](#styles) +1. [Testing](#testing) +1. [Deployment](#deployment) +1. [Troubleshooting](#troubleshooting) + +Requirements +------------ + +Node `^5.0.0` + +Features +-------- + +* [React](https://github.com/facebook/react) (`^0.14.0`) + * Includes react-addons-test-utils (`^0.14.0`) +* [Redux](https://github.com/gaearon/redux) (`^3.0.0`) + * react-redux (`^4.0.0`) + * redux-devtools + * use `npm run dev:nw` to display them in a separate window. + * redux-thunk middleware +* [react-router](https://github.com/rackt/react-router) (`^1.0.0`) +* [redux-simple-router](https://github.com/jlongster/redux-simple-router) (`^1.0.0`) +* [Webpack](https://github.com/webpack/webpack) + * [CSS modules!](https://github.com/css-modules/css-modules) + * sass-loader + * postcss-loader with cssnano for style autoprefixing and minification + * Bundle splitting for app and vendor dependencies + * CSS extraction during production builds + * Loaders for fonts and images +* [Express](https://github.com/strongloop/express) + * webpack-dev-middleware + * webpack-hot-middleware +* [Karma](https://github.com/karma-runner/karma) + * Mocha w/ chai, sinon-chai, and chai-as-promised + * PhantomJS + * Code coverage reports +* [Babel](https://github.com/babel/babel) (`^6.3.0`) + * react-transform-hmr for hot reloading + * `react-transform-catch-errors` with `redbox-react` for more visible error reporting + * Uses babel runtime rather than inline transformations +* [ESLint](http://eslint.org) + * Uses [Standard Style](https://github.com/feross/standard) by default, but you're welcome to change this! + * Includes separate test-specific `.eslintrc` to work with Mocha and Chai + +Getting Started +--------------- + +Just clone the repo and install the necessary node modules: + +```shell +$ git clone https://github.com/davezuko/react-redux-starter-kit.git +$ cd react-redux-starter-kit +$ npm install # Install Node modules listed in ./package.json (may take a while the first time) +$ npm start # Compile and launch +``` + +Usage +----- + +Before delving into the descriptions of each available npm script, here's a brief summary of the three which will most likely be your bread and butter: + +* Doing live development? Use `npm start` to spin up the dev server. +* Compiling the application to disk? Use `npm run compile`. +* Deploying to an environment? `npm run deploy` can help with that. + +**NOTE:** This package makes use of [debug](https://github.com/visionmedia/debug) to improve your debugging experience. For convenience, all of messages are prefixed with `app:*`. If you'd like to to change what debug statements are displayed, you can override the `DEBUG` environment variable to `app:*` via the CLI (e.g. `DEBUG=app:* npm start`) or update the `~/.env` file. + +Great, now that introductions have been made here's everything in full detail: + +* `npm start` - Spins up express server to serve your app at `localhost:3000`. HMR will be enabled in development. +* `npm run compile` - Compiles the application to disk (`~/dist` by default). +* `npm run dev:nw` - Same as `npm start`, but opens the redux devtools in a new window. +* `npm run dev:no-debug` - Same as `npm start` but disables redux devtools. +* `npm run test` - Runs unit tests with Karma and generates a coverage report. +* `npm run test:dev` - Runs Karma and watches for changes to re-run tests; does not generate coverage reports. +* `npm run deploy`- Runs linter, tests, and then, on success, compiles your application to disk. +* `npm run lint`- Lint all `.js` files. +* `npm run lint:fix` - Lint and fix all `.js` files. [Read more on this](http://eslint.org/docs/user-guide/command-line-interface.html#fix). + +**NOTE:** Deploying to a specific environment? Make sure to specify your target `NODE_ENV` so webpack will use the correct configuration. For example: `NODE_ENV=production npm run compile` will compile your application with `~/build/webpack/_production.js`. + +### Configuration + +Basic project configuration can be found in `~/config/_base.js`. Here you'll be able to redefine your `src` and `dist` directories, adjust compilation settings, tweak your vendor dependencies, and more. For the most part, you should be able to make changes in here **without ever having to touch the webpack build configuration**. If you need environment-specific overrides, create a file with the name of target `NODE_ENV` prefixed by an `_` in `~/config` (see `~/config/_production.js` for an example). + +Common configuration options: + +* `dir_src` - application source code base path +* `dir_dist` - path to build compiled application to +* `server_host` - hostname for the express server +* `server_port` - port for the express server +* `compiler_css_modules` - whether or not to enable CSS modules +* `compiler_source_maps` - whether or not to generate source maps +* `compiler_vendor` - packages to separate into to the vendor bundle + +Structure +--------- + +The folder structure provided is only meant to serve as a guide, it is by no means prescriptive. It is something that has worked very well for me and my team, but use only what makes sense to you. + +``` +. +├── bin # Build/Start scripts +├── build # All build-related configuration +│ └── webpack # Environment-specific configuration files for webpack +├── config # Project configuration settings +├── server # Express application (uses webpack middleware) +│ └── app.js # Server application entry point +├── src # Application source code +│ ├── assets # Static assets (not imported anywhere in source code) +│ ├── components # Generic React Components (generally Dumb components) +│ ├── containers # Components that provide context (e.g. Redux Provider) +│ ├── layouts # Components that dictate major page structure +│ ├── redux # Redux-specific pieces +│ │ ├── modules # Collections of reducers/constants/actions +│ │ └── utils # Redux-specific helpers +│ ├── routes # Application route definitions +│ ├── styles # Application-wide styles (generally settings) +│ ├── views # Components that live at a route +│ └── app.js # Application bootstrap and rendering +└── tests # Unit tests +``` + +### Components vs. Views vs. Layouts + +**TL;DR:** They're all components. + +This distinction may not be important for you, but as an explanation: A **Layout** is something that describes an entire page structure, such as a fixed navigation, viewport, sidebar, and footer. Most applications will probably only have one layout, but keeping these components separate makes their intent clear. **Views** are components that live at routes, and are generally rendered within a **Layout**. What this ends up meaning is that, with this structure, nearly everything inside of **Components** ends up being a dumb component. + +Webpack +------- + +### Configuration +The webpack compiler configuration is located in `~/build/webpack`. Here you'll find configurations for each environment; `development` and `production` exist out of the box. + +**Note**: There has been a conscious decision to keep development-specific configuration (such as hot-reloading) out of `.babelrc`. By doing this, it's possible to create cleaner development builds (such as for teams that have a `dev` -> `stage` -> `production` workflow) that don't, for example, constantly poll for HMR updates. + +So why not just disable HMR? Well, as a further explanation, enabling `react-transform-hmr` in `.babelrc` but building the project without HMR enabled (think of running tests with `NODE_ENV=development` but without a dev server) causes errors to be thrown, so this decision also alleviates that issue. + +### Vendor Bundle +You can redefine which packages to bundle separately by modifying `compiler_vendor` in `~/config/_base.js`. These default to: + +```js +[ + 'history', + 'react', + 'react-redux', + 'react-router', + 'redux-simple-router', + 'redux' +] +``` + +### Webpack Root Resolve +Webpack is configured to make use of [resolve.root](http://webpack.github.io/docs/configuration.html#resolve-root), which lets you import local packages as if you were traversing from the root of your `~/src` directory. Here's an example: + +```js +// current file: ~/src/views/some/nested/View.js + +// What used to be this: +import SomeComponent from '../../../components/SomeComponent' + +// Can now be this: +import SomeComponent from 'components/SomeComponent' // Hooray! +``` + +### Globals + +These are global variables available to you anywhere in your source code. If you wish to modify them, they can be found as the `globals` key in `~/config/index.js`. + +* `process.env.NODE_ENV` - the active `NODE_ENV` when the build started +* `__DEV__` - True when `process.env.NODE_ENV` is `development` +* `__PROD__` - True when `process.env.NODE_ENV` is `production` + +Server +------ + +This starter kit comes packaged with an Express server. It's important to note that the sole purpose of this server is to provide `webpack-dev-middleware` and `webpack-hot-middleware` for hot module replacement. Using a custom Express app in place of webpack-dev-server will hopefully make it easier for users to extend the starter kit to include functionality such as back-end API's, isomorphic/universal rendering, and more -- all without bloating the base boilerplate. Because of this, it should be noted that the provided server is **not** production-ready. If you're deploying to production, take a look at [the deployment section](#deployment). + +Styles +------ + +Both `.scss` and `.css` file extensions are supported out of the box and are configured to use [CSS Modules](https://github.com/css-modules/css-modules). After being imported, styles will be processed with [PostCSS](https://github.com/postcss/postcss) for minification and autoprefixing, and will be extracted to a `.css` file during production builds. + +**NOTE:** If you're importing styles from a base styles directory (useful for generic, app-wide styles), you can make use of the `styles` alias, e.g.: + +```js +// current file: ~/src/components/some/nested/component/index.jsx +import 'styles/core.scss' // this imports ~/src/styles/core.scss +``` + +Furthermore, this `styles` directory is aliased for sass imports, which further eliminates manual directory traversing; this is especially useful for importing variables/mixins. + +Here's an example: + +```scss +// current file: ~/src/styles/some/nested/style.scss +// what used to be this (where base is ~/src/styles/_base.scss): +@import '../../base'; + +// can now be this: +@import 'base'; +``` + +Testing +------- + +To add a unit test, simply create a `.spec.js` file anywhere in `~/tests`. Karma will pick up on these files automatically, and Mocha and Chai will be available within your test without the need to import them. + +Coverage reports will be compiled to `~/coverage` by default. If you wish to change what reporters are used and where reports are compiled, you can do so by modifying `coverage_reporters` in `~/config/_base.js`. + +Deployment +---------- + +Out of the box, this starter kit is deployable by serving the `~/dist` folder generated by `npm run compile` (make sure to specify your target `NODE_ENV` as well). This project does not concern itself with the details of server-side rendering or API structure, since that demands an opinionated structure that makes it difficult to extend the starter kit. However, if you do need help with more advanced deployment strategies, here are a few tips: + +If you are serving the application via a web server such as nginx, make sure to direct incoming routes to the root `~/dist/index.html` file and let react-router take care of the rest. The Express server that comes with the starter kit is able to be extended to serve as an API or whatever else you need, but that's entirely up to you. + +Have more questions? Feel free to submit an issue or join the Gitter chat! + +Troubleshooting +--------------- + +### `npm run dev:nw` produces `cannot read location of undefined.` + +This is most likely because the new window has been blocked by your popup blocker, so make sure it's disabled before trying again. + +Reference: [issue 110](https://github.com/davezuko/react-redux-starter-kit/issues/110) diff --git a/bin/compile.js b/bin/compile.js new file mode 100755 index 0000000..a02b5c3 --- /dev/null +++ b/bin/compile.js @@ -0,0 +1,37 @@ +require('babel-register') + +const config = require('../config') +const debug = require('debug')('app:bin:compile') +const fs = require('fs-extra') + +const paths = config.utils_paths + +debug('Create webpack compiler.') +const compiler = require('webpack')(require('../build/webpack')) + +compiler.run(function (err, stats) { + const jsonStats = stats.toJson() + + debug('Webpack compile completed.') + console.log(stats.toString(config.compiler_stats)) + + if (err) { + debug('Webpack compiler encountered a fatal error.', err) + process.exit(1) + } else if (jsonStats.errors.length > 0) { + debug('Webpack compiler encountered errors.') + console.log(jsonStats.errors) + process.exit(1) + } else if (jsonStats.warnings.length > 0) { + debug('Webpack compiler encountered warnings.') + + if (config.compiler_fail_on_warning) { + process.exit(1) + } + } else { + debug('No errors or warnings encountered.') + } + + debug('Copy static assets to dist folder.') + fs.copySync(paths.client('assets'), paths.dist()) +}) diff --git a/bin/karma.js b/bin/karma.js new file mode 100755 index 0000000..be75030 --- /dev/null +++ b/bin/karma.js @@ -0,0 +1,3 @@ +require('babel-register') + +module.exports = require('../build/karma.conf') diff --git a/bin/server.js b/bin/server.js new file mode 100755 index 0000000..8dc8f21 --- /dev/null +++ b/bin/server.js @@ -0,0 +1,12 @@ +require('babel-register') + +const config = require('../config') +const server = require('../server/app') +const debug = require('debug')('app:bin:server') + +const host = config.server_host +const port = config.server_port + +server.listen(port, host, function () { + debug('Server is now running at ' + host + ':' + port + '.') +}) diff --git a/build/karma.conf.js b/build/karma.conf.js new file mode 100755 index 0000000..f68a23d --- /dev/null +++ b/build/karma.conf.js @@ -0,0 +1,53 @@ +import { argv } from 'yargs' +import config from '../config' +import webpackConfig from './webpack' + +const debug = require('debug')('app:karma') +debug('Create configuration.') + +const karmaConfig = { + basePath: '../', // project root in relation to bin/karma.js + files: [ + './node_modules/phantomjs-polyfill/bind-polyfill.js', + { + pattern: `./${config.dir_test}/**/*.js`, + watched: false, + served: true, + included: true + } + ], + singleRun: !argv.watch, + frameworks: ['mocha', 'chai-sinon', 'chai-as-promised', 'chai'], + preprocessors: { + [`${config.dir_test}/**/*.js`]: ['webpack'] + }, + reporters: ['spec'], + browsers: ['PhantomJS'], + webpack: { + devtool: 'inline-source-map', + resolve: webpackConfig.resolve, + plugins: webpackConfig.plugins + .filter(plugin => !plugin.__KARMA_IGNORE__), + module: { + loaders: webpackConfig.module.loaders + }, + sassLoader: webpackConfig.sassLoader + }, + webpackMiddleware: { + noInfo: true + }, + coverageReporter: { + reporters: config.coverage_reporters + } +} + +if (config.coverage_enabled) { + karmaConfig.reporters.push('coverage') + karmaConfig.webpack.module.preLoaders = [{ + test: /\.(js|jsx)$/, + include: new RegExp(config.dir_client), + loader: 'isparta' + }] +} + +export default (cfg) => cfg.set(karmaConfig) diff --git a/build/webpack/_base.js b/build/webpack/_base.js new file mode 100755 index 0000000..531924f --- /dev/null +++ b/build/webpack/_base.js @@ -0,0 +1,147 @@ +import webpack from 'webpack' +import cssnano from 'cssnano' +import HtmlWebpackPlugin from 'html-webpack-plugin' +import config from '../../config' +import _debug from 'debug' + +const paths = config.utils_paths +const debug = _debug('app:webpack:_base') +debug('Create configuration.') + +const CSS_LOADER = !config.compiler_css_modules + ? 'css?sourceMap' + : [ + 'css?modules', + 'sourceMap', + 'importLoaders=1', + 'localIdentName=[name]__[local]___[hash:base64:5]' + ].join('&') + +const webpackConfig = { + name: 'client', + target: 'web', + entry: { + app: [ + paths.base(config.dir_client) + '/app.js' + ], + vendor: config.compiler_vendor + }, + output: { + filename: `[name].[${config.compiler_hash_type}].js`, + path: paths.base(config.dir_dist), + publicPath: config.compiler_public_path + }, + plugins: [ + new webpack.DefinePlugin(config.globals), + new webpack.optimize.OccurrenceOrderPlugin(), + new webpack.optimize.DedupePlugin(), + new HtmlWebpackPlugin({ + template: paths.client('index.html'), + hash: false, + favicon: paths.client('assets/favicon.ico'), + filename: 'index.html', + inject: 'body', + minify: { + collapseWhitespace: true + } + }) + ], + resolve: { + root: paths.base(config.dir_client), + extensions: ['', '.js', '.jsx'] + }, + module: { + preLoaders: [ + { + test: /\.js$/, + loader: 'eslint', + exclude: /node_modules/ + } + ], + loaders: [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + loader: 'babel', + query: { + cacheDirectory: true, + plugins: ['transform-runtime', 'add-module-exports'], + presets: ['es2015', 'react', 'stage-0'], + env: { + development: { + plugins: [ + ['react-transform', { + // omit HMR plugin by default and _only_ load in hot mode + transforms: [{ + transform: 'react-transform-catch-errors', + imports: ['react', 'redbox-react'] + }] + }] + ] + } + } + } + }, + { + test: /\.json$/, + loader: 'json' + }, + { + test: /\.scss$/, + loaders: [ + 'style', + CSS_LOADER, + 'postcss', + 'sass' + ] + }, + { + test: /\.css$/, + loaders: [ + 'style', + CSS_LOADER, + 'postcss' + ] + }, + /* eslint-disable */ + { test: /\.woff(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff' }, + { test: /\.woff2(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/font-woff2' }, + { test: /\.ttf(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=application/octet-stream' }, + { test: /\.eot(\?.*)?$/, loader: 'file?prefix=fonts/&name=[path][name].[ext]' }, + { test: /\.svg(\?.*)?$/, loader: 'url?prefix=fonts/&name=[path][name].[ext]&limit=10000&mimetype=image/svg+xml' }, + { test: /\.(png|jpg)$/, loader: 'url?limit=8192' } + /* eslint-enable */ + ] + }, + sassLoader: { + includePaths: paths.client('styles') + }, + postcss: [ + cssnano({ + sourcemap: true, + autoprefixer: { + add: true, + remove: true, + browsers: ['last 2 versions'] + }, + discardComments: { + removeAll: true + } + }) + ], + eslint: { + configFile: paths.base('.eslintrc') + } +} + +// NOTE: this is a temporary workaround. I don't know how to get Karma +// to include the vendor bundle that webpack creates, so to get around that +// we remove the bundle splitting when webpack is used with Karma. +const commonChunkPlugin = new webpack.optimize.CommonsChunkPlugin({ + names: ['vendor'] +}) +commonChunkPlugin.__KARMA_IGNORE__ = true + +webpackConfig.plugins.push(commonChunkPlugin) + +export default webpackConfig diff --git a/build/webpack/_development.js b/build/webpack/_development.js new file mode 100755 index 0000000..050549d --- /dev/null +++ b/build/webpack/_development.js @@ -0,0 +1,58 @@ +/* eslint key-spacing:0 */ +import webpack from 'webpack' +import config from '../../config' +import _debug from 'debug' + +const debug = _debug('app:webpack:development') + +export default (webpackConfig) => { + debug('Create configuration.') + + debug('Override devtool with cheap-module-eval-source-map.') + webpackConfig.devtool = 'cheap-module-eval-source-map' + + // ------------------------------------ + // Enable HMR if Configured + // ------------------------------------ + if (config.compiler_enable_hmr) { + debug('Enable Hot Module Replacement (HMR).') + + webpackConfig.entry.app.push( + 'webpack-hot-middleware/client?path=/__webpack_hmr' + ) + + webpackConfig.plugins.push( + new webpack.HotModuleReplacementPlugin(), + new webpack.NoErrorsPlugin() + ) + + webpackConfig.eslint.emitWarning = true + + // We need to apply the react-transform HMR plugin to the Babel configuration, + // but _only_ when HMR is enabled. Putting this in the default development + // configuration will break other tasks such as test:unit because Webpack + // HMR is not enabled there, and these transforms require it. + webpackConfig.module.loaders = webpackConfig.module.loaders.map(loader => { + if (/babel/.test(loader.loader)) { + debug('Apply react-transform-hmr to babel development transforms') + + if (loader.query.env.development.plugins[0][0] !== 'react-transform') { + debug('ERROR: react-transform must be the first plugin') + return loader + } + + const reactTransformHmr = { + transform : 'react-transform-hmr', + imports : ['react'], + locals : ['module'] + } + loader.query.env.development.plugins[0][1].transforms + .push(reactTransformHmr) + } + + return loader + }) + } + + return webpackConfig +} diff --git a/build/webpack/_production.js b/build/webpack/_production.js new file mode 100755 index 0000000..dc6a9f5 --- /dev/null +++ b/build/webpack/_production.js @@ -0,0 +1,46 @@ +import webpack from 'webpack' +import ExtractTextPlugin from 'extract-text-webpack-plugin' +import config from '../../config' +import _debug from 'debug' + +const debug = _debug('app:webpack:production') + +export default (webpackConfig) => { + debug('Create configuration.') + + if (config.compiler_source_maps) { + debug('Source maps enabled for production.') + webpackConfig.devtool = 'source-map' + } else { + debug('Source maps are disabled in production.') + } + + debug('Apply ExtractTextPlugin to CSS loaders.') + webpackConfig.module.loaders = webpackConfig.module.loaders.map(loader => { + if (!loader.loaders || + !loader.loaders.find(name => /css/.test(name.split('?')[0]))) { + return loader + } + + const [first, ...rest] = loader.loaders + loader.loader = ExtractTextPlugin.extract(first, rest.join('!')) + delete loader.loaders + + return loader + }) + + debug('Inject ExtractText and UglifyJS plugins.') + webpackConfig.plugins.push( + new ExtractTextPlugin('[name].[contenthash].css', { + allChunks: true + }), + new webpack.optimize.UglifyJsPlugin({ + compress: { + unused: true, + dead_code: true + } + }) + ) + + return webpackConfig +} diff --git a/build/webpack/index.js b/build/webpack/index.js new file mode 100755 index 0000000..d858d4f --- /dev/null +++ b/build/webpack/index.js @@ -0,0 +1,4 @@ +import config from '../../config' +import base from './_base' + +export default require(`./_${config.env}`)(base) diff --git a/config/_base.js b/config/_base.js new file mode 100755 index 0000000..4e0ae55 --- /dev/null +++ b/config/_base.js @@ -0,0 +1,115 @@ +/* eslint key-spacing:0 spaced-comment:0 */ +import _debug from 'debug' +import path from 'path' +import { argv } from 'yargs' + +const debug = _debug('app:config:_base') +const config = { + env : process.env.NODE_ENV, + + // ---------------------------------- + // Project Structure + // ---------------------------------- + path_base : path.resolve(__dirname, '../'), + dir_client : 'src', + dir_dist : 'dist', + dir_server : 'server', + dir_test : 'tests', + + // ---------------------------------- + // Server Configuration + // ---------------------------------- + server_host : 'localhost', + server_port : process.env.PORT || 3000, + + // ---------------------------------- + // Compiler Configuration + // ---------------------------------- + compiler_css_modules : true, + compiler_enable_hmr : false, + compiler_source_maps : true, + compiler_hash_type : 'hash', + compiler_fail_on_warning : false, + compiler_quiet : false, + compiler_public_path : '/', + compiler_stats : { + chunks : false, + chunkModules : false, + colors : true + }, + compiler_vendor : [ + 'history', + 'react', + 'react-redux', + 'react-router', + 'redux', + 'redux-actions', + 'redux-simple-router' + ], + + // ---------------------------------- + // Test Configuration + // ---------------------------------- + coverage_enabled : !argv.watch, + coverage_reporters : [ + { type : 'text-summary' }, + { type : 'html', dir : 'coverage' } + ] +} + +/************************************************ +------------------------------------------------- + +All Internal Configuration Below +Edit at Your Own Risk + +------------------------------------------------- +************************************************/ + +// ------------------------------------ +// Environment +// ------------------------------------ +config.globals = { + 'process.env' : { + 'NODE_ENV' : JSON.stringify(config.env) + }, + 'NODE_ENV' : config.env, + '__DEV__' : config.env === 'development', + '__PROD__' : config.env === 'production', + '__DEBUG__' : config.env === 'development' && !argv.no_debug, + '__DEBUG_NEW_WINDOW__' : !!argv.nw +} + +// ------------------------------------ +// Validate Vendor Dependencies +// ------------------------------------ +const pkg = require('../package.json') + +config.compiler_vendor = config.compiler_vendor + .filter(dep => { + if (pkg.dependencies[dep]) return true + + debug( + `Package "${dep}" was not found as an npm dependency in package.json; ` + + `it won't be included in the webpack vendor bundle.\n` + + `Consider removing it from vendor_dependencies in ~/config/index.js` + ) + }) + +// ------------------------------------ +// Utilities +// ------------------------------------ +config.utils_paths = (() => { + const resolve = path.resolve + + const base = (...args) => + resolve.apply(resolve, [config.path_base, ...args]) + + return { + base : base, + client : base.bind(null, config.dir_client), + dist : base.bind(null, config.dir_dist) + } +})() + +export default config diff --git a/config/_development.js b/config/_development.js new file mode 100755 index 0000000..381791c --- /dev/null +++ b/config/_development.js @@ -0,0 +1,20 @@ +/* eslint key-spacing:0 */ +import { argv } from 'yargs' + +export default (config) => { + const HMR_ENABLED = !!argv.hot + const overrides = { + compiler_enable_hmr : HMR_ENABLED + } + + // We use an explicit public path when the assets are served by webpack + // to fix this issue: + // http://stackoverflow.com/questions/34133808/webpack-ots-parsing-error-loading-fonts/34133809#34133809 + if (HMR_ENABLED) { + overrides.compiler_public_path = ( + `http://${config.server_host}:${config.server_port}/` + ) + } + + return overrides +} diff --git a/config/_production.js b/config/_production.js new file mode 100755 index 0000000..37f7119 --- /dev/null +++ b/config/_production.js @@ -0,0 +1,11 @@ +/* eslint key-spacing:0 */ +export default (config) => ({ + compiler_fail_on_warning : false, + compiler_hash_type : 'chunkhash', + compiler_source_maps : false, + compiler_stats : { + chunks : true, + chunkModules : true, + colors : true + } +}) diff --git a/config/index.js b/config/index.js new file mode 100755 index 0000000..1dc16eb --- /dev/null +++ b/config/index.js @@ -0,0 +1,17 @@ +import _debug from 'debug' + +const debug = _debug('app:config') +debug('Create configuration.') +import base from './_base' + +debug(`Apply environment overrides for NODE_ENV "${base.env}".`) +let overrides +try { + overrides = require(`./_${base.env}`)(base) +} catch (e) { + debug( + `No configuration overrides found for NODE_ENV "${base.env}"` + ) +} + +export default Object.assign({}, base, overrides) diff --git a/jsconfig.json b/jsconfig.json new file mode 100755 index 0000000..adc7fde --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "target": "ES6" + } +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..e87be79 --- /dev/null +++ b/package.json @@ -0,0 +1,100 @@ +{ + "name": "react-redux-starter-kit", + "version": "0.18.0", + "description": "Get started with React, Redux, and React-Router!", + "main": "index.js", + "scripts": { + "clean": "rm -rf dist", + "compile": "node -r dotenv/config --harmony bin/compile", + "lint": "eslint . ./", + "lint:fix": "npm run lint -- --fix", + "start": "npm run dev", + "dev": "node -r dotenv/config bin/server --hot", + "dev:nw": "npm run dev -- --nw", + "dev:no-debug": "npm run dev -- --no_debug", + "test": "node -r dotenv/config ./node_modules/karma/bin/karma start bin/karma.js", + "test:dev": "npm run test -- --watch", + "deploy": "npm run test && npm run compile" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/davezuko/react-redux-starter-kit.git" + }, + "author": "David Zukowski (http://zuko.me)", + "license": "MIT", + "dependencies": { + "cssnano": "^3.3.2", + "debug": "^2.2.0", + "dotenv": "^1.2.0", + "history": "1.13.1", + "react": "^0.14.0", + "react-dom": "^0.14.0", + "react-redux": "^4.0.0", + "react-router": "1.0.1", + "redux": "^3.0.0", + "redux-actions": "^0.9.0", + "redux-simple-router": "^1.0.0", + "redux-thunk": "^1.0.0", + "yargs": "^3.18.0" + }, + "devDependencies": { + "babel-core": "^6.3.17", + "babel-eslint": "^5.0.0-beta6", + "babel-loader": "^6.2.0", + "babel-plugin-add-module-exports": "^0.1.1", + "babel-plugin-react-transform": "^2.0.0-beta1", + "babel-plugin-transform-runtime": "^6.3.13", + "babel-preset-es2015": "^6.3.13", + "babel-preset-react": "^6.3.13", + "babel-preset-stage-0": "^6.3.13", + "babel-register": "^6.3.13", + "babel-runtime": "^6.3.19", + "chai": "^3.4.1", + "chai-as-promised": "^5.1.0", + "connect-history-api-fallback": "^1.1.0", + "css-loader": "^0.23.0", + "eslint": "^1.10.3", + "eslint-config-standard": "^4.4.0", + "eslint-config-standard-react": "^1.2.1", + "eslint-loader": "^1.1.1", + "eslint-plugin-babel": "^3.0.0", + "eslint-plugin-react": "^3.11.3", + "eslint-plugin-standard": "^1.3.1", + "express": "^4.13.3", + "extract-text-webpack-plugin": "^0.9.0", + "file-loader": "^0.8.4", + "fs-extra": "^0.26.3", + "html-webpack-plugin": "^1.6.1", + "isparta-loader": "^2.0.0", + "json-loader": "^0.5.4", + "karma": "^0.13.8", + "karma-chai": "^0.1.0", + "karma-chai-as-promised": "^0.1.2", + "karma-chai-sinon": "^0.1.5", + "karma-coverage": "^0.5.0", + "karma-mocha": "^0.2.0", + "karma-phantomjs-launcher": "^0.2.1", + "karma-spec-reporter": "0.0.23", + "karma-webpack": "^1.7.0", + "mocha": "^2.2.5", + "node-sass": "^3.3.3", + "phantomjs": "^1.9.17", + "phantomjs-polyfill": "0.0.1", + "postcss-loader": "^0.8.0", + "react-addons-test-utils": "^0.14.0", + "react-transform-catch-errors": "^1.0.0", + "react-transform-hmr": "^1.0.0", + "redbox-react": "^1.0.4", + "redux-devtools": "^3.0.0", + "redux-devtools-dock-monitor": "^1.0.1", + "redux-devtools-log-monitor": "^1.0.1", + "sass-loader": "^3.0.0", + "sinon": "^1.17.2", + "sinon-chai": "^2.8.0", + "style-loader": "^0.13.0", + "url-loader": "^0.5.6", + "webpack": "^1.12.9", + "webpack-dev-middleware": "^1.4.0", + "webpack-hot-middleware": "^2.6.0" + } +} diff --git a/server/app.js b/server/app.js new file mode 100755 index 0000000..878addf --- /dev/null +++ b/server/app.js @@ -0,0 +1,38 @@ +import express from 'express' +import historyApiFallback from 'connect-history-api-fallback' +import config from '../config' + +const app = express() +const debug = require('debug')('app:server') +const paths = config.utils_paths + +app.use(historyApiFallback({ + verbose: false +})) + +// Serve app with Webpack if HMR is enabled +if (config.compiler_enable_hmr) { + const webpack = require('webpack') + const webpackConfig = require('../build/webpack') + const compiler = webpack(webpackConfig) + + app.use(require('./middleware/webpack-dev')({ + compiler, + publicPath: webpackConfig.output.publicPath + })) + app.use(require('./middleware/webpack-hmr')({ compiler })) +} else { + debug( + 'Application is being run outside of development mode. This starter kit ' + + 'does not provide any production-specific server functionality. To learn ' + + 'more about deployment strategies, check out the "deployment" section ' + + 'in the README.' + ) + + // Serving ~/dist by default. Ideally these files should be served by + // the web server and not the app server, but this helps to demo the + // server in production. + app.use(express.static(paths.base(config.dir_dist))) +} + +export default app diff --git a/server/middleware/webpack-dev.js b/server/middleware/webpack-dev.js new file mode 100755 index 0000000..6eb7bbb --- /dev/null +++ b/server/middleware/webpack-dev.js @@ -0,0 +1,20 @@ +import WebpackDevMiddleware from 'webpack-dev-middleware' +import config from '../../config' + +const paths = config.utils_paths +const debug = require('debug')('app:server:webpack-dev') + +export default function ({ compiler, publicPath }) { + debug('Enable Webpack dev middleware.') + + /* eslint key-spacing:0 */ + return WebpackDevMiddleware(compiler, { + publicPath, + contentBase : paths.base(config.dir_client), + hot : true, + quiet : config.compiler_quiet, + noInfo : config.compiler_quiet, + lazy : false, + stats : config.compiler_stats + }) +} diff --git a/server/middleware/webpack-hmr.js b/server/middleware/webpack-hmr.js new file mode 100755 index 0000000..273b766 --- /dev/null +++ b/server/middleware/webpack-hmr.js @@ -0,0 +1,9 @@ +import WebpackHotMiddleware from 'webpack-hot-middleware' + +const debug = require('debug')('app:server:webpack-hmr') + +export default function ({ compiler }) { + debug('Enable Webpack Hot Module Replacement (HMR).') + + return WebpackHotMiddleware(compiler) +} diff --git a/src/app.js b/src/app.js new file mode 100755 index 0000000..2034449 --- /dev/null +++ b/src/app.js @@ -0,0 +1,18 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import createBrowserHistory from 'history/lib/createBrowserHistory' +import { syncReduxAndRouter } from 'redux-simple-router' +import routes from './routes' +import Root from './containers/Root' +import configureStore from './redux/configureStore' + +const history = createBrowserHistory() +const store = configureStore(window.__INITIAL_STATE__) + +syncReduxAndRouter(history, store, (state) => state.router) + +// Render the React application to the DOM +ReactDOM.render( + , + document.getElementById('root') +) diff --git a/src/assets/favicon.ico b/src/assets/favicon.ico new file mode 100755 index 0000000000000000000000000000000000000000..5c125de5d897c1ff5692a656485b3216123dcd89 GIT binary patch literal 24838 zcmeI4X^>UL6@VY56)S&I{`6Nu0RscWCdj@GJHx(%?6_-;yKy1n;EEf9f}pr1CW5HA zYt$%U#C=}?jWH&%G@BaHBxsWAoUb3}&6%Ei@4Ii_JRa1`RQ23*yU)_wJ$?H0>6gj0 z${d_I^w5kvTW3xYEc?FvyP3>p$!py@`@T`|dVepIsjbbvR}af%KKy7YuQ%SDC^zmNWPYR^7avI5P-@dKev}UZ^aDAOyci9Nn zwR4qEz~tSvrp|#ACvWzo9`3B;`}^{t18dxaH;?xT7#hmJiKAaI;|O=$yxzXNOHGw~ z^!5pE^SW`av%t_$22LFPsM^l%=PSp!3r`>9w%s+^ZQYnnTQ*Ggd9-1~kj_o$YdW@b ztCkJ(ZGYjusqV5L4{^)R9Gt@gzU1t|?xhE&c^q(|(R#oa*}Sj5c({A$mhrB8*Y@tc zr)K#C{KOp-eHl35ZWJ1&zkmI>9DL%!KJE@_!=W?aH;i?ZDb0O1HPFy6 zcV0Kf)eZ0BHmz9vowF7EA{z*aue9M)iJP&Zd)qYlfJ-c^sS1qY^?>s)!!Ta@x zr@Lz|80r)7<{QVk9Z$}5SDaVtz*Rc?oH5~Wcjoc^eA&EdJ^h@aZ-BvL{K2s_7Cvfr zFL&(R?D&(9OxsS%z_BzI9^Ai^AOF$PUpGk~oO(=OpMc3@Zh&KH1a9>G%%0rC)t@oQ z4d~M`hX+g^Wf8P>A&&qjq|tZe*44Laq7qVPK#QIc)s*Qj34P`NL`Q{xBI`SnR!RC? zlGdTvC%oVZ@0BgcH>}qc!uzul@{i@sH}L0|=eZBJ9qF!HHaw?`s0(_DJj(v`(memI z6jH}=BfGlSlRV4)ouv#h*65yRR>G zo;I#~BVK&l&{+H=_~Nq$d%bFLh7GE5pS&>Fr{RMe>)MM19~z6F1oQo_y>vtlpEZF# zIc82TpMc3z9;{Q)=zG5B#4+96yHCvYy8p4;C%6x`%y$2HccC9|#vGVD)**C0xX|R| z%h)}ze!Tnrvvb@RZ!GX@2lMEq`=`08b`9$%FnN@*zJLo2wD5?MbE&LN)Z>Kty*;m= zt{Cn0>Q3nk)`bR^{dVf!3ECg6Yz4YcskI>$XH*L8E)MsudhnkP0B>+M(XEcErHUBKi~ z1`fEP&WPhp{@Ew?cPlR(ma9iw8NbJWHqp=btCtM*FnP*@ZwwlJ&-Y|LEjgvJzUtPc zz5CrWNBRV8d0-bpWAl<=zM1PU8lJseDxBK^QuuCj2fg{&2#*IG5ezf1B(o%lU+OZx7So4D?yi2*h zFBkr5pG3AJs83uy!~C3mQZLp~ss7-N9oAY>t)!eC#s)CrPukK!(!G*)H?v(~JCoj# zfvgTxMV{4?zL1neQ;ITVBAdFDf`1yG$o{g7^1sR_n{RZ7tnXio?tM%240}(z9xFY0 zlz{^-G*RET;-`7`>e0b{{`!2kM)t7Si9ZqD$~wh*hyGC>z~qs@0T&u*;h}hiKGEga zHkJ;%7aNc^o_0(>Z{Gp069H;TwPTUnvvX0SJ+kGGZ0lFBWocl>kaa)AoiMta+x_-J-?#KHFnJ*! zwD1V?)4s#|?O)DlMBhVv4IgZs?d>b<6%xK3<{o91H?-%8?PK!_fm#3d>{{gQ z?*8`b{G6?bZKdO{_9IVlz{R$PcGjeL|3*|@upby()_Lf^eQ&XQe)CjsbJ3Uolrgt< zweld3GH|fZpn(=1@PencO_a_)v6tU?WV-w8wfXLbOGae0{<*C?Ead$6v+> z|EQKThJTmwXK!c6AOD+FgtDv7i<48{-OPce!KDVkzR+XKOcREPha(;$}iUb!*)f-Fb}Y4@r9z-_{OIg z`xn^T#ZtEPv_T$M*Sr+=Z{q#~8$|7Y{0!*2u${D*Jj%dfOrS~FzpH*_|55J!7kl4w z?LT!7T(!3!632pmZh?dh`n-z$_ts42pn6;c`}hx;TSYd0idsqal5&0uGV=UM{c9xQ z1KK6&TS+a^H|6B_hPo1W3 zh+Dun!`UkP%H3}*@IE18q{7&MH2f3?T6o}Jf+xI@fh=SyUOArw`*w1_-PUlHZTHc@ z--yqIxPtI}IjPRzLIZ8cPv4P=>?A&=E~~0)>&J#V;TwAR*6}`01iu~U$@prtzW6YS ze}E>gUX+0YuF}B+Uhw2x7a7Q+oOzMNFHTNN<)40Rzg#`pABKF18@l}5A>RL`?Ri;Z zC8ExD$)im1@R{N7(wIog8$Yn(6%q$yd9(zKe};OnH%;mWBs7)>ls~T3Wi6!Xqw6+dpJLVS1P| z9qV%io-nE*rYcPxiS31>U_>mbPTXxkC*!?*zefr#2vF|qr8{|4|u^7-pD|f z&OPc->UKu)=iHgIpysp;Lsbyj}GJWoBkufOA={CRTUjr%af zc5pUH9{pg?M5%+)oN`q9yBbBt@+3xHV)qGm8b)Cp-w7~CwEhtBUk0rbjrqM zTb|tQ3-5-pw^cul`T+X&s?O;?V(FD!(Q9Qg@(LTCNz{0-vBM^SX5lti3|GpxFn4;Ax6pGc~t)R!Bo${lYH(* z!F&5X*?S&}YoDCyzwv1H+XI(+rL`;RN9}iLxlfr-r&vGG8OQa@=>+a)+Ij)sd_{wu z1Am(+3-RFr4&N8N6+hqo19S#;SA1-hG>07p3}&*j4CR+rqdV)^6n; z_vFr!(a%-=#=kb{pYmNL@6|DWkw~%E2V2jYl*e1}c{e$fib?(O+hs}eoBLRo&9(;J}YV}0Mi;LZAe{U$(s= zT<-IaV$Z+q-P!~3{HxN>Kbw30jXzM&I(S<6Ksx^}HvU2Vntb!etSsm0>)j}Me^+L5{2yz--)?W`Q?az z!WLG4UNP}+#C+NKH+ZG-Q=E>IPp%LuKLx$$8NAOGr(#~P>!EA zDYlpXDR=xM?Xv5(-qp74Cw3LzBeASHSBY`OezkbOyjP!G%WSymju_C$VBl--z + + +) diff --git a/src/containers/DevToolsWindow.js b/src/containers/DevToolsWindow.js new file mode 100755 index 0000000..3a8dff3 --- /dev/null +++ b/src/containers/DevToolsWindow.js @@ -0,0 +1,7 @@ +import React from 'react' +import { createDevTools } from 'redux-devtools' +import LogMonitor from 'redux-devtools-log-monitor' + +export default createDevTools( + +) diff --git a/src/containers/Root.js b/src/containers/Root.js new file mode 100755 index 0000000..ccafc11 --- /dev/null +++ b/src/containers/Root.js @@ -0,0 +1,41 @@ +import React from 'react' +import { Provider } from 'react-redux' +import { Router } from 'react-router' + +export default class Root extends React.Component { + static propTypes = { + history: React.PropTypes.object.isRequired, + routes: React.PropTypes.element.isRequired, + store: React.PropTypes.object.isRequired + } + + get content () { + return ( + + {this.props.routes} + + ) + } + + get devTools () { + if (__DEBUG__) { + if (__DEBUG_NEW_WINDOW__) { + require('../redux/utils/createDevToolsWindow')(this.props.store) + } else { + const DevTools = require('containers/DevTools') + return + } + } + } + + render () { + return ( + +
+ {this.content} + {this.devTools} +
+
+ ) + } +} diff --git a/src/index.html b/src/index.html new file mode 100755 index 0000000..08a85d6 --- /dev/null +++ b/src/index.html @@ -0,0 +1,12 @@ + + + + React Redux Starter Kit + + + + + +
+ + diff --git a/src/layouts/CoreLayout.js b/src/layouts/CoreLayout.js new file mode 100755 index 0000000..fcecc0f --- /dev/null +++ b/src/layouts/CoreLayout.js @@ -0,0 +1,27 @@ +import React from 'react' +import 'styles/core.scss' + +// Note: Stateless/function components *will not* hot reload! +// react-transform *only* works on component classes. +// +// Since layouts rarely change, they are a good place to +// leverage React's new Statelesss Functions: +// https://facebook.github.io/react/docs/reusable-components.html#stateless-functions +// +// CoreLayout is a pure function of it's props, so we can +// define it with a plain javascript function... +function CoreLayout ({ children }) { + return ( +
+
+ {children} +
+
+ ) +} + +CoreLayout.propTypes = { + children: React.PropTypes.element +} + +export default CoreLayout diff --git a/src/redux/configureStore.js b/src/redux/configureStore.js new file mode 100755 index 0000000..2d25ffc --- /dev/null +++ b/src/redux/configureStore.js @@ -0,0 +1,34 @@ +import thunk from 'redux-thunk' +import rootReducer from './modules' +import { + applyMiddleware, + compose, + createStore +} from 'redux' + +export default function configureStore (initialState) { + let createStoreWithMiddleware + + const middleware = applyMiddleware(thunk) + + if (__DEBUG__) { + createStoreWithMiddleware = compose( + middleware, + require('containers/DevTools').instrument() + ) + } else { + createStoreWithMiddleware = compose(middleware) + } + + const store = createStoreWithMiddleware(createStore)( + rootReducer, initialState + ) + if (module.hot) { + module.hot.accept('./modules', () => { + const nextRootReducer = require('./modules') + + store.replaceReducer(nextRootReducer) + }) + } + return store +} diff --git a/src/redux/modules/counter.js b/src/redux/modules/counter.js new file mode 100755 index 0000000..afa2ba6 --- /dev/null +++ b/src/redux/modules/counter.js @@ -0,0 +1,37 @@ +import { createAction, handleActions } from 'redux-actions' + +// ------------------------------------ +// Constants +// ------------------------------------ +export const COUNTER_INCREMENT = 'COUNTER_INCREMENT' + +// ------------------------------------ +// Actions +// ------------------------------------ +export const increment = createAction(COUNTER_INCREMENT, (value = 1) => value) + +// This is a thunk, meaning it is a function that immediately +// returns a function for lazy evaluation. It is incredibly useful for +// creating async actions, especially when combined with redux-thunk! +// NOTE: This is solely for demonstration purposes. In a real application, +// you'd probably want to dispatch an action of COUNTER_DOUBLE and let the +// reducer take care of this logic. +export const doubleAsync = () => { + return (dispatch, getState) => { + setTimeout(() => { + dispatch(increment(getState().counter)) + }, 1000) + } +} + +export const actions = { + increment, + doubleAsync +} + +// ------------------------------------ +// Reducer +// ------------------------------------ +export default handleActions({ + [COUNTER_INCREMENT]: (state, { payload }) => state + payload +}, 1) diff --git a/src/redux/modules/index.js b/src/redux/modules/index.js new file mode 100755 index 0000000..7685f7e --- /dev/null +++ b/src/redux/modules/index.js @@ -0,0 +1,8 @@ +import { combineReducers } from 'redux' +import { routeReducer } from 'redux-simple-router' +import counter from './counter' + +export default combineReducers({ + counter, + router: routeReducer +}) diff --git a/src/redux/utils/createDevToolsWindow.js b/src/redux/utils/createDevToolsWindow.js new file mode 100755 index 0000000..b098a18 --- /dev/null +++ b/src/redux/utils/createDevToolsWindow.js @@ -0,0 +1,30 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import { Provider } from 'react-redux' +import DevTools from 'containers/DevToolsWindow' + +export default function createDevToolsWindow (store) { + const win = window.open( + null, + 'redux-devtools', // give it a name so it reuses the same window + `width=400,height=${window.outerHeight},menubar=no,location=no,resizable=yes,scrollbars=no,status=no` + ) + + // reload in case it's reusing the same window with the old content + win.location.reload() + + // wait a little bit for it to reload, then render + setTimeout(() => { + // Wait for the reload to prevent: + // "Uncaught Error: Invariant Violation: _registerComponent(...): Target container is not a DOM element." + win.document.write('
') + win.document.body.style.margin = '0' + + ReactDOM.render( + + + + , win.document.getElementById('react-devtools-root') + ) + }, 10) +} diff --git a/src/routes/index.js b/src/routes/index.js new file mode 100755 index 0000000..af31d6b --- /dev/null +++ b/src/routes/index.js @@ -0,0 +1,12 @@ +import React from 'react' +import { Route, IndexRoute } from 'react-router' +import CoreLayout from 'layouts/CoreLayout' +import HomeView from 'views/HomeView' +import AboutView from 'views/AboutView' + +export default ( + + + + +) diff --git a/src/styles/_base.scss b/src/styles/_base.scss new file mode 100755 index 0000000..73e45fd --- /dev/null +++ b/src/styles/_base.scss @@ -0,0 +1,12 @@ +/* +Application Settings Go Here +------------------------------------ +This file acts as a bundler for all variables/mixins/themes, so they +can easily be swapped out without `core.scss` ever having to know. + +For example: + +@import './variables/colors' +@import './variables/components' +@import './themes/default' +*/ diff --git a/src/styles/core.scss b/src/styles/core.scss new file mode 100755 index 0000000..3eaf33e --- /dev/null +++ b/src/styles/core.scss @@ -0,0 +1,21 @@ +@import 'base'; +@import 'vendor/normalize'; + +// Some best-practice CSS that's useful for most apps +// Just remove them if they're not what you want +html { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + height: 100%; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} diff --git a/src/styles/vendor/_normalize.scss b/src/styles/vendor/_normalize.scss new file mode 100755 index 0000000..458eea1 --- /dev/null +++ b/src/styles/vendor/_normalize.scss @@ -0,0 +1,427 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/src/views/AboutView.js b/src/views/AboutView.js new file mode 100755 index 0000000..0f5887c --- /dev/null +++ b/src/views/AboutView.js @@ -0,0 +1,16 @@ +import React from 'react' +import { Link } from 'react-router' + +export class AboutView extends React.Component { + render () { + return ( +
+

This is the about view!

+
+ Back To Home View +
+ ) + } +} + +export default AboutView diff --git a/src/views/HomeView.js b/src/views/HomeView.js new file mode 100755 index 0000000..113a36d --- /dev/null +++ b/src/views/HomeView.js @@ -0,0 +1,45 @@ +import React from 'react' +import { connect } from 'react-redux' +import { Link } from 'react-router' +import { actions as counterActions } from '../redux/modules/counter' +import styles from './HomeView.scss' + +// We define mapStateToProps where we'd normally use +// the @connect decorator so the data requirements are clear upfront, but then +// export the decorated component after the main class definition so +// the component can be tested w/ and w/o being connected. +// See: http://rackt.github.io/redux/docs/recipes/WritingTests.html +const mapStateToProps = (state) => ({ + counter: state.counter +}) +export class HomeView extends React.Component { + static propTypes = { + counter: React.PropTypes.number.isRequired, + doubleAsync: React.PropTypes.func.isRequired, + increment: React.PropTypes.func.isRequired + } + + render () { + return ( +
+

Welcome to the React Redux Starter Kit

+

+ Sample Counter:  + {this.props.counter} +

+ + +
+ Go To About View +
+ ) + } +} + +export default connect(mapStateToProps, counterActions)(HomeView) diff --git a/src/views/HomeView.scss b/src/views/HomeView.scss new file mode 100755 index 0000000..30f9f12 --- /dev/null +++ b/src/views/HomeView.scss @@ -0,0 +1,8 @@ +.counter { + font-weight: bold; +} + +.counter--green { + composes: counter; + color: rgb(25,200,25); +} diff --git a/tests/.eslintrc b/tests/.eslintrc new file mode 100755 index 0000000..a62af02 --- /dev/null +++ b/tests/.eslintrc @@ -0,0 +1,11 @@ +{ + "extends" : "../.eslintrc", + "env" : { + "mocha" : true + }, + "globals" : { + "expect" : false, + "should" : false, + "sinon" : false + } +} diff --git a/tests/framework.spec.js b/tests/framework.spec.js new file mode 100755 index 0000000..ce90fcf --- /dev/null +++ b/tests/framework.spec.js @@ -0,0 +1,21 @@ +import assert from 'assert' + +describe('(Framework) Karma Plugins', function () { + it('Should expose "expect" globally.', function () { + assert.ok(expect) + }) + + it('Should expose "should" globally.', function () { + assert.ok(should) + }) + + it('Should have chai-as-promised helpers.', function () { + const pass = new Promise(res => res('test')) + const fail = new Promise((res, rej) => rej()) + + return Promise.all([ + expect(pass).to.be.fulfilled, + expect(fail).to.not.be.fulfilled + ]) + }) +}) diff --git a/tests/layouts/CoreLayout.spec.js b/tests/layouts/CoreLayout.spec.js new file mode 100755 index 0000000..9090eb9 --- /dev/null +++ b/tests/layouts/CoreLayout.spec.js @@ -0,0 +1,33 @@ +import React from 'react' +import TestUtils from 'react-addons-test-utils' +import CoreLayout from 'layouts/CoreLayout' + +function shallowRender (component) { + const renderer = TestUtils.createRenderer() + + renderer.render(component) + return renderer.getRenderOutput() +} + +function shallowRenderWithProps (props = {}) { + return shallowRender() +} + +describe('(Layout) Core', function () { + let _component + let _props + let _child + + beforeEach(function () { + _child =

Child

+ _props = { + children: _child + } + + _component = shallowRenderWithProps(_props) + }) + + it('Should render as a
.', function () { + expect(_component.type).to.equal('div') + }) +}) diff --git a/tests/views/HomeView.spec.js b/tests/views/HomeView.spec.js new file mode 100755 index 0000000..ba605d4 --- /dev/null +++ b/tests/views/HomeView.spec.js @@ -0,0 +1,102 @@ +import React from 'react' +import TestUtils from 'react-addons-test-utils' +import { bindActionCreators } from 'redux' +import { HomeView } from 'views/HomeView' + +function shallowRender (component) { + const renderer = TestUtils.createRenderer() + + renderer.render(component) + return renderer.getRenderOutput() +} + +function renderWithProps (props = {}) { + return TestUtils.renderIntoDocument() +} + +function shallowRenderWithProps (props = {}) { + return shallowRender() +} + +describe('(View) Home', function () { + let _component, _rendered, _props, _spies + + beforeEach(function () { + _spies = {} + _props = { + counter: 0, + ...bindActionCreators({ + doubleAsync: (_spies.doubleAsync = sinon.spy()), + increment: (_spies.increment = sinon.spy()) + }, _spies.dispatch = sinon.spy()) + } + + _component = shallowRenderWithProps(_props) + _rendered = renderWithProps(_props) + }) + + it('Should render as a
.', function () { + expect(_component.type).to.equal('div') + }) + + it('Should include an

with welcome text.', function () { + const h1 = TestUtils.findRenderedDOMComponentWithTag(_rendered, 'h1') + + expect(h1).to.exist + expect(h1.textContent).to.match(/Welcome to the React Redux Starter Kit/) + }) + + it('Should render with an

that includes Sample Counter text.', function () { + const h2 = TestUtils.findRenderedDOMComponentWithTag(_rendered, 'h2') + + expect(h2).to.exist + expect(h2.textContent).to.match(/Sample Counter/) + }) + + it('Should render props.counter at the end of the sample counter

.', function () { + const h2 = TestUtils.findRenderedDOMComponentWithTag( + renderWithProps({ ..._props, counter: 5 }), 'h2' + ) + + expect(h2).to.exist + expect(h2.textContent).to.match(/5$/) + }) + + describe('An increment button...', function () { + let _btn + + beforeEach(() => { + _btn = TestUtils.scryRenderedDOMComponentsWithTag(_rendered, 'button') + .filter(a => /Increment/.test(a.textContent))[0] + }) + + it('should be rendered.', function () { + expect(_btn).to.exist + }) + + it('should dispatch an action when clicked.', function () { + _spies.dispatch.should.have.not.been.called + TestUtils.Simulate.click(_btn) + _spies.dispatch.should.have.been.called + }) + }) + + describe('A Double (Async) button...', function () { + let _btn + + beforeEach(() => { + _btn = TestUtils.scryRenderedDOMComponentsWithTag(_rendered, 'button') + .filter(a => /Double/.test(a.textContent))[0] + }) + + it('should be rendered.', function () { + expect(_btn).to.exist + }) + + it('should dispatch an action when clicked.', function () { + _spies.dispatch.should.have.not.been.called + TestUtils.Simulate.click(_btn) + _spies.dispatch.should.have.been.called + }) + }) +})