Skip to content
This repository has been archived by the owner on Sep 11, 2018. It is now read-only.

Commit

Permalink
refactor(server): koa -> express, deprecate use of babel on server
Browse files Browse the repository at this point in the history
  • Loading branch information
David Zukowski committed Sep 12, 2016
1 parent 15f703f commit f00ebd3
Show file tree
Hide file tree
Showing 17 changed files with 119 additions and 310 deletions.
10 changes: 0 additions & 10 deletions .babelrc

This file was deleted.

108 changes: 14 additions & 94 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
[![devDependency Status](https://david-dm.org/davezuko/react-redux-starter-kit/dev-status.svg)](https://david-dm.org/davezuko/react-redux-starter-kit#info=devDependencies)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)

This starter kit is designed to get you up and running with a bunch of awesome new front-end technologies, all on top of a configurable, feature-rich webpack build system that's already setup to provide hot reloading, CSS modules with Sass support, unit testing, code coverage reports, bundle splitting, and a whole lot more.
This starter kit is designed to get you up and running with a bunch of awesome new front-end technologies, all on top of a configurable, feature-rich webpack build system that's already setup to provide hot reloading, CSS preprocessing with Sass, unit testing, code coverage reports, bundle splitting, and more.

The primary goal of this project is to remain as **unopinionated** as possible. Its purpose is not to dictate your project structure or to demonstrate a complete sample application, but to provide a set of tools intended to make front-end development robust, easy, and, most importantly, fun. Check out the full feature list below!

Expand Down Expand Up @@ -40,7 +40,7 @@ Finally, This project wouldn't be possible without the help of our many contribu
* [react-router-redux](https://github.com/rackt/react-router-redux)
* [webpack](https://github.com/webpack/webpack)
* [babel](https://github.com/babel/babel)
* [koa](https://github.com/koajs/koa)
* [express](https://github.com/expressjs/express)
* [karma](https://github.com/karma-runner/karma)
* [eslint](http://eslint.org)

Expand All @@ -54,38 +54,14 @@ After confirming that your development environment meets the specified [requirem

### Install from source

First, clone or download:
First, clone the project:

```bash
$ git clone https://github.com/davezuko/react-redux-starter-kit.git
// or
$ wget -O react-redux-starter-kit.zip https://github.com/davezuko/react-redux-starter-kit/archive/master.zip
$ unzip react-redux-starter-kit.zip
```

Then, rename to your project name and change into the directory:

```bash
$ mv react-redux-starter-kit <my-project-name>
$ cd <my-project-name>
```

### Alternatively, install via `redux-cli`

If not already installed (globally):

```bash
$ npm i redux-cli -g
```

Then, create a new project:

```bash
$ redux new <my-project-name>
$ git clone https://github.com/davezuko/react-redux-starter-kit.git <my-project-name>
$ cd <my-project-name>
```

### Install dependencies, and check to see it works
Then install dependencies and check to see it works

```bash
$ npm install # Install project dependencies
Expand All @@ -102,7 +78,6 @@ While developing, you will probably rely mostly on `npm start`; however, there a
|`start`|Serves your app at `localhost:3000`. HMR will be enabled in development.|
|`compile`|Compiles the application to disk (`~/dist` by default).|
|`dev`|Same as `npm start`, but enables nodemon for the server as well.|
|`dev:no-debug`|Same as `npm run dev` but disables devtool instrumentation.|
|`test`|Runs unit tests with Karma and generates a coverage report.|
|`test:dev`|Runs Karma and watches for changes to re-run tests; does not generate coverage reports.|
|`deploy`|Runs linter, tests, and then, on success, compiles your application to disk.|
Expand All @@ -122,13 +97,13 @@ The application structure presented in this boilerplate is **fractal**, where fu
├── build # All build-related configuration
│ └── webpack # Environment-specific configuration files for webpack
├── config # Project configuration settings
├── server # Koa application (uses webpack middleware)
├── server # Express application that provides webpack middleware
│ └── main.js # Server application entry point
├── src # Application source code
│ ├── index.html # Main HTML page container for app
│ ├── main.js # Application bootstrap and rendering
│ ├── components # Reusable Presentational Components
│ ├── containers # Reusable Container Components
│ ├── components # Global Reusable Presentational Components
│ ├── containers # Global Reusable Container Components
│ ├── layouts # Components that dictate major page structure
│ ├── redux # "Ducks" location...
│ │ └── modules # reducer, action, creators not part of a route
Expand Down Expand Up @@ -164,70 +139,17 @@ npm i --save-dev redux-devtools redux-devtools-log-monitor redux-devtools-dock-m

Then follow the [manual integration walkthrough](https://github.com/gaearon/redux-devtools/blob/master/docs/Walkthrough.md).

#### `redux-cli`

```bash
npm install redux-cli --save-dev
```

### Routing
We use `react-router` [route definitions](https://github.com/reactjs/react-router/blob/master/docs/API.md#plainroute) (`<route>/index.js`) to define units of logic within our application. See the [application structure](#application-structure) section for more information.

## Testing
To add a unit test, simply create a `.spec.js` file anywhere in `~/tests`. Karma will pick up on these files automatically, and Mocha and Chai will be available within your test without the need to import them. If you are using `redux-cli`, test files should automatically be generated when you create a component or redux module.

Coverage reports will be compiled to `~/coverage` by default. If you wish to change what reporters are used and where reports are compiled, you can do so by modifying `coverage_reporters` in `~/config/index.js`.
To add a unit test, simply create a `.spec.js` file anywhere in `~/tests`. Karma will pick up on these files automatically, and Mocha and Chai will be available within your test without the need to import them. Coverage reports will be compiled to `~/coverage` by default. If you wish to change what reporters are used and where reports are compiled, you can do so by modifying `coverage_reporters` in `~/config/index.js`.

## Deployment
Out of the box, this starter kit is deployable by serving the `~/dist` folder generated by `npm run deploy` (make sure to specify your target `NODE_ENV` as well). This project does not concern itself with the details of server-side rendering or API structure, since that demands an opinionated structure that makes it difficult to extend the starter kit. However, if you do need help with more advanced deployment strategies, here are a few tips:

### Static Deployments
If you are serving the application via a web server such as nginx, make sure to direct incoming routes to the root `~/dist/index.html` file and let react-router take care of the rest. If you are unsure of how to do this, you might find [this documentation](https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#configuring-your-server) helpful. The Koa server that comes with the starter kit is able to be extended to serve as an API or whatever else you need, but that's entirely up to you.

### Heroku

Heroku has `nodejs buildpack` script that does the following when you deploy your app to Heroku.
1. Find `packages.json` in the root directory.
2. Install `nodejs` and `npm` packages.
3. Run `npm postinstall script`
4. Run `npm start`

Therefore, you need to modify `package.json` before deploying to Heroku. Make the following changes in the `scripts` section of `package.json`.

```
...
"start": "better-npm-run start:prod",
"serve": "better-npm-run start",
"postinstall": "npm run deploy:prod",
"betterScripts": {
...
"start:prod": {
"command": "babel-node bin/server",
"env": {
"NODE_ENV": "production"
}
}
...
},
```

It's also important to tell Heroku to install all `devDependencies` to successfully compile your app on Heroku's environment. Run the following in your terminal.

```bash
$ heroku config:set NPM_CONFIG_PRODUCTION=false
```

With this setup, you will install all the necessray packages, build your app, and start the webserver (e.g. koa) everytime you push your app to Heroku. Try to deploy to Heroku by running the following commands.

```bash
$ git add .
$ git commit -m 'My awesome commit'
$ git push heroku master
```

If you fail to deploy for an unknown reason, try [this helpful comment](https://github.com/davezuko/react-redux-starter-kit/issues/730#issuecomment-213997120) by [DonHansDampf](https://github.com/DonHansDampf) addressing Heroku deployments.

Have more questions? Feel free to submit an issue or join the Gitter chat!
If you are serving the application via a web server such as nginx, make sure to direct incoming routes to the root `~/dist/index.html` file and let react-router take care of the rest. If you are unsure of how to do this, you might find [this documentation](https://github.com/reactjs/react-router/blob/master/docs/guides/Histories.md#configuring-your-server) helpful. The Express server that comes with the starter kit is able to be extended to serve as an API or whatever else you need, but that's entirely up to you.

## Build System

Expand All @@ -241,13 +163,11 @@ If you need environment-specific overrides (useful for dynamically setting API e
|---|-----------|
|`dir_src`|application source code base path|
|`dir_dist`|path to build compiled application to|
|`server_host`|hostname for the Koa server|
|`server_port`|port for the Koa server|
|`compiler_css_modules`|whether or not to enable CSS modules|
|`server_host`|hostname for the Express server|
|`server_port`|port for the Express server|
|`compiler_devtool`|what type of source-maps to generate (set to `false`/`null` to disable)|
|`compiler_vendor`|packages to separate into to the vendor bundle|


### Root Resolve
Webpack is configured to make use of [resolve.root](http://webpack.github.io/docs/configuration.html#resolve-root), which lets you import local packages as if you were traversing from the root of your `~/src` directory. Here's an example:

Expand Down Expand Up @@ -275,11 +195,11 @@ These are global variables available to you anywhere in your source code. If you

### Styles

Both `.scss` and `.css` file extensions are supported out of the box and are configured to use [CSS Modules](https://github.com/css-modules/css-modules). After being imported, styles will be processed with [PostCSS](https://github.com/postcss/postcss) for minification and autoprefixing, and will be extracted to a `.css` file during production builds.
Both `.scss` and `.css` file extensions are supported out of the box. After being imported, styles will be processed with [PostCSS](https://github.com/postcss/postcss) for minification and autoprefixing, and will be extracted to a `.css` file during production builds.

### Server

This starter kit comes packaged with an Koa server. It's important to note that the sole purpose of this server is to provide `webpack-dev-middleware` and `webpack-hot-middleware` for hot module replacement. Using a custom Koa app in place of [webpack-dev-server](https://github.com/webpack/webpack-dev-server) makes it easier to extend the starter kit to include functionality such as API's, universal rendering, and more -- all without bloating the base boilerplate.
This starter kit comes packaged with an Express server. It's important to note that the sole purpose of this server is to provide `webpack-dev-middleware` and `webpack-hot-middleware` for hot module replacement. Using a custom Express app in place of [webpack-dev-server](https://github.com/webpack/webpack-dev-server) makes it easier to extend the starter kit to include functionality such as API's, universal rendering, and more -- all without bloating the base boilerplate.

### Production Optimization

Expand Down
45 changes: 25 additions & 20 deletions bin/compile.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import fs from 'fs-extra'
import _debug from 'debug'
import webpackCompiler from '../build/webpack-compiler'
import webpackConfig from '../build/webpack.config'
import config from '../config'
const fs = require('fs-extra')
const debug = require('debug')('app:bin:compile')
const webpackCompiler = require('../build/webpack-compiler')
const webpackConfig = require('../build/webpack.config')
const config = require('../config')

const debug = _debug('app:bin:compile')
const paths = config.utils_paths

;(async function () {
try {
debug('Run compiler')
const stats = await webpackCompiler(webpackConfig)
if (stats.warnings.length && config.compiler_fail_on_warning) {
debug('Config set to fail on warning, exiting with status code "1".')
const compile = () => {
debug('Starting compiler.')
return Promise.resolve()
.then(() => webpackCompiler(webpackConfig))
.then(stats => {
if (stats.warnings.length && config.compiler_fail_on_warning) {
throw new Error('Config set to fail on warning, exiting with status code "1".')
}
debug('Copying static assets to dist folder.')
fs.copySync(paths.client('static'), paths.dist())
})
.then(() => {
debug('Compilation completed successfully.')
})
.catch((err) => {
debug('Compiler encountered an error.', err)
process.exit(1)
}
debug('Copy static assets to dist folder.')
fs.copySync(paths.client('static'), paths.dist())
} catch (e) {
debug('Compiler encountered an error.', e)
process.exit(1)
}
})()
})
}

compile()
8 changes: 3 additions & 5 deletions bin/server.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import config from '../config'
import server from '../server/main'
import _debug from 'debug'
const config = require('../config')
const server = require('../server/main')
const debug = require('debug')('app:bin:server')

const debug = _debug('app:bin:server')
const port = config.server_port
const host = config.server_host

server.listen(port)
debug(`Server is now running at http://${host}:${port}.`)
debug(`Server accessible via localhost:${port} if you are using the project defaults.`)
28 changes: 11 additions & 17 deletions build/karma.conf.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { argv } from 'yargs'
import config from '../config'
import webpackConfig from './webpack.config'
import _debug from 'debug'

const debug = _debug('app:karma')
debug('Create configuration.')
const { argv } = require('yargs')
const config = require('../config')
const webpackConfig = require('./webpack.config')
const debug = require('debug')('app:karma')

debug('Creating configuration.')
const karmaConfig = {
basePath : '../', // project root in relation to bin/karma.js
files : [
Expand All @@ -25,13 +23,11 @@ const karmaConfig = {
browsers : ['PhantomJS'],
webpack : {
devtool : 'cheap-module-source-map',
resolve : {
...webpackConfig.resolve,
alias : {
...webpackConfig.resolve.alias,
resolve : Object.assign({}, webpackConfig.resolve, {
alias : Object.assign({}, webpackConfig.resolve.alias, {
sinon : 'sinon/pkg/sinon.js'
}
},
})
}),
plugins : webpackConfig.plugins,
module : {
noParse : [
Expand All @@ -46,12 +42,11 @@ const karmaConfig = {
},
// Enzyme fix, see:
// https://github.com/airbnb/enzyme/issues/47
externals : {
...webpackConfig.externals,
externals : Object.assign({}, webpackConfig.externals, {
'react/addons' : true,
'react/lib/ExecutionEnvironment' : true,
'react/lib/ReactContext' : 'window'
},
}),
sassLoader : webpackConfig.sassLoader
},
webpackMiddleware : {
Expand All @@ -72,5 +67,4 @@ if (config.globals.__COVERAGE__) {
}]
}

// cannot use `export default` because of Karma.
module.exports = (cfg) => cfg.set(karmaConfig)
13 changes: 7 additions & 6 deletions build/webpack-compiler.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import webpack from 'webpack'
import _debug from 'debug'
import config from '../config'
const webpack = require('webpack')
const debug = require('debug')('app:build:webpack-compiler')
const config = require('../config')

const debug = _debug('app:build:webpack-compiler')
const DEFAULT_STATS_FORMAT = config.compiler_stats
function webpackCompiler (webpackConfig, statsFormat) {
statsFormat = statsFormat || config.compiler_stats

export default function webpackCompiler (webpackConfig, statsFormat = DEFAULT_STATS_FORMAT) {
return new Promise((resolve, reject) => {
const compiler = webpack(webpackConfig)

Expand Down Expand Up @@ -33,3 +32,5 @@ export default function webpackCompiler (webpackConfig, statsFormat = DEFAULT_ST
})
})
}

module.exports = webpackCompiler
Loading

11 comments on commit f00ebd3

@bhj
Copy link
Contributor

@bhj bhj commented on f00ebd3 Sep 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davezuko Curious, did you guys run into issues with Koa2+Babel in production? All the recent simplification looks great btw.

@dvdzkwsk
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhj no issues with Koa, and it's by far the saddest loss with the simplifications. The backend team at TA still uses Express but started ripping babel out and it's convinced me to do the same for personal projects, which means dropping Koa just because I'm not much of a fan of the generator async hackery despite its brilliance. Ultimately, the server in this project is just there for webpack in development and a rudimentary backend API. Anything bigger than that it's totally feasible to use whatever you want.

Babel did cause various headaches, if not outright issues, and at a minimum just made our lives more difficult (accurate code coverage reports, breaking changes/dependencies, inability to just start coding without any setup). It's a great idea, but I find that I'm distancing myself from complex build systems with each passing day.

And thanks, I appreciate it. I started more seriously questioning why all this stuff was in the project other than that it's part of the ecosystem and was requested at one point in time, so hopefully we can make things a little more manageable.

@bhj
Copy link
Contributor

@bhj bhj commented on f00ebd3 Sep 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davezuko Thanks and 100% support the philosophy. Maybe in a year or two we can finally use async/await properly!

@jonricaurte
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhj Just a heads up that async await will be added in the next month or so @davezuko
nodejs/promises#4 (comment)
https://www.chromestatus.com/feature/5643236399906816

Might want to revert back to koa in December

@bhj
Copy link
Contributor

@bhj bhj commented on f00ebd3 Oct 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonricaurte I've been using the Node 7 betas with --harmony_async_await for a few days and so far no problems! Will be interesting to see how long it stays behind a flag.

@jonricaurte
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhj Hopefully not for long! :)

@dvdzkwsk
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the heads up @jonricaurte, would love to use Koa natively.

@joshuaandrewhoffman
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving a little feedback since my experience has been different than what's expressed in this thread.

First, thank you for this project. It has helped me a TON in getting familiar with this stack; I wouldn't be where I am without this work. I recently made the jump over to Express by pulling in latest. I loved the gains I got in my learning because it's so much easier to find examples, tutorials etc for Express compared to Koa.

I'm bummed about losing the es6 stuff though. That was what I focused on learning first when I started modifying this project, and I've written quite a bit of code that leverages it. Especially bummed that the commit that does Koa->Express is the same as the one that nixes babel, as I now have to comb through this line by line to try to figure out how to get my babel functionality back (a piece of the stack which I've not yet devoted any time to). I'll solve this and move on with my project, but it was an unexpected hiccup and took a bit of the wind out of my sails after being so high on my progress gains from moving to Express.

No expectations here, just a little honest feedback for you guys. Again, I'm super thankful for this project and owe you a ton for how much I've learned. Thanks again for your hard work!

@dvdzkwsk
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the feedback @joshuaandrewhoffman, it's always nice to hear others' perspectives. I understand it's a super controversial decision, but hopefully one that at least some will understand (or at least hate less) over time. The latest Node versions do support a lot of ES6 features, so I think you'll be surprised at what exactly you need Babel for. It is painful to lose those unsupported features, as some of them are striking (CommonJS vs ES6 modules being the most obvious) but you also gain a lot by having code that can be run natively without having to deal with the headaches caused by transpilation (portability, debugging, etc.).

Trust me, I wish as much as you that I could write the same exact type of code on the client as the server, but that's one of the flaws of our ecosystem. I tried for so long to use whatever tools made sense to lessen this gap, but the end result never felt right. With the backend at TechnologyAdvice completely dropping Babel from their stack, I've kind of had a heart-to-heart with the state of tooling. Babel is awesome, and great, and makes writing code for the browser not an absolutely soul killing experience, but if I can get away without using it I will.

Hopefully that sheds some pre-coffee probably-super-rambly light on the decision. Best of luck with everything.

@joshuaandrewhoffman
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davezuko thank you for taking the time to respond, your reply definitely helped me understand your decision. You're spot on that modules was the first thing I stumbled into. I've decided to give things a go the way you're advocating for and see how it works out; so far so good!

I really do want to stress again how appreciative I am of this project. I absolutely would not be where I am in my understanding of this stack without your effort, so thank you!

@jflayhart
Copy link

@jflayhart jflayhart commented on f00ebd3 Dec 1, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good move for this project, simplifying, but a word of warning to those (like me) that depend on the dependency babel-polyfill for non-es6 browsers like IE11, etc. I use axios and IE throws Unhandled promise rejection ReferenceError: 'Promise' is undefined after this change. So I had to add babel-polyfill back as a dependency and vendor file.

Please sign in to comment.