├── Procfile ├── views ├── index.ejs ├── contacts.ejs └── layout.ejs ├── .coveralls.yml ├── client ├── main.scss ├── entryPoints │ ├── common-styles.js │ ├── common.js │ ├── contacts.jsx │ └── index.jsx └── components │ ├── NotFound │ ├── NotFoundComponent.jsx │ └── NotFoundComponentTest.jsx │ └── Index │ ├── IndexComponentTest.jsx │ └── IndexComponent.jsx ├── .travis.yml ├── .gitignore ├── .jshintrc ├── test └── phantomjs-shims.js ├── readme.md ├── package.json ├── index.js ├── karma.conf.js └── webpack.config.js /Procfile: -------------------------------------------------------------------------------- 1 | web: node index.js 2 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 |

index - <%= entryPoint %>

-------------------------------------------------------------------------------- /views/contacts.ejs: -------------------------------------------------------------------------------- 1 |

<%= entryPoint %>

2 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | repo_token: C4YPbuExDE7Ei7c4RPOjDnUD1D8ofOCN2 3 | -------------------------------------------------------------------------------- /client/main.scss: -------------------------------------------------------------------------------- 1 | $background: white; 2 | 3 | body { 4 | background: $background; 5 | } 6 | -------------------------------------------------------------------------------- /client/entryPoints/common-styles.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require("!style!css!sass!../main.scss"); 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | cache: npm 5 | script: "npm run-script travis" 6 | -------------------------------------------------------------------------------- /client/entryPoints/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./common-styles'); 4 | var React = require('react'); 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | npm-debug.log 4 | coverage 5 | client/build 6 | test/reports 7 | client/common.* 8 | client/*.entry.js* 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "immed": true, 8 | "newcap": true, 9 | "noarg": true, 10 | "undef": true, 11 | "unused": "vars", 12 | "strict": true, 13 | "predef": [ 14 | "_" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /client/entryPoints/contacts.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | require('./common-styles'); 6 | 7 | var ContactsComponent = React.createClass({ 8 | render: function() { 9 | return ( 10 |

Hello, Contacts Entry from React!

