├── 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 | [](https://travis-ci.org/srn/react-webpack-boilerplate) [](https://coveralls.io/r/srn/react-webpack-boilerplate) [](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 | | Name: |
39 | {this.state.user.name} |
40 |
41 |
42 | | Email: |
43 | {this.state.user.mail[0]} |
44 |
45 |
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 |
--------------------------------------------------------------------------------