67 | );
68 | }
69 | }
70 |
71 | export default Home;
--------------------------------------------------------------------------------
/webpack.production.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const fs = require('fs');
4 | const ExtractTextPlugin = require("extract-text-webpack-plugin");
5 | const HtmlWebpackPlugin = require('html-webpack-plugin');
6 | const nodeModulesPath = path.resolve(__dirname, 'node_modules');
7 | const buildPath = path.resolve(__dirname, 'build');
8 | const mainPath = path.resolve(__dirname, 'src', 'index.js');
9 |
10 | const pathToReact = path.resolve(nodeModulesPath, 'react/dist/react.min.js');
11 | const pathToReactDom = path.resolve(nodeModulesPath, 'react-dom/dist/react-dom.min.js');
12 | const pathToReactObj = path.resolve(nodeModulesPath, 'react/lib/Object.assign');
13 |
14 | const config = {
15 |
16 | // We change to normal source mapping
17 | devtool: 'source-map',
18 | entry: mainPath,
19 | output: {
20 | path: buildPath,
21 | filename: '/dist/bundle.js'
22 | },
23 | resolve: {
24 | root: path.resolve('./src'),
25 | extensions: ['', '.js'],
26 | alias: {
27 | 'react/lib/Object.assign': pathToReactObj,
28 | 'react': pathToReact,
29 | 'react-dom': pathToReactDom
30 | },
31 | },
32 | module: {
33 | loaders: [{
34 | test: /\.js$/,
35 | loader: 'babel',
36 | exclude: [nodeModulesPath]
37 | }, {
38 | test: /\.(jpe?g|png|gif|svg|ico)$/i,
39 | loaders: [
40 | 'file?hash=sha512&digest=hex&name=dist/images/[name]-[hash].[ext]',
41 | 'image-webpack?bypassOnDebug&optimizationLevel=7&interlaced=false'
42 | ]
43 | }, {
44 | test: /\.css$/,
45 | loader: ExtractTextPlugin.extract("style-loader", "css-loader")
46 | }],
47 | noParse: [pathToReact]
48 | },
49 | plugins: [
50 | new ExtractTextPlugin('/dist/styles.css', {
51 | allChunks: true
52 | }),
53 | new HtmlWebpackPlugin({
54 | templateContent: function () {
55 | var htmlTemplate = path.join(__dirname, 'src', 'index.html');
56 | var template = fs.readFileSync(htmlTemplate, 'utf8');
57 | return template.replace('', '');
58 | },
59 | hash: true,
60 | filename: 'index.html',
61 | inject: 'body' // Inject all scripts into the body
62 | }),
63 | new webpack.DefinePlugin({
64 | 'process.env': {
65 | 'NODE_ENV': '"production"'
66 | }
67 | })
68 | ]
69 | };
70 |
71 | module.exports = config;
72 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const app = express();
3 | const path = require('path');
4 | const httpProxy = require('http-proxy');
5 |
6 | const isProduction = process.env.NODE_ENV === 'production';
7 | const port = isProduction ? process.env.PORT || 4000 : 3000;
8 | const publicPath = isProduction ? path.resolve(__dirname, 'build') : path.resolve(__dirname, 'src');
9 |
10 | app.set("view options", {layout: false});
11 | app.use(express.static(publicPath));
12 |
13 | // We only want to run the workflow when not in production
14 | if (!isProduction) {
15 |
16 | const Webpack = require('webpack');
17 | const WebpackDevServer = require('webpack-dev-server');
18 | const webpackConfig = require('./webpack.config.js');
19 | const proxy = httpProxy.createProxyServer();
20 |
21 | // First we fire up Webpack an pass in the configuration we
22 | // created
23 | var bundleStart = null;
24 | const compiler = Webpack(webpackConfig);
25 |
26 | // We give notice in the terminal when it starts bundling and
27 | // set the time it started
28 | compiler.plugin('compile', function() {
29 | console.log('Bundling...');
30 | bundleStart = Date.now();
31 | });
32 |
33 | // We also give notice when it is done compiling, including the
34 | // time it took. Nice to have
35 | compiler.plugin('done', function() {
36 | console.log('Bundled in ' + (Date.now() - bundleStart) + 'ms!');
37 | });
38 |
39 | const bundler = new WebpackDevServer(compiler, {
40 |
41 | // We need to tell Webpack to serve our bundled application
42 | // from the build path. When proxying:
43 | // http://localhost:3000/build -> http://localhost:8080/build
44 | publicPath: '/build/',
45 |
46 | // Configure hot replacement
47 | hot: true,
48 |
49 | historyApiFallback: true,
50 |
51 | // The rest is terminal configurations
52 | quiet: false,
53 | noInfo: true,
54 | stats: {
55 | colors: true
56 | }
57 | });
58 |
59 | // We fire up the development server and give notice in the terminal
60 | // that we are starting the initial bundle
61 | bundler.listen(8080, 'localhost', function () {
62 | console.log('Bundling project, please wait...');
63 | });
64 |
65 |
66 | // Any requests to localhost:3000/build is proxied
67 | // to webpack-dev-server
68 | app.all('/build/*', function (req, res) {
69 | proxy.web(req, res, {
70 | target: 'http://localhost:8080'
71 | });
72 | });
73 |
74 | // It is important to catch any errors from the proxy or the
75 | // server will crash. An example of this is connecting to the
76 | // server when webpack is bundling
77 | proxy.on('error', function (e) {
78 | console.log('Could not connect to proxy, please try again...');
79 | });
80 | }
81 |
82 | app.all('*', function (req, res) {
83 | res.sendFile(`${publicPath}/index.html`);
84 | });
85 |
86 | app.listen(port, function () {
87 | console.log('Server running on port ' + port);
88 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Webpack + ReactJS
2 |
3 | This repository is a basic example of a ReactJS application, built with [Webpack](https://webpack.github.io) and [ReactJS](https://facebook.github.io/react/index.html). It's aim is to be simple & highly configurable, with no crazy opinionated structure. This is not an isomorphic structure, it works purely on the client side with a simple [Express](http://expressjs.com) based server running on the backend (for development, or production if needed).
4 |
5 | ## Setup
6 |
7 | 1. Clone the repository.
8 | 2. Install the project dependencies: `npm install`.
9 | 3. Run the development setup: `npm run dev`.
10 | 4. View the application on `http://localhost:3000`.
11 |
12 | You'll see that a very basic application is in place, which allows you to navigate to another route and add text, which saves to local storage.
13 |
14 | ## How it works
15 |
16 | The project is built using ES6/7, over ES5... But don't worry, Webpack has been setup with Babel 6 to handle this for you.
17 |
18 | The files you'll mostly be concerned with are located within `./src`. Here you'll see a number of files:
19 |
20 | #### index.html
21 |
22 | This is the base entry point of your application, for both development and production. Change this to your will, but make sure you keep the `root` div, and the script which is importing your entire React application.
23 |
24 | #### index.js
25 |
26 | This is the base JavaScript entry point of your application. Here, we use [React Router](https://github.com/rackt/react-router) to load in our components/routes.
27 |
28 | #### routes.js
29 |
30 | Here we export our React Router routes config. A helper has been created called `GenerateRoute` (see below) which allows us to quickly pass in route names and the route component, along with any child routes.
31 |
32 | The `GenerateRoute` function takes arguments, as an object:
33 | - index (boolean): Whether the route is an [IndexRoute](https://github.com/rackt/react-router/blob/master/docs/guides/basics/IndexRoutes.md). Used when you can have multiple children of a route.
34 | - key (string): Required when `index` is true, used to give a unique identifier to the IndexRoute.
35 | - paths (array): An array of strings, which are your routing paths. For params, use `:`, e.g. `/user/:userId`, which can then be accessed via the component props, e.g. `this.props.params.userId`.
36 | - component (component): A ReactJS component.
37 | - children (array): An array of `GenerateRoute` functions, but the routes will be generated as sub-routes of the parent.
38 |
39 | ```javascript
40 | { GenerateRoute({
41 | paths: ['/profile', '/account'],
42 | component: require('./pages/account/Index'),
43 | children: [
44 | GenerateRoute({
45 | key: 'account-index',
46 | index: true,
47 | component: require('./pages/account/Overview')
48 | }),
49 | GenerateRoute({
50 | paths: ['billing'],
51 | component: require('./pages/account/Billing')
52 | })
53 | ]
54 | }) }
55 | ```
56 |
57 | ### Managing State (Flux)
58 |
59 | The suggested way of managing your applications state is [Flux](https://facebook.github.io/flux). Since Flux is pretty confusing, this application uses a Flux wrapper library called [Alt](http://alt.js.org),
60 | which makes Flux much simpler to understand and work with.
61 |
62 | Basically Alt requires `Actions` to send information (in our example text from the input box) into a specific method in our `Store`. The Store updates it's state, which triggers an event.
63 | Any components listening for a store event (e.g. `UserStore.listen(this.someMethod)`) will trigger the function. In our case updating the array of users on the components state.
64 |
65 | There's not much point in explaining more, visit the [documentation](http://alt.js.org/guide) for more information on Alt.
66 |
67 | > For users wanting [Redux](https://github.com/rackt/redux), it should be pretty straight forward to implement yourselves.
68 |
69 | ### Images/CSS
70 |
71 | Any images should be placed within the `src/assets/images` directory, and required so Webpack can minify and cache bust them for you.
72 |
73 | There's a handy `` component within `src/components/Image` which handles this for you, used like so:
74 |
75 | ```javascript
76 | import { Image } from 'components';
77 |
78 | ...
79 |
80 |
81 | ```
82 |
83 | Any CSS is loaded in using a `css-loader`. A good place to include it in your project would be in the `src/index.js` file, for example:
84 |
85 | ```javascript
86 | import 'bootstrap/css/bootstrap.css';
87 | ```
88 |
89 | ### Development vs Production
90 |
91 | Development: `npm run dev`.
92 |
93 | In development, component updates are changed on the fly (known as hot-reloading) using websockets. All files are stored 'in memory' too.
94 |
95 | Production: `npm run prod`.
96 |
97 | In production, Webpack bundles your files into a single file, compressed and without hot-reloading. The contents of which are within `build`. The bundle name is hashed on each build (if something has changed),
98 | to stop browsers caching the bundle even if it's been changed. This also applies for your images.
99 |
100 | The bundled, minified production ReactJS script will also be used, which does not produce any errors.
101 |
102 | ## Misc
103 |
104 | #### Changing ports
105 |
106 | Simply change the numbers in `server.js`, or with production add `PORT=1234` as an environment variable.
107 |
108 | #### LESS/SASS
109 |
110 | These aren't handled by default, you'll need to configure Webpack yourselves using a loader, e.g. [less-loader](https://github.com/webpack/less-loader).
111 |
112 | #### Environment Detection
113 |
114 | If you need to detect the working environment within your code, simply use the process as you would in a Node application:
115 |
116 | ```
117 | if (process.env.NODE_ENV === 'development')
118 | // Developing
119 | if (process.env.NODE_ENV === 'production')
120 | // Production build
121 | ```
122 |
--------------------------------------------------------------------------------