11 | ); 12 | } 13 | }); 14 | 15 | React.render(, document.body); 16 | -------------------------------------------------------------------------------- /client/components/NotFound/NotFoundComponent.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | 5 | var NotFoundComponent = React.createClass({ 6 | 7 | render: function() { 8 | return ( 9 |
10 |

404. Not found.

11 |

12 | 13 |

14 |

Go to index

15 |
16 | ); 17 | } 18 | }); 19 | 20 | module.exports = NotFoundComponent; 21 | -------------------------------------------------------------------------------- /views/layout.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test page 4 | 5 | 6 | <%-body%> 7 | 8 | <% if (env.production) { %> 9 | 10 | 11 | <% } else { %> 12 | 13 | 14 | <% } %> 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/components/NotFound/NotFoundComponentTest.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react/addons'); 2 | 3 | var NotFound = require('./NotFoundComponent'); 4 | var ReactTestUtils; 5 | 6 | describe('NotFoundComponent', function() { 7 | 8 | beforeEach(function() { 9 | ReactTestUtils = React.addons.TestUtils; 10 | }); 11 | 12 | it('should render', function() { 13 | var instance = ReactTestUtils.renderIntoDocument(); 14 | 15 | expect(instance).toBeDefined(); 16 | expect(instance.refs.title).toBeDefined(); 17 | expect(instance.refs.title.props.children).toEqual('404. Not found.'); 18 | }); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /client/entryPoints/index.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./common-styles'); 4 | 5 | var React = require('react'); 6 | 7 | var IndexComponent = require('../components/Index/IndexComponent'); 8 | var NotFoundComponent = require('../components/NotFound/NotFoundComponent'); 9 | 10 | var Router = require('react-mini-router'); 11 | 12 | var MainComponent = React.createClass({ 13 | mixins : [Router.RouterMixin], 14 | 15 | routes : { 16 | '/': 'onIndex', 17 | '/404': 'notFound' 18 | }, 19 | 20 | onIndex: function(){ 21 | var items = []; 22 | 23 | for (var i = 0; i < 2; i++) { 24 | items.push(i); 25 | } 26 | 27 | return ; 28 | }, 29 | 30 | notFound: function () { 31 | return ; 32 | }, 33 | 34 | render: function() { 35 | return ( 36 |
{this.renderCurrentRoute()}
37 | ); 38 | } 39 | }); 40 | 41 | React.render(, document.body); 42 | -------------------------------------------------------------------------------- /client/components/Index/IndexComponentTest.jsx: -------------------------------------------------------------------------------- 1 | var React = require('react/addons'); 2 | 3 | var IndexComponent = require('./IndexComponent'); 4 | var ReactTestUtils; 5 | 6 | describe('IndexComponent', function() { 7 | 8 | beforeEach(function() { 9 | ReactTestUtils = React.addons.TestUtils; 10 | }); 11 | 12 | it('should render', function() { 13 | var items = []; 14 | 15 | for (var i = 0; i < 2; i++) { 16 | items.push(i); 17 | } 18 | 19 | var componentInstance = ReactTestUtils.renderIntoDocument(); 20 | var indexItems = ReactTestUtils.scryRenderedDOMComponentsWithTag(componentInstance, 'li'); 21 | 22 | expect(componentInstance).toBeDefined(); 23 | expect(componentInstance.refs.indexList).toBeDefined(); 24 | expect(indexItems).toBeDefined(); 25 | expect(indexItems.length).toEqual(items.length); 26 | }); 27 | 28 | it('should render empty list', function() { 29 | var componentInstance = ReactTestUtils.renderIntoDocument(); 30 | 31 | expect(componentInstance).toBeDefined(); 32 | expect(componentInstance.refs.empty).toBeDefined(); 33 | expect(componentInstance.refs.empty.props.children).toEqual('Index is empty.'); 34 | }); 35 | 36 | }); -------------------------------------------------------------------------------- /test/phantomjs-shims.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 3 | var Ap = Array.prototype; 4 | var slice = Ap.slice; 5 | var Fp = Function.prototype; 6 | 7 | if (!Fp.bind) { 8 | // PhantomJS doesn't support Function.prototype.bind natively, so 9 | // polyfill it whenever this module is required. 10 | Fp.bind = function(context) { 11 | var func = this; 12 | var args = slice.call(arguments, 1); 13 | 14 | function bound() { 15 | var invokedAsConstructor = func.prototype && (this instanceof func); 16 | return func.apply( 17 | // Ignore the context parameter when invoking the bound function 18 | // as a constructor. Note that this includes not only constructor 19 | // invocations using the new keyword but also calls to base class 20 | // constructors such as BaseClass.call(this, ...) or super(...). 21 | !invokedAsConstructor && context || this, 22 | args.concat(slice.call(arguments)) 23 | ); 24 | } 25 | 26 | // The bound function must share the .prototype of the unbound 27 | // function so that any object created by one constructor will count 28 | // as an instance of both constructors. 29 | bound.prototype = func.prototype; 30 | 31 | return bound; 32 | }; 33 | } 34 | 35 | })(); 36 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # react-webpack-boilerplate 2 | [![Build Status](http://img.shields.io/travis/srn/react-webpack-boilerplate.svg?style=flat-square)](https://travis-ci.org/srn/react-webpack-boilerplate) [![Build Status](http://img.shields.io/coveralls/srn/react-webpack-boilerplate.svg?style=flat-square)](https://coveralls.io/r/srn/react-webpack-boilerplate) [![Dependency Status](http://img.shields.io/gemnasium/srn/react-webpack-boilerplate.svg?style=flat-square)](https://gemnasium.com/srn/react-webpack-boilerplate) 3 | 4 | Simple production-ready boilerplate for [React](http://facebook.github.io/react/) and [Webpack](http://webpack.github.io/) (SASS and React hot reloading) 5 | 6 | ## Install 7 | 8 | ```sh 9 | # Clone repository 10 | $ git clone https://github.com/srn/react-webpack-boilerplate.git && cd react-webpack-boilerplate 11 | 12 | # Install dependencies 13 | $ npm install 14 | ``` 15 | 16 | ## Development 17 | 18 | ```sh 19 | $ node index 20 | ``` 21 | 22 | Go to [http://localhost:3001](http://localhost:3001) and see the magic happen. 23 | 24 | ## Add entry point 25 | 26 | * Create new file in `/client/entryPoints` 27 | * Add a new view file in `/views` 28 | * Add a new entry in `webpack.config.js` 29 | * Restart the development server (required whenever you make changes to webpack.config.js) 30 | 31 | ## Production 32 | 33 | Run this command to output the current environment: 34 | 35 | ```sh 36 | export | grep NODE_ENV 37 | ``` 38 | 39 | If you want to run the project in production, set the `NODE_ENV` environment variable to `production`. 40 | 41 | ```sh 42 | export NODE_ENV=production 43 | ``` 44 | 45 | Run this command to generate the required bundles for production: 46 | 47 | ```sh 48 | npm run-script bundle 49 | ``` 50 | 51 | Run this command to switch back to development environment: 52 | 53 | ```sh 54 | export NODE_ENV=development 55 | ``` 56 | 57 | ## Tests 58 | 59 | ```sh 60 | $ npm test 61 | ``` 62 | 63 | ## License 64 | 65 | MIT © [Søren Brokær](http://srn.io) 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-webpack-boilerplate", 3 | "version": "1.0.0", 4 | "description": "Simple production ready boilerplate for React, Webpack (sass and React hot reloading), tests and coverage.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "NODE_ENV=test ./node_modules/karma/bin/karma start --single-run", 8 | "jenkins": "NODE_ENV=test ./node_modules/karma/bin/karma start --single-run --reporters junit,dots", 9 | "travis": "NODE_ENV=test ./node_modules/karma/bin/karma start --single-run && cat ./coverage/lcov/lcov.info | ./node_modules/coveralls/bin/coveralls.js", 10 | "bundle": "node ./node_modules/webpack/bin/webpack.js -p -d" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/srn/react-webpack-boilerplate.git" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "webpack", 19 | "boilerplate", 20 | "production", 21 | "sass", 22 | "hot", 23 | "reload" 24 | ], 25 | "author": "Søren Brokær (http://srn.io)", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/srn/react-webpack-boilerplate/issues" 29 | }, 30 | "homepage": "https://github.com/srn/react-webpack-boilerplate", 31 | "dependencies": { 32 | "PlatformJS": "git+ssh://git@github.com:podio/platformJS.git", 33 | "compression": "^1.1.0", 34 | "css-loader": "^0.8.0", 35 | "ejs": "^1.0.0", 36 | "express": "^4.9.5", 37 | "express-ejs-layouts": "^1.1.0", 38 | "istanbul": "^0.3.2", 39 | "jsx-loader": "^0.12.1", 40 | "react-mini-router": "^1.0.0", 41 | "sass-loader": "^0.2.0", 42 | "style-loader": "^0.8.0", 43 | "webpack": "1.4.13", 44 | "bluebird": "^2.3.5", 45 | "autoprefixer-loader": "^1.0.0", 46 | "jshint": "^2.5.10", 47 | "jshint-loader": "^0.8.0", 48 | "merge": "^1.2.0", 49 | "karma-webpack": "^1.3.1", 50 | "normalize.css": "git://github.com/podio/normalize.css" 51 | }, 52 | "devDependencies": { 53 | "coveralls": "^2.11.2", 54 | "istanbul-instrumenter-loader": "^0.1.2", 55 | "karma": "0.12.23", 56 | "karma-cli": "0.0.4", 57 | "karma-coverage": "^0.2.6", 58 | "karma-jasmine": "0.2.2", 59 | "karma-phantomjs-launcher": "0.1.4", 60 | "karma-webpack": "^1.3.1", 61 | "react-hot-loader": "0.4.5", 62 | "webpack-dev-server": "1.6.5", 63 | "karma-junit-reporter": "^0.2.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var express = require('express'); 3 | var app = express(); 4 | var compress = require('compression'); 5 | var layouts = require('express-ejs-layouts'); 6 | var fs = require('fs'); 7 | 8 | var Promise = require("bluebird"); 9 | 10 | app.set('layout'); 11 | app.set('view engine', 'ejs'); 12 | app.set('view options', { layout:'layout' }); 13 | 14 | app.use(compress()); 15 | app.use("/client", express.static(path.join(__dirname, 'client'))); 16 | app.use(layouts); 17 | 18 | var env = { 19 | production: process.env['NODE_ENV'] === 'production' 20 | }; 21 | 22 | var retrieveCommonFileData = function (filePath) { 23 | return new Promise(function (resolve, reject) { 24 | fs.readFile(path.join(__dirname, filePath), { encoding: 'utf8' }, function(err, data) { 25 | if (err) { 26 | reject(err); 27 | } 28 | 29 | resolve(data); 30 | }); 31 | }); 32 | }; 33 | 34 | app.get('/*', function(req, res) { 35 | var split = req.url.split('/')[1]; 36 | /* 37 | var common = retrieveCommonFileData('client/build/common.js'); 38 | 39 | // use settle for future usage 40 | Promise.settle([common]).done(function (results) { 41 | res.render('index', { 42 | locals: { 43 | env: env, 44 | entryPoint: 'index', 45 | inlineCommon: results[0].value() 46 | } 47 | }); 48 | }); 49 | */ 50 | 51 | res.render('index', { 52 | locals: { 53 | env: env, 54 | entryPoint: split || 'index' 55 | } 56 | }); 57 | }); 58 | 59 | var port = Number(process.env.PORT || 3001); 60 | app.listen(port, function () { 61 | console.log('server running at localhost:3001, go refresh and see magic'); 62 | }); 63 | 64 | if (env.production === false) { 65 | var webpack = require('webpack'); 66 | var WebpackDevServer = require('webpack-dev-server'); 67 | var webpackConfig = require('./webpack.config'); 68 | 69 | new WebpackDevServer(webpack(webpackConfig), { 70 | publicPath: webpackConfig.output.publicPath, 71 | 72 | hot: true, 73 | 74 | stats: { 75 | colors: true 76 | }, 77 | 78 | headers: { 79 | 'Access-Control-Allow-Origin': 'http://localhost:3001', 80 | 'Access-Control-Allow-Headers': 'X-Requested-With' 81 | } 82 | }).listen(3000, 'localhost', function (err, result) { 83 | if (err) { 84 | console.log(err); 85 | } 86 | 87 | console.log('webpack dev server listening on localhost:3000'); 88 | }); 89 | } 90 | -------------------------------------------------------------------------------- /client/components/Index/IndexComponent.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var PlatformJS = require('PlatformJS'); 5 | 6 | var clientId = ''; 7 | var redirectURL = 'http://localhost:3001'; 8 | var platform = new PlatformJS({ authType: 'client', clientId: clientId }); 9 | 10 | var IndexComponent = React.createClass({ 11 | getInitialState: function() { 12 | return {}; 13 | }, 14 | 15 | getDefaultProps: function () { 16 | return { 17 | items: [] 18 | }; 19 | }, 20 | 21 | fetchUserDetails: function() { 22 | if (!_.isUndefined(this.state.user)) { 23 | return; 24 | } 25 | 26 | platform.request('get', '/user/status').then(function(responseData) { 27 | this.setState({ user: responseData.profile }); 28 | }.bind(this)); 29 | }, 30 | 31 | showUserDetails: function() { 32 | if (this.state.user) { 33 | return ( 34 |

35 | Using a sophisticated technology we managed to get your user data: 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
Name:{this.state.user.name}
Email:{this.state.user.mail[0]}
46 |

47 | ); 48 | } 49 | }, 50 | 51 | showClientSideAuthExample: function() { 52 | if (!_.isEmpty(clientId)) { 53 | if (platform.isAuthenticated()) { 54 | this.fetchUserDetails(); 55 | 56 | return ( 57 |

58 | We are happy, because we are authenticated! 59 | {this.showUserDetails()} 60 |

61 | ); 62 | } else { 63 | return ( 64 |

65 | Now let's do a client side auth: Click on this link 66 |

67 | ); 68 | } 69 | } 70 | }, 71 | 72 | render: function() { 73 | if (this.props.items.length === 0) { 74 | return ( 75 |

Index is empty.

76 | ); 77 | } 78 | 79 | return ( 80 |
81 |

Welcome to the React Webpack Boilerplate

82 | Please take a look at this cool list of things: 83 |
    84 | {this.props.items.map(function(item, index){ 85 | return
  • item {item}
  • ; 86 | })} 87 |
88 | {this.showClientSideAuthExample()} 89 |
90 | ); 91 | } 92 | }); 93 | 94 | module.exports = IndexComponent; 95 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Thu Jun 12 2014 00:40:31 GMT+0200 (CEST) 3 | 4 | var webpackConfig = require('./webpack.config'); 5 | webpackConfig['cache'] = true; 6 | webpackConfig['module']['postLoaders'] = [ { 7 | test: /\.jsx$/, 8 | exclude: /(Test|node_modules)\//, 9 | loader: 'istanbul-instrumenter' 10 | } ]; 11 | 12 | module.exports = function(config) { 13 | config.set({ 14 | 15 | // base path that will be used to resolve all patterns (eg. files, exclude) 16 | basePath: '', 17 | 18 | 19 | // frameworks to use 20 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 21 | frameworks: ['jasmine'], 22 | 23 | 24 | // list of files / patterns to load in the browser 25 | files: [ 26 | 'test/phantomjs-shims.js', 27 | 28 | 'client/**/*Test.*' 29 | ], 30 | 31 | 32 | // list of files to exclude 33 | exclude: [ 34 | 'client/app.*', 35 | '*.scss' 36 | ], 37 | 38 | 39 | // preprocess matching files before serving them to the browser 40 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 41 | preprocessors: { 42 | 'client/**/*Test.*': ['webpack'] 43 | }, 44 | 45 | 46 | webpack: webpackConfig, 47 | 48 | 49 | webpackServer: { 50 | stats: { 51 | colors: true 52 | }, 53 | quiet: true 54 | }, 55 | 56 | 57 | // test results reporter to use 58 | // possible values: 'dots', 'progress' 59 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 60 | reporters: ['dots', 'coverage'], 61 | 62 | 63 | coverageReporter: { 64 | type: 'lcov', 65 | dir: 'coverage/', 66 | subdir: 'lcov' 67 | }, 68 | 69 | 70 | junitReporter: { 71 | outputFile: 'test/reports/test-results.xml', 72 | suite: '' 73 | }, 74 | 75 | 76 | // web server port 77 | port: 9876, 78 | 79 | 80 | // enable / disable colors in the output (reporters and logs) 81 | colors: true, 82 | 83 | 84 | // level of logging 85 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 86 | logLevel: config.LOG_INFO, 87 | 88 | 89 | // enable / disable watching file and executing tests whenever any file changes 90 | autoWatch: true, 91 | 92 | 93 | // start these browsers 94 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 95 | browsers: ['PhantomJS'], 96 | 97 | 98 | // Continuous Integration mode 99 | // if true, Karma captures browsers, runs the tests and exits 100 | singleRun: false, 101 | 102 | 103 | plugins: [ 104 | require("karma-jasmine"), 105 | require("karma-phantomjs-launcher"), 106 | require("karma-coverage"), 107 | require("karma-webpack"), 108 | require("karma-junit-reporter") 109 | ] 110 | }); 111 | }; 112 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | 5 | var merge = require('merge'); 6 | 7 | var jshintrc = merge(fs.readFileSync('./.jshintrc', {encoding: 'utf8'}), { 8 | // jshint errors are displayed by default as warnings 9 | // set emitErrors to true to display them as errors 10 | emitErrors: true, 11 | 12 | // jshint to not interrupt the compilation 13 | // if you want any file with jshint errors to fail 14 | // set failOnHint to true 15 | failOnHint: true 16 | }); 17 | 18 | var currentEnv = process.env['NODE_ENV']; 19 | 20 | var env = { 21 | production: currentEnv === 'production', 22 | development: currentEnv === 'development', 23 | test: currentEnv === 'test' 24 | }; 25 | 26 | var addDevServerEntryPoint = function (entryPoint) { 27 | if (currentEnv !== 'production') { 28 | var entries = [ 29 | 'webpack-dev-server/client?http://localhost:3000', 30 | 'webpack/hot/dev-server', 31 | entryPoint 32 | ]; 33 | return entries; 34 | } else { 35 | return entryPoint; 36 | } 37 | }; 38 | 39 | var entry = { 40 | index: addDevServerEntryPoint('./client/entryPoints/index'), 41 | contacts: addDevServerEntryPoint('./client/entryPoints/contacts') 42 | }; 43 | 44 | if (currentEnv !== 'test') { 45 | entry['common.js'] = './client/entryPoints/common.js' 46 | } 47 | 48 | var plugins = []; 49 | 50 | if (currentEnv !== 'test') { 51 | plugins.push(new webpack.optimize.CommonsChunkPlugin('common', 'common.js')); 52 | } 53 | 54 | if (env.production) { 55 | plugins.push(new webpack.optimize.DedupePlugin()); 56 | plugins.push(new webpack.optimize.UglifyJsPlugin({ output: {comments: false} })); // https://github.com/webpack/webpack/issues/324 57 | } 58 | 59 | if (env.production === false) { 60 | plugins.push(new webpack.HotModuleReplacementPlugin()); 61 | } 62 | 63 | var jsxLoaders = ['jsx']; 64 | 65 | if (env.production === false && env.test === false) { 66 | jsxLoaders.unshift('jshint-loader'); 67 | jsxLoaders.unshift('react-hot'); 68 | } 69 | 70 | var exports = { 71 | entry: entry, 72 | 73 | output: { 74 | path: env.production ? path.join('client', 'build') : __dirname + '/client', 75 | 76 | filename: "[name].entry.js", 77 | 78 | publicPath: env.production ? 'http://www.production-site.com' : 'http://localhost:3000/client/' 79 | }, 80 | 81 | resolve: { 82 | extensions: ['', '.jsx', '.js'] 83 | }, 84 | 85 | plugins: plugins, 86 | 87 | // http://webpack.github.io/docs/loaders.html#loader-order 88 | module: { 89 | loaders: [ 90 | { 91 | test: /\.scss$/, 92 | loaders: [ 93 | "style", 94 | "css", 95 | "autoprefixer?browsers=last 2 version", 96 | "sass?outputStyle=expanded" 97 | ] 98 | }, 99 | { 100 | test: /\.css$/, 101 | loaders: [ 102 | "style", 103 | "css" 104 | ] 105 | }, 106 | { 107 | test: /\.jsx$/, 108 | loaders: jsxLoaders 109 | } 110 | ] 111 | }, 112 | 113 | jshint: jshintrc 114 | }; 115 | 116 | if (env.production) { 117 | exports['devtool'] = 'source-map'; 118 | } 119 | 120 | module.exports = exports; 121 | --------------------------------------------------------------------------------