├── .env.sample ├── .eslintignore ├── .gitignore ├── src ├── stylesheets │ ├── top-wrappers.css │ ├── themes │ │ ├── green.scss │ │ └── default.scss │ ├── color.scss │ └── reset.css ├── components │ ├── app │ │ ├── index.scss │ │ ├── test │ │ │ └── app.test.js │ │ └── index.js │ ├── font-loader │ │ └── index.js │ ├── home-screen │ │ ├── test │ │ │ └── home-screen.test.js │ │ ├── index.js │ │ └── index.scss │ └── counter │ │ ├── index.scss │ │ ├── test │ │ └── counter.test.js │ │ └── index.js ├── routes.js ├── index.tpl.html └── boot.js ├── .editorconfig ├── test ├── configuration.js └── utils.js ├── lib └── read-theme.js ├── bin ├── publish-gh-page └── server.js ├── package.json ├── webpack.development.babel.js ├── webpack.production.babel.js ├── README.md └── .eslintrc.json /.env.sample: -------------------------------------------------------------------------------- 1 | FOO=bar 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .DS_Store 4 | *.log 5 | .sass-cache 6 | *.map 7 | .env 8 | .eslintcache 9 | -------------------------------------------------------------------------------- /src/stylesheets/top-wrappers.css: -------------------------------------------------------------------------------- 1 | /* Top wrapper styles */ 2 | /* ================== */ 3 | 4 | html, 5 | body, 6 | .main-wrapper { 7 | height: 100%; 8 | } 9 | -------------------------------------------------------------------------------- /src/stylesheets/themes/green.scss: -------------------------------------------------------------------------------- 1 | // Green theme 2 | // =========== 3 | 4 | $color-palette: ( 5 | 'first': #008542, 6 | 'second': #ffffff 7 | ); 8 | 9 | $step-size: 10% !default; 10 | -------------------------------------------------------------------------------- /src/stylesheets/themes/default.scss: -------------------------------------------------------------------------------- 1 | // Default theme 2 | // ============= 3 | 4 | $color-palette: ( 5 | 'first': #d92500, 6 | 'second': #ffffff 7 | ); 8 | 9 | $step-size: 10% !default; 10 | -------------------------------------------------------------------------------- /src/components/app/index.scss: -------------------------------------------------------------------------------- 1 | // Root app wrapper styles 2 | // ======================= 3 | 4 | .App { 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | height: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/font-loader/index.js: -------------------------------------------------------------------------------- 1 | // Font loader 2 | // =========== 3 | 4 | import WebFont from 'webfontloader' 5 | 6 | export default function loadFonts() { 7 | WebFont.load({ 8 | google: { families: ['Fira Mono'] } 9 | }) 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /test/configuration.js: -------------------------------------------------------------------------------- 1 | // Test suite configuration 2 | // ======================== 3 | 4 | import hook from 'css-modules-require-hook' 5 | 6 | hook({ 7 | extensions: ['.scss', '.css'], 8 | preprocessCss: function() { 9 | return '' 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /lib/read-theme.js: -------------------------------------------------------------------------------- 1 | // Read theme 2 | // ========== 3 | // 4 | // Reads a styleguide theme file and returns it. 5 | 6 | import fs from 'fs' 7 | 8 | export default function readTheme(name) { 9 | const path = `${__dirname}/../src/stylesheets/themes/${name}.scss` 10 | return fs.readFileSync(path).toString() 11 | } 12 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | // Test utilities 2 | // ============== 3 | 4 | import TestUtils from 'react-addons-test-utils' 5 | 6 | export function shallowlyRenderedOutput(Component) { 7 | const shallowRenderer = TestUtils.createRenderer() 8 | shallowRenderer.render(Component) 9 | 10 | return shallowRenderer.getRenderOutput() 11 | } 12 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | // Client routes 2 | // ============= 3 | 4 | import React from 'react' 5 | import { Route } from 'react-router' 6 | import App from './components/app' 7 | import HomeScreen from './components/home-screen' 8 | 9 | export default 10 | 11 | 12 | -------------------------------------------------------------------------------- /bin/publish-gh-page: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | npm run build 4 | rm -rf gh-pages 5 | mkdir gh-pages 6 | cd gh-pages 7 | cp -r ../build/* . 8 | git init . 9 | git remote add origin git@github.com:juliocesar/neob.git 10 | git checkout -b gh-pages 11 | git add . 12 | git commit -m 'Publishing...' 13 | git push origin gh-pages -f 14 | cd .. 15 | rm -rf gh-pages 16 | -------------------------------------------------------------------------------- /src/components/app/test/app.test.js: -------------------------------------------------------------------------------- 1 | // App component test 2 | // ================== 3 | 4 | import React from 'react' 5 | import App from '../' 6 | import test from 'ava' 7 | import { shallow } from 'enzyme' 8 | 9 | test('App should be a div because this is a dumb test', t => { 10 | const component = shallow() 11 | t.is(component.node.type, 'div') 12 | }) 13 | -------------------------------------------------------------------------------- /src/components/app/index.js: -------------------------------------------------------------------------------- 1 | // App container 2 | // ============= 3 | 4 | import React from 'react' 5 | import style from './index.scss' 6 | 7 | export default class App extends React.Component { 8 | render() { 9 | return
10 | {this.props.children} 11 |
12 | } 13 | } 14 | 15 | App.propTypes = { 16 | children: React.PropTypes.node 17 | } 18 | -------------------------------------------------------------------------------- /src/components/home-screen/test/home-screen.test.js: -------------------------------------------------------------------------------- 1 | // HomeScreen component test 2 | // ========================= 3 | 4 | import React from 'react' 5 | import HomeScreen from '../' 6 | import test from 'ava' 7 | import { shallow } from 'enzyme' 8 | 9 | test('HomeScreen example test. Do not do this, for real', t => { 10 | const component = shallow() 11 | t.is(component.find('h1').length, 1) 12 | }) 13 | -------------------------------------------------------------------------------- /src/index.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | neob ― yet another Webpack boilerplate 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/boot.js: -------------------------------------------------------------------------------- 1 | // Client boot file 2 | // ================ 3 | 4 | import './stylesheets/reset.css' 5 | import './stylesheets/top-wrappers.css' 6 | import 'babel-polyfill' 7 | import React from 'react' 8 | import { render } from 'react-dom' 9 | import { Router, hashHistory as history } from 'react-router' 10 | import routes from './routes' 11 | import loadFonts from './components/font-loader' 12 | 13 | loadFonts() 14 | 15 | render( 16 | , 17 | document.querySelector('.main-wrapper') 18 | ) 19 | -------------------------------------------------------------------------------- /src/stylesheets/color.scss: -------------------------------------------------------------------------------- 1 | // Colour scale function 2 | // ===================== 3 | 4 | @function color($name, $scale: 0) { 5 | @if ($scale > 5 or $scale < -5) { 6 | @error "Scale argument cannot be #{$scale}. Passed with #{$name}"; 7 | } 8 | 9 | $val: map-get($color-palette, $name); 10 | 11 | @if $val { 12 | @if $scale < 0 { 13 | @return mix(white, $val, abs($scale) * $step-size); 14 | } @else { 15 | @return mix(black, $val, $scale * $step-size); 16 | } 17 | } @else { 18 | @error "Couldn't find color with name #{$name}"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/components/home-screen/index.js: -------------------------------------------------------------------------------- 1 | // Home screen 2 | // =========== 3 | 4 | import React from 'react' 5 | import Counter from '../counter' 6 | import style from './index.scss' 7 | 8 | export default class HomeScreen extends React.Component { 9 | render() { 10 | return
11 |

