Files: c18fa66a9374d3899a43338d27c66f5d82dabd78 / README.md
business-stack
work in progress
real-world, production-quality stack for Craftworks
resources
- thinking in react
- simplest redux example
- smart and dumb components
- css modules
- mixins are dead, long live higher-order components
- react on es6+
- how to use classes and sleep at night
- common react mistakes: unneeded state
- promise cookbook
- approaches to testing react components
- unit testing react components without a dom
stack
- node version manager:
nvm
- when in this directory run
nvm use
which will use the version ofnode
specified in our .nvmrc.
- when in this directory run
- package manager:
npm@3
- install with
npm install -g npm@3
- to install a package, run
npm install --save my-favorite-package
- install with
- task runner: npm scripts
- browser bundler: browserify
- es6/jsx transpiler: babel (babel-require-hook and babelify)
- modular css: css modules (css-modules-require-hook and cssify)
- bulk require: bulk-require and bulkify
- configuration: simple-rc and evalify
- utility functions: lodash
- data model: tcomb
- database: knex
- api service: feathers-knex
- api validator: feathers-tcomb
- TODO api authentication: feathers-authentication
- api transport: feathers-rest
- client transport: isomorphic-fetch
- client: feathers-client
- render action { creators, reducers }: feathers-action
- render action store: redux
- render getters: reselect
- render router: redux-router
- render views: react
- render forms: tcomb-form
- test specs: ava
- test features: cucumber
- test browser: zombie
- TODO generators: plop
scripts
install
npm install
start
dev
starts development environment
npm run dev
prod
starts production environment
npm run prod
test
runs tests
npm test
test:spec
runs ava tests
can optionally take a glob
npm run test:spec -- './app/todos/**/*.spec.js'
default glob is ./app/**/*.spec.js
test:feature
runs cucumber tests
can optionally take a glob
npm run test:feature -- './features/todo.feature`
default glob is ./features/**/*.feature
lint
checks for standard style
can optionally take a glob
npm run lint -- './app/todos/**/*.js'
default glob is ./**/*.js
ignoring node_modules
format
converts to standard if possible
can optionally take a glob
npm run format -- './app/**/*.js'
default glob is ./**/*.js
ignoring node_modules
database
pg
to run a local postgres db with docker
installed,
- to install db, run
npm run pg:pull
- to create db, run
npm run pg:run
- to start db, run
npm run pg:start
- to stop db, run
npm run pg:stop
- to remove db, run
npm run pg:rm
- to show db logs, run
npm run pg:logs
knex
run any knex
command with npm run knex -- [command] [args]
for example, run latest sql migrations with
npm run knex -- migrate:latest
and generate new sql migration with
npm run knex -- migrate:make add_some_columns_and_stuff
directory structure
/config/
/config/index.js
/config/${ NODE_ENV }.js
/app/${ module }/
- symlink
/app
to/node_modules/app
- only do relative requires (
require('./models')
) if within module - otherwise, always require top-down (
require('app/things/models')
).
- symlink
/feature/
/feature/support/world.js
/feature/support/hooks.js
/feature/step_definitions/steps.js
/feature/smoke.feature
- spec tests are any files that end in
.spec.js
app entry points
our server code is run as separate processes, namely:
static
: static file server usinghttp
andecstatic
api
: RESTful HTTP interface to data usingfeathers
andconnect
middlewarerender
: server-side rendering usinghttp
,react-dom/server
,feathers-client
, and more...
our client code for render
is bundled using browserify
, which similar to the server render
process also uses react-dom
, feathers-client
, and more...
each server process has a separate url available to other entry points via the ./config
app modules
in contrast to frameworks like Rails which split our app
into directories for each "type" of file (models, views, controllers), our app
is split into directories for each module, where each module contains the various types of files within that module.
each module directory may contain any of:
index.js
: exports all the below exports from the modulemodels.js
: exportstcomb
modelsconstants.js
: exports constants (such as redux action types)actions.js
: exports redux actions (recommended to usefeathers-action
)reducer.js
: exports redux reducer (recommended to usefeathers-action
)routes.js
: exportsfunction (store) { return <ReactRouter.Route /> }
getters.js
: exportsreselect
getters for use in containers'connect
containers/*.js
: exports smart componentconnect
ed withreact-redux
components/*.js
: exports dumb componentcomponents/*.css
: exports css modules for respective componentservice.js
: exportsfeathers
service (recommended to usefeathers-knex
andfeathers-tcomb
)
FAQ
how do i do relations between models?
implement them in your getters.js
file as selectors.
// app/groups/getter.js
import { createSelector } from 'reselect'
import { getMemberships } from 'app/memberships/getters'
export const getGroups = (state) => state.groups
export const getMembersByGroupId = createSelector (
getGroups,
getMemberships,
(groups, memberships) => {
const isInGroup = (group) => (membership) => {
return group.id === membership.groupId
}
return Object.values(groups).map((group) => {
return Object.values(memberships)
.filter(isInGroup(group))
.map((membership) => membership.memberId)
})
}
)
in the future, we should extract common relations into helper creators.
how to set default props
import React from 'react'
export default class Thing extends React.Component {
static displayName = 'Thing'
static defaultProps = {
isAwesome: true
}
...
}
how to set prop types
import React, { PropTypes } from 'react'
export default class Thing extends React.Component {
static displayName = 'Thing'
static propTypes = {
increment: PropTypes.function
}
...
}
how to bind functions to parent component when passing them down?
import React, { PropTypes } from 'react'
export class Parent extends React.Component {
constructor(props) {
super(props)
this.state = { a: 0 }
}
render () {
return <Child increment={this.increment} />
}
increment = () => {
this.setState({ a: this.state.a + 1 })
}
}
export class Child extends React.Component {
static propTypes = {
increment: PropTypes.function
}
render () {
return <button onClick={this.props.increment}>click me</button>
}
}
known issues
- adding a new file won't always be noticed by
node-dev
orwatchify
due to usage ofbulk-require
). potential fix is to usechokidar-cli
and some transform to watch for new files and re-run the script command
Built with git-ssb-web