├── src
├── styles
│ ├── components
│ │ ├── Header.styl
│ │ ├── Nav.styl
│ │ └── ComponentName.styl
│ ├── base
│ │ ├── reset.styl
│ │ └── normalize.styl
│ ├── theme
│ │ ├── fonts.styl
│ │ └── variables.styl
│ └── app.styl
└── app
│ ├── config.js
│ ├── containers
│ ├── About.js
│ ├── App.js
│ └── Layout.js
│ ├── reducers
│ ├── index.js
│ └── counter.js
│ ├── routes.js
│ ├── test
│ ├── reducers
│ │ └── counter-test.js
│ ├── actions
│ │ └── CounterActions.test.js
│ └── components
│ │ └── Counter-test.js
│ ├── api
│ └── Counter.js
│ ├── client.js
│ ├── store
│ └── configureStore.js
│ ├── actions
│ └── Counter.js
│ ├── components
│ └── Counter.js
│ ├── utils
│ └── createDevToolsWindow.js
│ └── server.js
├── docs
└── header_universaljs.jpg
├── tasks
├── gulp-clean.js
├── gulp-lint-css.js
├── gulp-build-css.js
├── gulp-lint-js.js
├── cfg
│ ├── gulp-config.js
│ ├── build-webpack-config.js
│ └── dev-webpack-config.js
└── gulp-build-js.js
├── .jscsrc
├── webpack.config.js
├── .gitignore
├── .babelrc
├── .editorconfig
├── .stylintrc
├── gulpfile.babel.js
├── LICENSE
├── .eslintrc
├── README.md
└── package.json
/src/styles/components/Header.styl:
--------------------------------------------------------------------------------
1 | header
2 | padding 10
3 | text-align center
4 |
--------------------------------------------------------------------------------
/src/styles/components/Nav.styl:
--------------------------------------------------------------------------------
1 | nav ul
2 | list-style none
3 | text-align center
4 |
--------------------------------------------------------------------------------
/src/styles/base/reset.styl:
--------------------------------------------------------------------------------
1 | h1,
2 | h2,
3 | h3,
4 | h4,
5 | h5,
6 | h6
7 | margin 0
8 | padding 0
9 |
--------------------------------------------------------------------------------
/docs/header_universaljs.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/carlosazaustre/universal-js-boilerplate/HEAD/docs/header_universaljs.jpg
--------------------------------------------------------------------------------
/src/styles/components/ComponentName.styl:
--------------------------------------------------------------------------------
1 | .ComponentName
2 | background-color $white_base
3 | border 1px solid $gray_darker
4 | margin 2em
5 | padding 2em
6 |
7 | &-title
8 | color $gray_darker
9 |
--------------------------------------------------------------------------------
/src/app/config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | app: {
3 | name: 'Universal JS Boilerplate',
4 | port: process.env.PORT || 3000
5 | },
6 | api: 'http://localhost:3001'
7 | };
8 |
9 | export default config;
10 |
--------------------------------------------------------------------------------
/tasks/gulp-clean.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import del from 'del';
3 | import config from './cfg/gulp-config';
4 |
5 | export default () => {
6 | return del([
7 | config.output + 'app.js',
8 | config.output + 'app.css'
9 | ]);
10 | };
11 |
--------------------------------------------------------------------------------
/src/app/containers/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class About extends React.Component {
4 |
5 | render() {
6 | return (
7 |
10 | );
11 | }
12 |
13 | }
14 |
15 | export default About;
16 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "preset": "airbnb",
3 | "disallowSpacesInAnonymousFunctionExpression": null,
4 | "validateLineBreaks": "LF",
5 | "requireTrailingComma": false,
6 | "requireCapitalizedConstructors": false,
7 | "esnext": true,
8 | "excludeFiles": ["build/**", "node_modules/**"]
9 | }
10 |
--------------------------------------------------------------------------------
/src/styles/theme/fonts.styl:
--------------------------------------------------------------------------------
1 | @font-face
2 | font-family 'Lato'
3 | font-style normal
4 | font-weight 400
5 | src local('Lato Normal'),
6 | local('Lato-Normal'),
7 | url('https://themes.googleusercontent.com/static/fonts/lato/v6/9k-RPmcnxYEPm8CNFsH2gg.woff')
8 | format('woff')
9 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var devConfig = require('./tasks/cfg/dev-webpack-config');
2 | var buildConfig = require('./tasks/cfg/build-webpack-config');
3 |
4 | const TARGET = process.env.TARGET;
5 |
6 | if (TARGET === 'build') {
7 | module.exports = buildConfig;
8 | } else {
9 | module.exports = devConfig;
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Include your project-specific ignores in this file
2 | # Read about how to use .gitignore:
3 | # https://help.github.com/articles/ignoring-files
4 |
5 | node_modules
6 | npm-debug.log
7 |
8 | # hide builded files on repo
9 | build/public/*
10 | !build/.gitkeep
11 | !build/public/favicon.ico
12 |
13 | src/app_/*
14 | App_
15 |
--------------------------------------------------------------------------------
/tasks/gulp-lint-css.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import stylint from 'gulp-stylint';
3 | import config from './cfg/gulp-config';
4 |
5 | const configStylint = {
6 | config: '.stylintrc'
7 | };
8 |
9 | export default () => {
10 | return gulp
11 | .src(config.styles.input)
12 | .pipe(stylint(configStylint));
13 | };
14 |
--------------------------------------------------------------------------------
/src/app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import { reduxReactRouter, routerStateReducer, ReduxRouter } from 'redux-router';
3 | import counter from './counter';
4 | import routes from '../routes';
5 |
6 | const rootReducer = combineReducers({
7 | counter,
8 | router: routerStateReducer
9 | });
10 |
11 | export default rootReducer;
12 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "stage": 0,
3 | "env": {
4 | "development": {
5 | "plugins": [
6 | "react-transform"
7 | ],
8 | "extra": {
9 | "react-transform": {
10 | "transforms": [{
11 | "transform": "react-transform-hmr",
12 | "imports": ["react"],
13 | "locals": ["module"]
14 | }]
15 | }
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/reducers/counter.js:
--------------------------------------------------------------------------------
1 | import { SET_COUNTER, INCREMENT_COUNTER, DECREMENT_COUNTER } from '../actions/counter';
2 |
3 | export default function counter(state = 0, action) {
4 | switch (action.type) {
5 | case SET_COUNTER:
6 | return action.payload;
7 | case INCREMENT_COUNTER:
8 | return state + 1;
9 | case DECREMENT_COUNTER:
10 | return state - 1;
11 | default:
12 | return state;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, DefaultRoute } from 'react-router';
3 | import App from './containers/App';
4 | import About from './containers/About';
5 |
6 | const routes = `
7 |
8 |
9 |
10 |
11 |
12 | `;
13 |
14 | export default routes;
15 |
--------------------------------------------------------------------------------
/src/app/test/reducers/counter-test.js:
--------------------------------------------------------------------------------
1 | import counter from '../../reducers/counter';
2 | import { increment, decrement } from '../../actions/CounterActions';
3 | import { expect } from 'chai';
4 |
5 | describe('counter-reducer', () => {
6 |
7 | it('increments', () => {
8 | expect(counter(10, increment())).to.equal(11);
9 | });
10 |
11 | it('decrements', () => {
12 | expect(counter(10, decrement())).to.equal(9);
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/app/api/Counter.js:
--------------------------------------------------------------------------------
1 | function getRandomInt(min, max) {
2 | return Math.floor(Math.random() * (max - min)) + min;
3 | }
4 |
5 | export function fetchCounter(callback) {
6 | // Rather than immediately returning, we delay our code with a timeout to simulate asynchronous behavior
7 | setTimeout(() => {
8 | callback(getRandomInt(1, 100));
9 | }, 500);
10 |
11 | // In the case of a real world API call, you'll normally run into a Promise like this:
12 | // API.getUser().then(user => callback(user));
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/containers/App.js:
--------------------------------------------------------------------------------
1 | import { bindActionCreators } from 'redux';
2 | import { connect } from 'react-redux';
3 | import Counter from '../components/Counter';
4 | import * as CounterActions from '../actions/counter';
5 |
6 | function mapStateToProps(state) {
7 | return {
8 | counter: state.counter
9 | };
10 | }
11 |
12 | function mapDispatchToProps(dispatch) {
13 | return bindActionCreators(CounterActions, dispatch);
14 | }
15 |
16 | export default connect(mapStateToProps, mapDispatchToProps)(Counter);
17 |
--------------------------------------------------------------------------------
/tasks/gulp-build-css.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import stylus from 'gulp-stylus';
3 | import nib from 'nib';
4 | import minifyCSS from 'gulp-minify-css';
5 | import config from './cfg/gulp-config';
6 |
7 | const configStylus = {
8 | use: nib(),
9 | 'include css': true
10 | };
11 |
12 | export default () => {
13 | return gulp
14 | .src(config.styles.main)
15 | .pipe(stylus(configStylus))
16 | .pipe(minifyCSS())
17 | .pipe(gulp.dest(config.output));
18 | };
19 |
--------------------------------------------------------------------------------
/tasks/gulp-lint-js.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import eslint from 'gulp-eslint';
3 | import jscs from 'gulp-jscs';
4 | import config from './cfg/gulp-config';
5 |
6 | const configEslint = {
7 | configFile: '.eslintrc'
8 | };
9 |
10 | export default () => {
11 | return gulp
12 | .src(config.scripts.input)
13 | .pipe(jscs())
14 | .pipe(jscs.reporter())
15 | .pipe(jscs.reporter('console'))
16 | .pipe(eslint(configEslint))
17 | .pipe(eslint.format())
18 | .pipe(eslint.failOnError());
19 | };
20 |
--------------------------------------------------------------------------------
/tasks/cfg/gulp-config.js:
--------------------------------------------------------------------------------
1 | export default {
2 | scripts: {
3 | client: './src/app/client.js',
4 | input: [
5 | './src/app/**/*.js',
6 | './src/app/**/*.jsx'
7 | ],
8 | watch: [
9 | './src/app/server.js',
10 | './src/app/client.js',
11 | './src/app/**/*.jsx',
12 | './src/app/routes.js'
13 | ]
14 | },
15 | styles: {
16 | main: './src/styles/app.styl',
17 | input: [
18 | './src/styles/**/*.styl',
19 | '!./src/styles/base/normalize.styl'
20 | ],
21 | watch: [
22 | './src/styles/**/*.styl'
23 | ]
24 | },
25 | output: './build/public'
26 | };
27 |
--------------------------------------------------------------------------------
/tasks/gulp-build-js.js:
--------------------------------------------------------------------------------
1 | import gulp from 'gulp';
2 | import babelify from 'babelify';
3 | import browserify from 'browserify';
4 | import source from 'vinyl-source-stream';
5 | import buffer from 'vinyl-buffer';
6 | import config from './cfg/gulp-config';
7 |
8 | const configBrowserify = {
9 | entries: config.scripts.client,
10 | debug: true,
11 | extensions: ['.js', '.jsx'],
12 | transform: babelify
13 | };
14 |
15 | export default () => {
16 | return browserify(configBrowserify)
17 | .bundle()
18 | .pipe(source('app.js'))
19 | .pipe(buffer())
20 | .pipe(gulp.dest(config.output));
21 | };
22 |
--------------------------------------------------------------------------------
/src/app/client.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { Link } from 'react-router';
5 | import configureStore from './store/configureStore';
6 | import App from './containers/App';
7 |
8 | import routes from './routes';
9 |
10 | const initialState = window.__INITIAL_STATE__;
11 | const store = configureStore(initialState);
12 | const rootElement = document.getElementById('app');
13 |
14 | render(
15 |
16 |
17 |
23 | ,
24 | rootElement
25 | );
26 |
--------------------------------------------------------------------------------
/src/styles/theme/variables.styl:
--------------------------------------------------------------------------------
1 | // -- Colors -------------------------------------------------------------------
2 |
3 | $white_base = #fafafa
4 |
5 | $gray_base = #222
6 | $gray_darker = $gray_base + 15%
7 | $gray_dark = $gray_base + 25%
8 | $gray = $gray_base + 35%
9 | $gray_light = $gray_base + 50%
10 | $gray_lighter = $gray_base + 90%
11 |
12 | // -- Typography ---------------------------------------------------------------
13 |
14 | $font_family_base = 'Lato', 'Helvetica-Neue', sans-serif
15 |
16 | // -- Layout -------------------------------------------------------------------
17 |
18 | $max_content-width = 1000px
19 |
20 | $screen_mobile = 480px
21 | $screen_tablet = 768px
22 | $screen_desktop = 992px
23 | $screen_wide = 1200px
24 |
--------------------------------------------------------------------------------
/src/app/store/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from '../reducers';
4 | import { reduxReactRouter, routerStateReducer, ReduxRouter } from 'redux-router';
5 | import routes from './../routes';
6 |
7 | const createStoreWithMiddleware = applyMiddleware(
8 | thunk
9 | )(createStore);
10 |
11 | export default function configureStore(initialState) {
12 | const store = createStoreWithMiddleware(rootReducer, initialState);
13 |
14 | if (module.hot) {
15 | // Enable Webpack hot module replacement for reducers
16 | module.hot.accept('../reducers', () => {
17 | const nextRootReducer = require('../reducers');
18 | store.replaceReducer(nextRootReducer);
19 | });
20 | }
21 |
22 | return store;
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/containers/Layout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Layout extends React.Component {
4 |
5 | render() {
6 | return (
7 |
8 |
9 |
10 |
11 | { this.props.title }
12 |
13 |
14 |
15 |
16 |
17 |
18 | { this.props.children }
19 |
20 |
21 |
22 | );
23 | }
24 | }
25 |
26 | export default Layout;
27 |
--------------------------------------------------------------------------------
/src/styles/app.styl:
--------------------------------------------------------------------------------
1 | /*
2 | * Universal JS Boilerplate - Styles
3 | * Copyright (c) Carlos Azaustre, carlosazaustre.es
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | */
8 |
9 | // -- Base and Reset styles ----------------------------------------------------
10 |
11 | @import './base/normalize.styl'
12 | @import './base/reset.styl'
13 |
14 | // -- Theme Styles -------------------------------------------------------------
15 |
16 | @import './theme/fonts.styl'
17 | @import './theme/variables.styl'
18 |
19 | // -- Main styles --------------------------------------------------------------
20 |
21 | body
22 | background-color $gray_light
23 | font-family $font_family_base
24 | font-size 16px
25 |
26 | // -- Component Styles ---------------------------------------------------------
27 |
28 | @import './components/*'
29 |
--------------------------------------------------------------------------------
/src/app/actions/Counter.js:
--------------------------------------------------------------------------------
1 | export const SET_COUNTER = 'SET_COUNTER';
2 | export const INCREMENT_COUNTER = 'INCREMENT_COUNTER';
3 | export const DECREMENT_COUNTER = 'DECREMENT_COUNTER';
4 |
5 | export function set(value) {
6 | return {
7 | type: SET_COUNTER,
8 | payload: value
9 | };
10 | }
11 |
12 | export function increment() {
13 | return {
14 | type: INCREMENT_COUNTER
15 | };
16 | }
17 |
18 | export function decrement() {
19 | return {
20 | type: DECREMENT_COUNTER
21 | };
22 | }
23 |
24 | export function incrementIfOdd() {
25 | return (dispatch, getState) => {
26 | const { counter } = getState();
27 |
28 | if (counter % 2 === 0) {
29 | return;
30 | }
31 |
32 | dispatch(increment());
33 | };
34 | }
35 |
36 | export function incrementAsync(delay = 1000) {
37 | return dispatch => {
38 | setTimeout(() => {
39 | dispatch(increment());
40 | }, delay);
41 | };
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/components/Counter.js:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 |
3 | export default class Counter extends Component {
4 |
5 | static propTypes = {
6 | increment: PropTypes.func.isRequired,
7 | incrementIfOdd: PropTypes.func.isRequired,
8 | incrementAsync: PropTypes.func.isRequired,
9 | decrement: PropTypes.func.isRequired,
10 | counter: PropTypes.number.isRequired
11 | }
12 |
13 | render() {
14 | const { increment, incrementIfOdd, incrementAsync, decrement, counter } = this.props;
15 | return (
16 |
17 | Clicked: {counter} times
18 | {' '}
19 |
20 | {' '}
21 |
22 | {' '}
23 |
24 | {' '}
25 |
26 |
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/utils/createDevToolsWindow.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { DevTools, DebugPanel, LogMonitor } from 'redux-devtools/lib/react';
4 |
5 | export default function createDevToolsWindow(store) {
6 | // Window name.
7 | const name = 'Redux DevTools';
8 |
9 | // Give it a name so it reuses the same window.
10 | const win = window.open(
11 | null,
12 | name,
13 | 'menubar=no,location=no,resizable=yes,scrollbars=no,status=no'
14 | );
15 |
16 | // Reload in case it's reusing the same window with the old content.
17 | win.location.reload();
18 |
19 | // Set visible Window title.
20 | win.document.title = name;
21 |
22 | // Wait a little bit for it to reload, then render.
23 | setTimeout(() => render(
24 |
25 |
26 | ,
27 | win.document.body.appendChild(document.createElement('div'))
28 | ), 10);
29 | }
30 |
--------------------------------------------------------------------------------
/.stylintrc:
--------------------------------------------------------------------------------
1 | {
2 | "blocks": false,
3 | "brackets": "never",
4 | "colons": "never",
5 | "colors": "always",
6 | "commaSpace": "always",
7 | "commentSpace": "always",
8 | "cssLiteral": "never",
9 | "depthLimit": false,
10 | "duplicates": true,
11 | "efficient": "always",
12 | "extendPref": false,
13 | "globalDupe": false,
14 | "indentPref": false,
15 | "leadingZero": "never",
16 | "maxErrors": false,
17 | "maxWarnings": false,
18 | "mixed": false,
19 | "namingConvention": false,
20 | "namingConventionStrict": false,
21 | "none": "never",
22 | "noImportant": true,
23 | "parenSpace": false,
24 | "placeholders": "always",
25 | "prefixVarsWithDollar": "always",
26 | "quotePref": false,
27 | "reporter": "../core/reporter",
28 | "semicolons": "never",
29 | "sortOrder": "alphabetical",
30 | "stackedProperties": "never",
31 | "trailingWhitespace": "never",
32 | "universal": false,
33 | "valid": true,
34 | "zeroUnits": "never",
35 | "zIndexNormalize": false
36 | }
37 |
--------------------------------------------------------------------------------
/src/app/test/actions/CounterActions.test.js:
--------------------------------------------------------------------------------
1 | /* eslint no-unused-expressions: 0 */
2 | import { incrementIfOdd, increment, incrementAsync } from '../../actions/CounterActions';
3 | import { spy, useFakeTimers } from 'sinon';
4 | import chai, { expect } from 'chai';
5 | import sinonChai from 'sinon-chai';
6 |
7 | chai.use(sinonChai);
8 |
9 | describe('counter actions', () => {
10 |
11 | it('dispatch increment if odd', () => {
12 | let getState = () => {
13 | return { counter:3 };
14 | };
15 |
16 | let dispatch = spy();
17 | incrementIfOdd()(dispatch, getState);
18 | expect(dispatch).to.have.been.calledWith(increment());
19 | });
20 |
21 | it('increment async calls increment after one sec', () => {
22 | let dispatch = spy();
23 | let clock = useFakeTimers();
24 | incrementAsync()(dispatch);
25 | clock.tick(999);
26 | expect(dispatch).not.to.have.been.called;
27 | clock.tick(1);
28 | expect(dispatch).to.have.been.calledWith(increment());
29 | clock.restore();
30 |
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/tasks/cfg/build-webpack-config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 |
5 | var ROOT_PATH = path.join(__dirname, '..', '..');
6 |
7 | module.exports = {
8 | entry: [
9 | path.resolve(ROOT_PATH, 'src/app/client.js'),
10 | path.resolve(ROOT_PATH, 'src/styles/app.styl')
11 | ],
12 | output: {
13 | path: path.resolve(ROOT_PATH, '../build/public'),
14 | filename: 'app.js',
15 | publicPath: '/build/public'
16 | },
17 | plugins: [
18 | new webpack.optimize.OccurenceOrderPlugin(),
19 | new webpack.HotModuleReplacementPlugin(),
20 | new webpack.NoErrorsPlugin()
21 | ],
22 | module: {
23 | loaders: [{
24 | test: /\.js$|\.jsx$/,
25 | loader: 'babel'
26 | },
27 | {
28 | test: /\.styl$/,
29 | exclude: /node_modules/,
30 | loader: 'css-loader!stylus-loader?paths=node_modules/bootstrap-stylus/stylus/'
31 | },
32 | {
33 | test: /\.json$/,
34 | loader: 'json-loader'
35 | }]
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/tasks/cfg/dev-webpack-config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 | var ROOT_PATH = path.join(__dirname, '..', '..');
5 |
6 | module.exports = {
7 | devtool: 'inline-source-map',
8 | entry: [
9 | 'webpack-hot-middleware/client',
10 | path.resolve(ROOT_PATH, 'src/app/client.js'),
11 | path.resolve(ROOT_PATH, 'src/styles/app.styl')
12 | ],
13 | output: {
14 | path: path.resolve(ROOT_PATH, '../build/public'),
15 | filename: 'app.js',
16 | publicPath: '/build/public/'
17 | },
18 | plugins: [
19 | new webpack.optimize.OccurenceOrderPlugin(),
20 | new webpack.HotModuleReplacementPlugin(),
21 | new webpack.NoErrorsPlugin()
22 | ],
23 | module: {
24 | loaders: [{
25 | test: /\.js$|\.jsx$/,
26 | loader: 'babel'
27 | },
28 | {
29 | test: /\.styl$/,
30 | exclude: /node_modules/,
31 | loader: 'css-loader!stylus-loader?paths=node_modules/bootstrap-stylus/stylus/'
32 | },
33 | {
34 | test: /\.json$/,
35 | loader: 'json-loader'
36 | }]
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/gulpfile.babel.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Universal JS Boilerplate
3 | * Copyright (c) Carlos Azaustre, carlosazaustre.es
4 | *
5 | * This source code is licensed under the MIT license found in the
6 | * LICENSE file in the root directory of this source tree.
7 | */
8 |
9 | import gulp from 'gulp';
10 | import linterJS from './tasks/gulp-lint-js';
11 | import linterCSS from './tasks/gulp-lint-css';
12 | import buildJS from './tasks/gulp-build-js';
13 | import buildCSS from './tasks/gulp-build-css';
14 |
15 | import clean from './tasks/gulp-clean';
16 |
17 | import config from './tasks/cfg/gulp-config';
18 |
19 | gulp.task('eslint', linterJS);
20 | gulp.task('stylint', linterCSS);
21 |
22 | gulp.task('build:js', ['eslint'], buildJS);
23 | gulp.task('build:css', ['stylint'], buildCSS);
24 |
25 | gulp.task('clean', clean);
26 |
27 | gulp.task('default', ['clean', 'build', 'watch']);
28 | gulp.task('build', ['clean', 'build:js', 'build:css']);
29 |
30 | gulp.task('watch', () => {
31 | gulp.watch(config.scripts.watch, ['build:js']);
32 | gulp.watch(config.styles.watch, ['build:css']);
33 | });
34 |
--------------------------------------------------------------------------------
/src/app/test/components/Counter-test.js:
--------------------------------------------------------------------------------
1 | /* eslint no-unused-expressions: 0 */
2 | import testdom from 'testdom';
3 | testdom('');
4 |
5 | import Counter from '../../components/Counter';
6 | import React from 'react';
7 | import ReactAddons from 'react/addons';
8 | import sinon from 'sinon';
9 | import chai, { expect } from 'chai';
10 | import sinonChai from 'sinon-chai';
11 | chai.use(sinonChai);
12 |
13 | const TestUtils = React.addons.TestUtils;
14 |
15 | describe('Counter component', () => {
16 |
17 | let increment = sinon.spy();
18 | let incrementIfOdd = sinon.spy();
19 | let decrement = sinon.spy();
20 | let counterProps = {
21 | increment: increment,
22 | incrementIfOdd: incrementIfOdd,
23 | decrement: decrement,
24 | counter: 10
25 | };
26 | let counter = TestUtils.renderIntoDocument();
27 |
28 | it('increment click', () => {
29 | let buttons = TestUtils.scryRenderedDOMComponentsWithTag(counter, 'button');
30 | TestUtils.Simulate.click(buttons[0]);
31 | expect(increment).to.have.been.called;
32 | });
33 |
34 | });
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Carlos Azaustre
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "plugins": [
5 | "react"
6 | ],
7 | "env": {
8 | "browser": true,
9 | "node": true,
10 | "es6": true,
11 | "mocha": true
12 | },
13 | "globals": {
14 | "__DEV__": true,
15 | "__SERVER__": true,
16 | "describe": true,
17 | "it": true
18 | },
19 | "ecmaFeatures": {
20 | "jsx": true,
21 | "arrowFunctions": true,
22 | "blockBindings": true,
23 | "modules": true,
24 | "classes": true
25 | },
26 | "rules": {
27 | "valid-jsdoc": 2,
28 | "indent": [2, 2],
29 | "quotes": [2, "single", "avoid-escape"],
30 | "no-multi-spaces": 0,
31 | "key-spacing": 0,
32 | "strict": [2, "never"],
33 | "comma-dangle": [2, "never"],
34 | "no-shadow": [0],
35 | "no-use-before-define": [2, "nofunc"],
36 | "new-cap": [0],
37 | "object-curly-spacing": [2, "always"],
38 | "no-undef": 2,
39 | "prefer-const": 0,
40 | "no-underscore-dangle": 0,
41 | "func-names": 0,
42 | "no-else-return": 0,
43 | "no-console": 0,
44 | "no-throw-literal": 0,
45 | "id-length": 0,
46 | "jsx-quotes": [2, "prefer-single"],
47 |
48 | "no-unused-vars": 0,
49 | "padded-blocks": 0,
50 | "block-scoped-var": 0,
51 |
52 | "react/jsx-quotes": 0,
53 | "react/display-name": 0,
54 | "react/jsx-boolean-value": 1,
55 | "react/jsx-no-undef": 1,
56 | "react/jsx-sort-prop-types": 0,
57 | "react/jsx-sort-props": 0,
58 | "react/jsx-uses-react": 1,
59 | "react/jsx-uses-vars": 1,
60 | "react/no-did-mount-set-state": 1,
61 | "react/no-did-update-set-state": 1,
62 | "react/no-multi-comp": 1,
63 | "react/no-unknown-property": 1,
64 | "react/prop-types": 1,
65 | "react/react-in-jsx-scope": 1,
66 | "react/self-closing-comp": 1,
67 | "react/sort-comp": 1,
68 | "react/wrap-multilines": 1
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UniversalJS Boilerplate
2 |
3 | 
4 |
5 | A boilerplate to quick start JavaScript Isomorphic Web Applications
6 |
7 | ## Technology Stack
8 |
9 | * **[ES2015 / ECMAScript 6th Edition](https://babeljs.io/docs/learn-es2015/)** - the upcoming version of the ECMAScript standard.
10 | * **[Babel](https://babeljs.io/)** - Next generation JavaScript, today.
11 | * **[Browserify](http://browserify.org/)** - Browserify lets you require('modules') in the browser by bundling up all of your dependencies.
12 | * **[React](http://facebook.github.io/react/)** - A JavaScript library for building user interfaces.
13 | * **[React Router](http://rackt.github.io/react-router/)** - Complete routing solution designed specifically for React.js.
14 | * **[React Engine](https://github.com/paypal/react-engine)** - A composite render engine for isomorphic express apps to render both plain react views and react-router views.
15 | * **[Stylus](https://learnboost.github.io/stylus/)** - Expressive, dynamic, robust CSS
16 | * **[NodeJS](https://nodejs.org/)** - Platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications.
17 | * **[Express](http://expressjs.com/)** - Fast, unopinionated, minimalist web framework for Node.js.
18 | * **[Gulp](http://gulpjs.com/)** - Automate and enhance your workflow.
19 |
20 | ## Requirements and Install
21 |
22 | Node.js v0.10.33 installed at least.
23 |
24 | ```
25 | $ npm install -g gulp@3.9.0
26 | $ npm install -g babel browserify nodemon
27 | $ npm install
28 | $ npm run build
29 | $ npm start
30 | ```
31 |
32 | And open your browser on `http://localhost:3000`
33 |
34 | ## Folder structure
35 |
36 | ```
37 | .
38 | ├── /build/ # Transpiled and minifiqued output files.
39 | ├── /docs/ # Documentation about your project.
40 | ├── /node_modules/ # Node Modules and 3rd-party libraries.
41 | ├── /src/ # Source code of the web application.
42 | │ ├── /app/ # Isomorphic App source code.
43 | │ │ ├── /components/ # React components.
44 | │ │ ├── /containers/ # Redux containers.
45 | │ │ └── routes.js # Shared routes between Client-Server.
46 | │ │ └── client.js #
47 | │ │ └── server.js #
48 | │ │ └── config.js # App config (URLs, names, etc...).
49 | │ └── /styles/ # Stylesheets coding with Stylus Preprocessor.
50 | ├── /tasks/ # Gulp tasks for build the client part
51 | ├── gulpfile.babel.js # Config file for automated Builders.
52 | ├── package.json # App manifest and list of libraries installed.
53 | ├── .editorconfig # define and maintain consistent coding styles.
54 | ├── .eslintrc # ECMAScript6 and React Code linter.
55 | ├── .babelrc # babel ES6 rules.
56 | ├── .jscsrc # JavaScript Code styles.
57 | └── .stylintc # Stylus CSS Code style.
58 | ```
59 |
60 | ## License
61 |
62 | Open Source. [MIT](LICENSE) © Carlos Azaustre
63 |
--------------------------------------------------------------------------------
/src/app/server.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import Express from 'express';
3 | import qs from 'qs';
4 | import engine from 'react-engine';
5 | import favicon from 'serve-favicon';
6 | import config from './config';
7 |
8 | import webpack from 'webpack';
9 | import webpackDevMiddleware from 'webpack-dev-middleware';
10 | import webpackHotMiddleware from 'webpack-hot-middleware';
11 | import webpackConfig from '../../webpack.config';
12 |
13 | import React from 'react';
14 | import { renderToString } from 'react-dom/server';
15 | import { Provider } from 'react-redux';
16 |
17 | import configureStore from './store/configureStore';
18 | import App from './containers/App';
19 | import { fetchCounter } from './api/counter';
20 |
21 | const app = new Express();
22 | const port = config.app.port;
23 |
24 | // -- Setup React Views engine -------------------------------------------------
25 |
26 | app.engine('.jsx', engine.server.create({
27 | reactRoutes: path.join(__dirname, '..', 'app', 'routes.js')
28 | }));
29 | app.set('views', path.join(__dirname, '..', 'app', 'components'));
30 | app.set('view engine', 'jsx');
31 | app.set('view', engine.expressView);
32 |
33 | const publicPath = path.join(__dirname, '..', '..', 'build', 'public');
34 | const faviconPath = path.join(__dirname, '..', '..', 'build', 'public', 'favicon.ico');
35 |
36 | // Use this middleware to set up hot module reloading via webpack.
37 | const compiler = webpack(webpackConfig);
38 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath }));
39 | app.use(webpackHotMiddleware(compiler));
40 |
41 | app.use(Express.static(publicPath));
42 | app.use(favicon(faviconPath));
43 |
44 | app.get('*', (req, res) => {
45 |
46 | // Query our mock API asynchronously
47 | fetchCounter(apiResult => {
48 | // Read the counter from the request, if provided
49 | const params = qs.parse(req.query);
50 | const counter = parseInt(params.counter, 10) || apiResult || 0;
51 |
52 | // Compile an initial state
53 | const initialState = { counter };
54 |
55 | // Create a new Redux store instance
56 | const store = configureStore(initialState);
57 |
58 | // Render the component to a string
59 | const html = renderToString(
60 |
61 |
62 |
63 | );
64 |
65 | // Grab the initial state from our Redux store
66 | const firstState = store.getState();
67 |
68 | // Send the rendered page back to the client
69 | res.send(renderFullPage(html, firstState));
70 | });
71 | });
72 |
73 | function renderFullPage(html, initialState) {
74 | return `
75 |
76 |
77 |
78 | Redux Universal Example
79 |
80 |
81 | ${html}
82 |
85 |
86 |
87 |
88 | `;
89 | }
90 |
91 | app.listen(port, () => {
92 | console.log(`WebServer listening on port ${port}`);
93 | });
94 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "engines": {
3 | "node": ">=4.1 <5",
4 | "npm": ">=3.1 <4"
5 | },
6 | "name": "universal-js-boilerplate",
7 | "version": "0.0.1",
8 | "description": "A boilerplate to start universal (isomorphic) web applications",
9 | "main": "./src/app/server.js",
10 | "scripts": {
11 | "start": "nodemon --exec babel-node -- ./src/app/server.js",
12 | "build": "TARGET=build webpack",
13 | "dev": "TARGET=dev webpack-dev-server --progress --colors --hot --inline --history-api-fallback --content-base www",
14 | "deploy": "TARGET=build webpack -p -d"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/carlosazaustre/universal-js-boilerplate"
19 | },
20 | "keywords": [
21 | "boilerplate",
22 | "web development",
23 | "frontend",
24 | "javascript",
25 | "es6",
26 | "ecmascript 6",
27 | "react",
28 | "isomorphic"
29 | ],
30 | "author": "Carlos Azaustre (https://carlosazaustre.es/blog)",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/carlosazaustre/universal-js-boilerplate/issues"
34 | },
35 | "homepage": "https://github.com/carlosazaustre/universal-js-boilerplate",
36 | "dependencies": {
37 | "babel": "^5.8.21",
38 | "express": "^4.13.3",
39 | "qs": "^4.0.0",
40 | "react": "^0.14.0",
41 | "react-dom": "^0.14.0",
42 | "react-engine": "^1.7.0",
43 | "react-redux": "^4.0.0",
44 | "react-router": "^1.0.0-rc3",
45 | "redux": "^3.0.2",
46 | "redux-thunk": "^0.1.0",
47 | "serve-favicon": "^2.3.0",
48 | "serve-static": "^1.10.0"
49 | },
50 | "devDependencies": {
51 | "babel": "^5.6.14",
52 | "babel-core": "^5.8.3",
53 | "babel-eslint": "^4.1.3",
54 | "babel-loader": "^5.3.2",
55 | "babel-plugin-react-transform": "^1.1.0",
56 | "babel-runtime": "^5.6.15",
57 | "babelify": "^6.1.2",
58 | "browserify": "^10.2.4",
59 | "chai": "^3.3.0",
60 | "css-loader": "^0.15.5",
61 | "del": "^2.0.2",
62 | "eslint": "^1.6.0",
63 | "eslint-config-airbnb": "^0.1.0",
64 | "eslint-loader": "^1.0.0",
65 | "eslint-plugin-react": "^2.7.1",
66 | "esprima-fb": "^15001.1.0-dev-harmony-fb",
67 | "expect": "^1.8.0",
68 | "extract-text-webpack-plugin": "^0.8.2",
69 | "gulp": "^3.9.0",
70 | "gulp-eslint": "^1.0.0",
71 | "gulp-jscs": "^3.0.1",
72 | "gulp-minify-css": "^1.2.0",
73 | "gulp-stylint": "^3.0.0",
74 | "gulp-stylus": "^2.1.0",
75 | "jsdom": "^5.6.1",
76 | "json-loader": "^0.5.2",
77 | "mocha": "^2.2.5",
78 | "mocha-jsdom": "^1.0.0",
79 | "nib": "^1.1.0",
80 | "node-libs-browser": "^0.5.2",
81 | "nodemon": "^1.3.7",
82 | "raw-loader": "^0.5.1",
83 | "react-hot-loader": "^1.2.8",
84 | "react-transform-hmr": "^1.0.0",
85 | "redux": "^3.0.2",
86 | "redux-devtools": "^2.1.5",
87 | "redux-logger": "^2.0.4",
88 | "redux-promise": "^0.5.0",
89 | "redux-router": "^1.0.0-beta3",
90 | "sinon": "^1.17.1",
91 | "sinon-chai": "^2.8.0",
92 | "style-loader": "^0.12.3",
93 | "stylus-loader": "^1.4.0",
94 | "todomvc-app-css": "^2.0.1",
95 | "vinyl-buffer": "^1.0.0",
96 | "vinyl-source-stream": "^1.1.0",
97 | "webpack": "^1.10.5",
98 | "webpack-dev-middleware": "^1.2.0",
99 | "webpack-dev-server": "^1.10.1",
100 | "webpack-hot-middleware": "^2.2.0",
101 | "webpack-merge": "^0.1.2",
102 | "webpack-module-hot-accept": "1.0.3"
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/styles/base/normalize.styl:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS and IE text size adjust after device orientation change,
6 | * without disabling user zoom.
7 | */
8 |
9 | html
10 | font-family: sans-serif // 1
11 | -ms-text-size-adjust: 100% // 2
12 | -webkit-text-size-adjust: 100% // 2
13 |
14 | /**
15 | * Remove default margin.
16 | */
17 |
18 | body
19 | margin 0
20 |
21 | /* HTML5 display definitions
22 | ========================================================================== */
23 |
24 | /**
25 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
26 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
27 | * and Firefox.
28 | * Correct `block` display not defined for `main` in IE 11.
29 | */
30 |
31 | article,
32 | aside,
33 | details,
34 | figcaption,
35 | figure,
36 | footer,
37 | header,
38 | hgroup,
39 | main,
40 | menu,
41 | nav,
42 | section,
43 | summary
44 | display block
45 |
46 | /**
47 | * 1. Correct `inline-block` display not defined in IE 8/9.
48 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
49 | */
50 |
51 | audio,
52 | canvas,
53 | progress,
54 | video
55 | display inline-block // 1
56 | vertical-align baseline // 2
57 |
58 | /**
59 | * Prevent modern browsers from displaying `audio` without controls.
60 | * Remove excess height in iOS 5 devices.
61 | */
62 |
63 | audio:not([controls])
64 | display none
65 | height 0
66 |
67 | /**
68 | * Address `[hidden]` styling not present in IE 8/9/10.
69 | * Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
70 | */
71 |
72 | [hidden],
73 | template
74 | display none
75 |
76 | /* Links
77 | ========================================================================== */
78 |
79 | /**
80 | * 1. Remove the gray background color from active links in IE 10.
81 | * 2. Improve readability of focused elements when they are also in
82 | * an active/hover state.
83 | */
84 |
85 | a
86 | background-color transparent // 1
87 | &:active,
88 | &:hover
89 | outline 0 // 2
90 |
91 | /* Text-level semantics
92 | ========================================================================== */
93 |
94 | /**
95 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
96 | */
97 |
98 | abbr[title]
99 | border-bottom 1px dotted
100 |
101 | /**
102 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
103 | */
104 |
105 | b,
106 | strong
107 | font-weight bold
108 |
109 | /**
110 | * Address styling not present in Safari and Chrome.
111 | */
112 |
113 | dfn
114 | font-style italic
115 |
116 | /**
117 | * Address variable `h1` font-size and margin within `section` and `article`
118 | * contexts in Firefox 4+, Safari, and Chrome.
119 | */
120 |
121 | h1
122 | font-size 2em
123 | margin 0.67em 0
124 |
125 | /**
126 | * Address styling not present in IE 8/9.
127 | */
128 |
129 | mark
130 | background #ff0
131 | color #000
132 |
133 | /**
134 | * Address inconsistent and variable font size in all browsers.
135 | */
136 |
137 | small
138 | font-size 80%
139 |
140 | /**
141 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
142 | */
143 |
144 | sub,
145 | sup
146 | font-size 75%
147 | line-height 0
148 | position relative
149 | vertical-align baseline
150 |
151 | sup
152 | top -0.5em
153 |
154 | sub
155 | bottom -0.25em
156 |
157 | /* Embedded content
158 | ========================================================================== */
159 |
160 | /**
161 | * Remove border when inside `a` element in IE 8/9/10.
162 | */
163 |
164 | img
165 | border 0
166 |
167 | /**
168 | * Correct overflow not hidden in IE 9/10/11.
169 | */
170 |
171 | svg:not(:root)
172 | overflow hidden
173 |
174 | /* Grouping content
175 | ========================================================================== */
176 |
177 | /**
178 | * Address margin not present in IE 8/9 and Safari.
179 | */
180 |
181 | figure
182 | margin 1em 40px
183 |
184 | /**
185 | * Address differences between Firefox and other browsers.
186 | */
187 |
188 | hr
189 | box-sizing content-box
190 | height 0
191 |
192 | /**
193 | * Contain overflow in all browsers.
194 | */
195 |
196 | pre
197 | overflow auto
198 |
199 | /**
200 | * Address odd `em`-unit font size rendering in all browsers.
201 | */
202 |
203 | code,
204 | kbd,
205 | pre,
206 | samp
207 | font-family monospace, monospace
208 | font-size 1em
209 |
210 | /* Forms
211 | ========================================================================== */
212 |
213 | /**
214 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
215 | * styling of `select`, unless a `border` property is set.
216 | */
217 |
218 | /**
219 | * 1. Correct color not being inherited.
220 | * Known issue: affects color of disabled elements.
221 | * 2. Correct font properties not being inherited.
222 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
223 | */
224 |
225 | button,
226 | input,
227 | optgroup,
228 | select,
229 | textarea
230 | color inherit // 1
231 | font inherit // 2
232 | margin 0 // 3
233 |
234 | /**
235 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
236 | */
237 |
238 | button
239 | overflow visible
240 |
241 | /**
242 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
243 | * All other form control elements do not inherit `text-transform` values.
244 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
245 | * Correct `select` style inheritance in Firefox.
246 | */
247 |
248 | button,
249 | select
250 | text-transform none
251 |
252 | /**
253 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
254 | * and `video` controls.
255 | * 2. Correct inability to style clickable `input` types in iOS.
256 | * 3. Improve usability and consistency of cursor style between image-type
257 | * `input` and others.
258 | */
259 |
260 | button,
261 | html input[type="button"], // 1
262 | input[type="reset"],
263 | input[type="submit"]
264 | -webkit-appearance button // 2
265 | cursor pointer // 3
266 |
267 | /**
268 | * Re-set default cursor for disabled elements.
269 | */
270 |
271 | button[disabled],
272 | html input[disabled]
273 | cursor default
274 |
275 | /**
276 | * Remove inner padding and border in Firefox 4+.
277 | */
278 |
279 | button::-moz-focus-inner,
280 | input::-moz-focus-inner
281 | border 0
282 | padding 0
283 |
284 | /**
285 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
286 | * the UA stylesheet.
287 | */
288 |
289 | input
290 | line-height normal
291 |
292 | /**
293 | * It's recommended that you don't attempt to style these elements.
294 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
295 | *
296 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
297 | * 2. Remove excess padding in IE 8/9/10.
298 | */
299 |
300 | input[type="checkbox"],
301 | input[type="radio"]
302 | box-sizing border-box // 1
303 | padding 0 // 2
304 |
305 | /**
306 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
307 | * `font-size` values of the `input`, it causes the cursor style of the
308 | * decrement button to change from `default` to `text`.
309 | */
310 |
311 | input[type="number"]::-webkit-inner-spin-button,
312 | input[type="number"]::-webkit-outer-spin-button
313 | height auto
314 |
315 | /**
316 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
317 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
318 | */
319 |
320 | input[type="search"]
321 | -webkit-appearance textfield // 1
322 | box-sizing content-box // 2
323 |
324 | /**
325 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
326 | * Safari (but not Chrome) clips the cancel button when the search input has
327 | * padding (and `textfield` appearance).
328 | */
329 |
330 | input[type="search"]::-webkit-search-cancel-button,
331 | input[type="search"]::-webkit-search-decoration
332 | -webkit-appearance none
333 |
334 | /**
335 | * Define consistent border, margin, and padding.
336 | */
337 |
338 | fieldset
339 | border 1px solid #c0c0c0
340 | margin 0 2px
341 | padding 0.35em 0.625em 0.75em
342 |
343 | /**
344 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
345 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
346 | */
347 |
348 | legend
349 | border 0 // 1
350 | padding 0 // 2
351 |
352 | /**
353 | * Remove default vertical scrollbar in IE 8/9/10/11.
354 | */
355 |
356 | textarea
357 | overflow auto
358 |
359 | /**
360 | * Don't inherit the `font-weight` (applied by a rule above).
361 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
362 | */
363 |
364 | optgroup
365 | font-weight bold
366 |
367 | /* Tables
368 | ========================================================================== */
369 |
370 | /**
371 | * Remove most spacing between table cells.
372 | */
373 |
374 | table
375 | border-collapse collapse
376 | border-spacing 0
377 |
378 | td,
379 | th
380 | padding 0
381 |
--------------------------------------------------------------------------------