12 | Neob 13 |

14 | 15 | 18 | ♥︎ Source on GitHub 19 | 20 |
21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/components/counter/index.scss: -------------------------------------------------------------------------------- 1 | // Counter styles 2 | // ============== 3 | 4 | @import 'dirg'; 5 | @import 'color'; 6 | 7 | .Counter { 8 | display: flex; 9 | flex-flow: column; 10 | align-items: center; 11 | cursor: pointer; 12 | color: color('first'); 13 | 14 | >* + * { 15 | margin-top: units(1); 16 | } 17 | } 18 | 19 | .label { 20 | @include font-size(0); 21 | } 22 | 23 | .countLabel { 24 | @include font-size(4); 25 | font-weight: bold; 26 | width: units(4); 27 | height: units(4); 28 | line-height: units(4); 29 | border-radius: 100%; 30 | background: color('first', -5); 31 | } 32 | -------------------------------------------------------------------------------- /src/components/counter/test/counter.test.js: -------------------------------------------------------------------------------- 1 | // Counter component test 2 | // ====================== 3 | 4 | import React from 'react' 5 | import Counter from '../' 6 | import test from 'ava' 7 | import { shallow } from 'enzyme' 8 | 9 | test('Counter defaults to 0 counts', t => { 10 | const component = shallow() 11 | t.is(component.state('count'), 0) 12 | }) 13 | 14 | test('Counter increases count when clicked', t => { 15 | const component = shallow() 16 | const currentCount = component.state('count') 17 | component.simulate('click') 18 | t.is(component.state('count'), currentCount + 1) 19 | }) 20 | -------------------------------------------------------------------------------- /src/components/home-screen/index.scss: -------------------------------------------------------------------------------- 1 | // Home screen styles 2 | // ================== 3 | 4 | @import 'dirg'; 5 | @import 'color'; 6 | 7 | .HomeScreen { 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | flex-direction: column; 12 | width: 100%; 13 | height: 100%; 14 | text-align: center; 15 | font-family: 'Fira Mono'; 16 | } 17 | 18 | .heading { 19 | @include font-size(4); 20 | color: color('first'); 21 | } 22 | 23 | .heading + * { 24 | margin-top: units(1); 25 | } 26 | 27 | .sourceLink { 28 | @include font-size(0); 29 | color: color('first'); 30 | } 31 | 32 | * + .sourceLink { 33 | margin-top: units(2); 34 | } 35 | -------------------------------------------------------------------------------- /src/components/counter/index.js: -------------------------------------------------------------------------------- 1 | // Counter component 2 | // ================= 3 | 4 | import React from 'react' 5 | import style from './index.scss' 6 | 7 | export default class Counter extends React.Component { 8 | constructor(options) { 9 | super(options) 10 | this.state = { count: this.props.count } 11 | this.onClick = this.onClick.bind(this) 12 | } 13 | 14 | onClick() { 15 | this.setState({ count: this.state.count + 1 }) 16 | } 17 | 18 | render() { 19 | return
20 | {this.state.count} 21 | 22 | Click the number to increase the count 23 | 24 |
25 | } 26 | } 27 | 28 | Counter.propTypes = { 29 | count: React.PropTypes.number.isRequired 30 | } 31 | 32 | Counter.defaultProps = { 33 | count: 0 34 | } 35 | -------------------------------------------------------------------------------- /bin/server.js: -------------------------------------------------------------------------------- 1 | // Development server 2 | // ================== 3 | 4 | import path from 'path' 5 | import express from 'express' 6 | import webpack from 'webpack' 7 | import webpackDevMiddleware from 'webpack-dev-middleware' 8 | import webpackHotMiddleWare from 'webpack-hot-middleware' 9 | import config from '../webpack.development.babel' 10 | 11 | const env = process.env.NODE_ENV || 'development' 12 | const app = express() 13 | const compiler = webpack(config) 14 | 15 | if (env === 'development') { 16 | app.use(webpackDevMiddleware(compiler, { 17 | publicPath: config.output.publicPath, 18 | contentBase: 'src', 19 | stats: { 20 | colors: true, 21 | hash: false, 22 | timings: true, 23 | chunks: false, 24 | chunkModules: false, 25 | modules: false 26 | } 27 | })) 28 | app.use(webpackHotMiddleWare(compiler)) 29 | } else { 30 | app.get('*', function(req, res) { 31 | res.sendFile(path.join(__dirname, 'build/index.html')) 32 | }) 33 | } 34 | 35 | app.use(express.static(path.join(__dirname, '/build'))) 36 | 37 | app.listen(config._hotPort, 'localhost', (err) => { 38 | if (err) console.log(err) 39 | console.info(` ==> Listening on port ${config._hotPort}`) 40 | }) 41 | -------------------------------------------------------------------------------- /src/stylesheets/reset.css: -------------------------------------------------------------------------------- 1 | /* Reset extracted from Compass reset */ 2 | 3 | html, body, div, span, applet, object, iframe, 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 5 | a, abbr, acronym, address, big, cite, code, 6 | del, dfn, em, img, ins, kbd, q, s, samp, 7 | small, strike, strong, sub, sup, tt, var, 8 | b, u, i, center, 9 | dl, dt, dd, ol, ul, li, 10 | fieldset, form, label, legend, 11 | table, caption, tbody, tfoot, thead, tr, th, td, 12 | article, aside, canvas, details, embed, 13 | figure, figcaption, footer, header, hgroup, 14 | menu, nav, output, ruby, section, summary, 15 | time, mark, audio, video { 16 | margin: 0; 17 | padding: 0; 18 | border: 0; 19 | font: inherit; 20 | font-size: 100%; 21 | vertical-align: baseline; } 22 | 23 | html { 24 | line-height: 1; } 25 | 26 | ol, ul { 27 | list-style: none; } 28 | 29 | table { 30 | border-collapse: collapse; 31 | border-spacing: 0; } 32 | 33 | caption, th, td { 34 | text-align: left; 35 | font-weight: normal; 36 | vertical-align: middle; } 37 | 38 | q, blockquote { 39 | quotes: none; } 40 | q:before, q:after, blockquote:before, blockquote:after { 41 | content: ""; 42 | content: none; } 43 | 44 | a img { 45 | border: none; } 46 | 47 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary { 48 | display: block; } 49 | 50 | 51 | /* Further resets custom added */ 52 | input, 53 | select, 54 | textarea, 55 | button { 56 | -webkit-appearance: none; 57 | -moz-appearance: none; 58 | font-family: inherit; 59 | padding: 0; 60 | margin: 0; 61 | border-radius: 0; 62 | border: 0; 63 | background: none; 64 | outline: none; 65 | } 66 | 67 | a, 68 | button { 69 | cursor: pointer; 70 | } 71 | 72 | *, 73 | *:before, 74 | *:after { 75 | box-sizing: border-box; 76 | } 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "ava", 4 | "start": "babel-node ./bin/server", 5 | "build": "rimraf build && NODE_ENV=production webpack --config ./webpack.production.babel.js --progress --profile --colors", 6 | "gh-page": "./bin/publish-gh-page", 7 | "eslint": "eslint . --ext .js --cache" 8 | }, 9 | "ava": { 10 | "files": [ 11 | "src/components/**/*.test.js" 12 | ], 13 | "failFast": true, 14 | "verbose": true, 15 | "require": [ 16 | "babel-core/register", 17 | "./test/configuration.js" 18 | ], 19 | "babel": "inherit" 20 | }, 21 | "dependencies": { 22 | "react": "^15.1.0", 23 | "react-dom": "^15.1.0", 24 | "react-router": "^2.4.1", 25 | "webfontloader": "^1.6.24" 26 | }, 27 | "devDependencies": { 28 | "autoprefixer": "^6.3.6", 29 | "ava": "^0.15.2", 30 | "babel-cli": "^6.10.1", 31 | "babel-core": "^6.9.1", 32 | "babel-loader": "^6.2.4", 33 | "babel-plugin-react-transform": "^2.0.2", 34 | "babel-polyfill": "^6.9.1", 35 | "babel-preset-es2015": "^6.9.0", 36 | "babel-preset-react": "^6.5.0", 37 | "babel-preset-stage-2": "^6.5.0", 38 | "css-loader": "^0.23.1", 39 | "css-modules-require-hook": "^4.0.1", 40 | "dirg": "git+https://github.com/juliocesar/dirg.git", 41 | "dotenv": "^2.0.0", 42 | "enzyme": "^2.3.0", 43 | "eslint": "^2.12.0", 44 | "eslint-config-defaults": "^9.0.0", 45 | "eslint-loader": "^1.3.0", 46 | "eslint-plugin-ava": "^2.5.0", 47 | "eslint-plugin-promise": "^1.3.2", 48 | "eslint-plugin-react": "^5.2.1", 49 | "express": "^4.14.0", 50 | "extract-text-webpack-plugin": "^1.0.1", 51 | "file-loader": "^0.8.5", 52 | "html-webpack-plugin": "^2.21.0", 53 | "json-loader": "^0.5.4", 54 | "node-sass": "^3.7.0", 55 | "postcss-loader": "^0.9.1", 56 | "postcss-modules-values": "^1.1.3", 57 | "react-addons-test-utils": "^15.1.0", 58 | "react-transform-catch-errors": "^1.0.2", 59 | "react-transform-hmr": "^1.0.4", 60 | "redbox-react": "^1.2.6", 61 | "rimraf": "^2.5.2", 62 | "sass-loader": "^3.2.0", 63 | "sinon": "^1.17.4", 64 | "stats-webpack-plugin": "^0.3.1", 65 | "style-loader": "^0.13.1", 66 | "webpack": "^1.13.1", 67 | "webpack-dev-middleware": "^1.6.1", 68 | "webpack-hot-middleware": "^2.10.0" 69 | }, 70 | "babel": { 71 | "presets": [ 72 | "es2015", 73 | "react", 74 | "stage-2" 75 | ] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /webpack.development.babel.js: -------------------------------------------------------------------------------- 1 | // Webpack development config file 2 | // =============================== 3 | 4 | import path from 'path' 5 | import webpack from 'webpack' 6 | import HtmlWebpackPlugin from 'html-webpack-plugin' 7 | import dotenv from 'dotenv' 8 | import readTheme from './lib/read-theme' 9 | import { includePaths } from 'dirg' 10 | 11 | dotenv.load() 12 | 13 | module.exports = { 14 | devtool: 'eval', 15 | entry: [ 16 | 'webpack-hot-middleware/client', 17 | path.join(__dirname, 'src/boot.js') 18 | ], 19 | output: { 20 | path: path.join(__dirname, '/build/'), 21 | filename: '[name].js', 22 | publicPath: '/' 23 | }, 24 | plugins: [ 25 | new HtmlWebpackPlugin({ 26 | template: 'src/index.tpl.html', 27 | inject: 'body', 28 | filename: 'index.html' 29 | }), 30 | new webpack.optimize.OccurenceOrderPlugin(), 31 | new webpack.HotModuleReplacementPlugin(), 32 | new webpack.NoErrorsPlugin(), 33 | new webpack.DefinePlugin({ 34 | 'process.env.NODE_ENV': JSON.stringify('development'), 35 | '__DEV__': JSON.stringify(process.env.NODE_ENV) 36 | }) 37 | ], 38 | module: { 39 | preLoaders: [ 40 | { 41 | test: /(\.jsx|\.js)$/, 42 | loader: 'eslint' 43 | } 44 | ], 45 | loaders: [ 46 | { 47 | test: /(\.jsx|\.js)$/, 48 | exclude: /node_modules/, 49 | loader: 'babel' 50 | }, 51 | { 52 | test: /\.json?$/, 53 | exclude: /node_modules/, 54 | loader: 'json' 55 | }, 56 | { 57 | test: /\.css$/, 58 | exclude: /node_modules/, 59 | loader: 'style!css' 60 | }, 61 | { 62 | test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, 63 | exclude: /node_modules/, 64 | loader: 'file-loader' 65 | }, 66 | { 67 | test: /\.scss$/, 68 | exclude: /node_modules/, 69 | loader: [ 70 | 'style', 71 | [ 72 | 'css?importLoaders=1', 73 | 'modules', 74 | 'localIdentName=[name]---[local]---[hash:base64:5]' 75 | ].join('&'), 76 | 'sass' 77 | ].join('!') 78 | } 79 | ] 80 | }, 81 | postcss: [ 82 | require('postcss-modules-values') 83 | ], 84 | sassLoader: { 85 | includePaths: [ 86 | path.resolve(__dirname, 'src/stylesheets'), 87 | ...includePaths 88 | ], 89 | data: [readTheme(process.env.THEME || 'default')] 90 | }, 91 | _hotPort: 4567 92 | } 93 | -------------------------------------------------------------------------------- /webpack.production.babel.js: -------------------------------------------------------------------------------- 1 | // Webpack development config file 2 | // =============================== 3 | 4 | import path from 'path' 5 | import webpack from 'webpack' 6 | import HtmlWebpackPlugin from 'html-webpack-plugin' 7 | import ExtractTextPlugin from 'extract-text-webpack-plugin' 8 | import StatsPlugin from 'stats-webpack-plugin' 9 | import dotenv from 'dotenv' 10 | import readTheme from './lib/read-theme' 11 | import { includePaths } from 'dirg' 12 | 13 | dotenv.load() 14 | 15 | const CSSLoaders = [ 16 | 'css?importLoaders=1', 17 | 'modules&localIdentName=[name]---[local]---[hash:base64:5]!sass' 18 | ].join('&') 19 | 20 | module.exports = { 21 | devtool: 'cheap-source-map', 22 | entry: [ 23 | path.join(__dirname, 'src/boot.js') 24 | ], 25 | output: { 26 | path: path.join(__dirname, '/build/'), 27 | filename: '[name]-[hash].min.js' 28 | }, 29 | plugins: [ 30 | new webpack.optimize.OccurenceOrderPlugin(), 31 | new HtmlWebpackPlugin({ 32 | template: 'src/index.tpl.html', 33 | inject: 'body', 34 | filename: 'index.html' 35 | }), 36 | new ExtractTextPlugin('[name]-[hash].min.css'), 37 | new webpack.optimize.UglifyJsPlugin({ 38 | compressor: { 39 | warnings: false, 40 | screw_ie8: true 41 | } 42 | }), 43 | new webpack.optimize.DedupePlugin(), 44 | new StatsPlugin('webpack.stats.json', { source: false, modules: false }), 45 | new webpack.DefinePlugin({ 46 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 47 | '__DEV__': JSON.stringify(process.env.NODE_ENV) 48 | }) 49 | ], 50 | module: { 51 | loaders: [ 52 | { 53 | test: /\.js?$/, 54 | exclude: /node_modules/, 55 | loader: 'babel' 56 | }, 57 | { 58 | test: /\.json?$/, 59 | loader: 'json' 60 | }, 61 | { 62 | test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/, 63 | loader: 'file-loader' 64 | }, 65 | { 66 | test: /\.css$/, 67 | loader: ExtractTextPlugin.extract('style', 'css') 68 | }, 69 | { 70 | test: /\.scss$/, 71 | loader: ExtractTextPlugin.extract('style', CSSLoaders) 72 | } 73 | ] 74 | }, 75 | sassLoader: { 76 | includePaths: [ 77 | path.resolve(__dirname, 'src/stylesheets'), 78 | ...includePaths 79 | ], 80 | data: [readTheme(process.env.THEME || 'default')] 81 | }, 82 | postcss: [ 83 | require('autoprefixer'), 84 | require('postcss-modules-values') 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Neob 2 | 3 | A React/Webpack/(S)CSS Modules/testing applications boilerplate. 4 | 5 | ## Features 6 | 7 | * Webpack + hot-module-replacement ([babel-plugin-react-transform](https://github.com/gaearon/babel-plugin-react-transform) + [react-transform-hmr](https://github.com/gaearon/react-transform-hmr)). 8 | * Production optimisations settings/plugins for Webpack. 9 | * Modular component organisation. Component folders in `src/components` carry code, styles and tests. 10 | * [css-modules](https://github.com/css-modules/css-modules/) + [SCSS](https://github.com/jtangelder/sass-loader) + [PostCSS](https://github.com/postcss/postcss). 11 | * **Themes** using colour scales. Single mixin for consolidating colour sourcing. 12 | * **Sizing scales** (vertical rhythm and other goodies) done using [dirg](https://github.com/juliocesar/dirg). 13 | * Basic routing setup with [react-router](https://github.com/reactjs/react-router). 14 | * Tests using [Ava](https://github.com/avajs/ava/) and [Enzyme](http://airbnb.io/enzyme/). 15 | * ESLint, with a slightly modified version of the [Feross Standard](https://github.com/feross/standard) as default, extended to include test files linting. Drop your own `.eslintrc.json` file on top of it if you don’t like it. 16 | * **No Gulp or Grunt**. Automation is done by commands in `package.json`. More complex stuff done by scripts in `bin`. 17 | 18 | ## Usage 19 | 20 | * Start the dev server: 21 | 22 | ``` 23 | $ npm start 24 | ``` 25 | 26 | * Build to static files in `build`: 27 | 28 | ``` 29 | $ npm run build 30 | ``` 31 | 32 | * Compile static application and deploy it to GitHub pages (update `bin/publish-gh-page` with your repository’s actual address): 33 | 34 | ``` 35 | $ ./bin/publish-gh-page 36 | ``` 37 | 38 | ## Themes 39 | 40 | Themes are SCSS hashes kept in `src/stylesheets/themes`. The [color mixin](https://github.com/juliocesar/neob/blob/master/src/stylesheets/color.scss) then sources them by key, and allows for 5 tint steps towards darker or lighter, allowing controlled variations for each entry. 41 | 42 | By default, the build will look for a `default.scss` file. Alternative files can be loaded by passing an environment variable `THEME`: 43 | 44 | ``` 45 | $ THEME=green npm start 46 | ``` 47 | 48 | Or build to static files using a specific theme: 49 | 50 | ``` 51 | $ THEME=green npm run build 52 | ``` 53 | ## Grid 54 | 55 | Refer to [dirg](https://github.com/juliocesar/dirg)’s documentation, or [the longer post](https://medium.com/@julio_ody/sizing-supra-summa-3701cd075244#.dhlhjf6vy) on it. 56 | 57 | You can override the default scale by supplying one in a separate file. Create (for example), a file `src/stylesheets/dirg-scale.scss` with: 58 | 59 | ``` 60 | $dirg-scales: ( 61 | default: ( 62 | font-size: 16px, 63 | unit: 21px, 64 | factor: 1.35 65 | ) 66 | ); 67 | 68 | ``` 69 | 70 | Then source it right before dirg: 71 | 72 | ``` 73 | @import 'dirg-scale'; 74 | @import 'dirg'; 75 | … 76 | ``` 77 | 78 | # Credits (y u no fork) 79 | 80 | neob is based off of [react-kickstart](https://github.com/vesparny/react-kickstart). I wasn’t sure initially how much it’d look like it, so I went with a copy first. It has now diverged sufficiently anyway to become more than just a build boilerplate, but introduce a few helpers for building applications. 81 | 82 | # License 83 | 84 | MIT. 85 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 6, 4 | "ecmaFeatures": { 5 | "experimentalObjectRestSpread": true, 6 | "jsx": true 7 | }, 8 | "sourceType": "module" 9 | }, 10 | 11 | "env": { 12 | "es6": true, 13 | "node": true 14 | }, 15 | 16 | "plugins": [ 17 | "promise", 18 | "react", 19 | "ava" 20 | ], 21 | 22 | "globals": { 23 | "document": false, 24 | "navigator": false, 25 | "window": false 26 | }, 27 | 28 | "rules": { 29 | "accessor-pairs": 2, 30 | "array-bracket-spacing": [2, "never"], 31 | "arrow-spacing": [2, { "before": true, "after": true }], 32 | "block-spacing": [2, "always"], 33 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 34 | "camelcase": [2, { "properties": "never" }], 35 | "comma-dangle": [2, "never"], 36 | "comma-spacing": [2, { "before": false, "after": true }], 37 | "comma-style": [2, "last"], 38 | "computed-property-spacing": [2, "never"], 39 | "constructor-super": 2, 40 | "curly": [2, "multi-line"], 41 | "dot-location": [2, "property"], 42 | "eol-last": 2, 43 | "eqeqeq": [2, "allow-null"], 44 | "generator-star-spacing": [2, { "before": true, "after": true }], 45 | "handle-callback-err": [2, "^(err|error)$" ], 46 | "indent": [2, 2, { "SwitchCase": 1 }], 47 | "jsx-quotes": [2, "prefer-double"], 48 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 49 | "keyword-spacing": [2, { "before": true, "after": true }], 50 | "max-len": [2, 80], 51 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 52 | "new-parens": 2, 53 | "no-array-constructor": 2, 54 | "no-caller": 2, 55 | "no-class-assign": 2, 56 | "no-cond-assign": 2, 57 | "no-const-assign": 2, 58 | "no-control-regex": 2, 59 | "no-debugger": 2, 60 | "no-delete-var": 2, 61 | "no-dupe-args": 2, 62 | "no-dupe-class-members": 2, 63 | "no-dupe-keys": 2, 64 | "no-duplicate-case": 2, 65 | "no-duplicate-imports": 2, 66 | "no-empty-character-class": 2, 67 | "no-empty-pattern": 2, 68 | "no-eval": 2, 69 | "no-ex-assign": 2, 70 | "no-extend-native": 2, 71 | "no-extra-bind": 2, 72 | "no-extra-boolean-cast": 2, 73 | "no-extra-parens": [2, "functions"], 74 | "no-fallthrough": 2, 75 | "no-floating-decimal": 2, 76 | "no-func-assign": 2, 77 | "no-implied-eval": 2, 78 | "no-inner-declarations": [2, "functions"], 79 | "no-invalid-regexp": 2, 80 | "no-irregular-whitespace": 2, 81 | "no-iterator": 2, 82 | "no-label-var": 2, 83 | "no-labels": [2, { "allowLoop": false, "allowSwitch": false }], 84 | "no-lone-blocks": 2, 85 | "no-mixed-spaces-and-tabs": 2, 86 | "no-multi-spaces": 2, 87 | "no-multi-str": 2, 88 | "no-multiple-empty-lines": [2, { "max": 1 }], 89 | "no-native-reassign": 2, 90 | "no-negated-in-lhs": 2, 91 | "no-new": 2, 92 | "no-new-func": 2, 93 | "no-new-object": 2, 94 | "no-new-require": 2, 95 | "no-new-symbol": 2, 96 | "no-new-wrappers": 2, 97 | "no-obj-calls": 2, 98 | "no-octal": 2, 99 | "no-octal-escape": 2, 100 | "no-path-concat": 2, 101 | "no-proto": 2, 102 | "no-redeclare": 2, 103 | "no-regex-spaces": 2, 104 | "no-return-assign": [2, "except-parens"], 105 | "no-self-assign": 2, 106 | "no-self-compare": 2, 107 | "no-sequences": 2, 108 | "no-shadow-restricted-names": 2, 109 | "no-spaced-func": 2, 110 | "no-sparse-arrays": 2, 111 | "no-this-before-super": 2, 112 | "no-throw-literal": 2, 113 | "no-trailing-spaces": 2, 114 | "no-undef": 2, 115 | "no-undef-init": 2, 116 | "no-unexpected-multiline": 2, 117 | "no-unmodified-loop-condition": 2, 118 | "no-unneeded-ternary": [2, { "defaultAssignment": false }], 119 | "no-unreachable": 2, 120 | "no-unsafe-finally": 2, 121 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 122 | "no-useless-call": 2, 123 | "no-useless-computed-key": 2, 124 | "no-useless-constructor": 2, 125 | "no-useless-escape": 2, 126 | "no-whitespace-before-property": 2, 127 | "no-with": 2, 128 | "object-curly-spacing": [2, "always"], 129 | "one-var": [2, { "initialized": "never" }], 130 | "operator-linebreak": [2, "after"], 131 | "padded-blocks": [2, "never"], 132 | "quotes": [2, "single", "avoid-escape"], 133 | "semi": [2, "never"], 134 | "semi-spacing": [2, { "before": false, "after": true }], 135 | "space-before-blocks": [2, "always"], 136 | "space-in-parens": [2, "never"], 137 | "space-infix-ops": 2, 138 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 139 | "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], 140 | "template-curly-spacing": [2, "never"], 141 | "use-isnan": 2, 142 | "valid-typeof": 2, 143 | "wrap-iife": [2, "any"], 144 | "yield-star-spacing": [2, "both"], 145 | "yoda": [2, "never"], 146 | 147 | "react/jsx-no-undef": 1, 148 | "react/jsx-uses-react": 1, 149 | "react/jsx-uses-vars": 1, 150 | "react/jsx-no-bind": 2, 151 | "react/no-did-update-set-state": 2, 152 | "react/no-unknown-property": 2, 153 | "react/prop-types": 2, 154 | 155 | "promise/param-names": 2, 156 | 157 | "ava/assertion-arguments": "error", 158 | "ava/assertion-message": ["off", "always"], 159 | "ava/max-asserts": ["error", 2], 160 | "ava/no-cb-test": "error", 161 | "ava/no-identical-title": "error", 162 | "ava/no-ignored-test-files": "error", 163 | "ava/no-invalid-end": "error", 164 | "ava/no-only-test": "error", 165 | "ava/no-skip-assert": "error", 166 | "ava/no-skip-test": "error", 167 | "ava/no-statement-after-end": "error", 168 | "ava/no-todo-test": "warn", 169 | "ava/no-unknown-modifiers": "error", 170 | "ava/prefer-power-assert": "off", 171 | "ava/test-ended": "error", 172 | "ava/test-title": ["error", "if-multiple"], 173 | "ava/use-t-well": "error", 174 | "ava/use-t": "error", 175 | "ava/use-test": "error", 176 | "ava/use-true-false": "error" 177 | } 178 | } 179 | --------------------------------------------------------------------------------