Jumpsuit is still under active development, so any feedback is welcome and appreciated!
A powerful and efficient Javascript framework that helps you build great apps. It is the fastest way to write scalable React/Redux with the least overhead.
- No boilerplate
- Minimalist API
- Built-in support for ES6
- Components powered by React
- State based on Redux
- CLI powered by Browserify
- HSR (Hot State Reloading)
Javascript fatigue is a real thing, especially in the React/Webpack/Redux world where there are so many options to choose from. Jumpsuit brings together the best standards in the industry with the least amount of headache. It should be easy for a developer of any skill level to get started writing great apps without spending hours setting them up.
$ npm install -g jumpsuit-cli
You can also use Jumpsuit with your own build system if you don't like ours. We'll only cry a little bit.
$ npm install --save jumpsuit
# Create a new project
$ jumpsuit new myProjectName
$ cd myProjectName
# Watch for changes
$ jumpsuit watch
# View your project
$ open localhost:8000
Using Jumpsuit in your project? Show it off!
[![built with jumpsuit](https://img.shields.io/badge/built%20with-jumpsuit-3A54AD.svg)](https://github.com/jumpsuit/jumpsuit)
<html>
<head>
<title>Jumpsuit Example</title>
</head>
<body>
<div id="app"></div>
<script src="app.js"></script>
</body>
</html>
// Yep, that's all you have to import
import { Render, State, Component } from 'jumpsuit'
// Create a state with some actions
const CounterState = State('counter', {
// Initial State
initial: { count: 0 },
// Actions
increment (state, payload) {
return { count: ++state.count }
},
decrement (state, payload) {
return { count: --state.count }
},
})
// Create a component
const Counter = Component({
render() {
return (
<div>
<h1>{ this.props.counter.count }</h1>
<button onClick={ () => CounterState.increment() }>Increment</button>
<button onClick={ () => CounterState.decrement() }>Decrement</button>
</div>
)
}
}, (state) => ({
// Subscribe to the counter state (will be available via this.props.counter)
counter: state.counter
}))
// Render your app!
Render({
counter: CounterState
}, <Counter/>)
- Creates a new simple or stateful component.
- Parameters
- config Object
- A react-class config
- stateMappingFn(state)
- An optional function that subscribes the component to the state
- Must return an object, which will be available via the component's
props
- config Object
- Returns
- React Component
- Simple Component
import { Component } from 'jumpsuit' const SayHello = Component({ render() { return ( <div> <button onClick={ this.sayHello }>Say Hello</button> </div> ) }, sayHello(){ console.log('Hello!') }, })
- Stateful Component
import { Component } from 'jumpsuit' const Counter = Component({ render() { return ( <div> Count: { this.props.count } </div> ) } // Subscribe to the count property on the count state }, (state) => { return { count: state.counter.count } })
-
Creates a new state instance
-
Parameters
- name String
- A unique name for this state
- config Object
- initial
- An object or value representing the initial properties for this state
- ...actionName(state, payload)
- Actions (functions) that transform your your current state. They receive the current state as the first parameter and any payload used by the caller as the second. Returns a new partial state object to be merged with the existing state.
- Notes on immutability: Any object returned will be automatically assigned to a new copy of the state object. This means you don't need to use
Object.assign
on the root object of your returns, but you will however need to maintain immutability for nested properties. If you're not sure what this means, see thestate/todos.js
file in the Todo List example for reference.
- initial
- detachedState Boolean
- If set to true, the state will not be attached to the underlying redux instance, and cannot be combined with other states. This basically creates a state machine that you can use how you please.
- name String
-
Returns
- State Reducer function - can be used directly in the Render method, or combined with other states using State.combine. It also has these these prototype methods:
- .getState()
- Returns the current state of the instance
- .dispatch()
- The underlying redux dispatcher
- .getState()
import { State } from 'jumpsuit' const CounterState = State({ initial: { count: 0 }, increment (state, payload) { return { count: ++state.count } }, set (state, payload) { return { count: payload } }, reset (state) { return { count: 0 } } }) // Call the methods normally. No action creators or dispatch required. CounterState.increment() // CounterState.getState() === { count: 1 } CounterState.set(5) // CounterState.getState() === { count: 5 } CounterState.reset() // CounterState.getState() === { count: 0 }
- State Reducer function - can be used directly in the Render method, or combined with other states using State.combine. It also has these these prototype methods:
- Renders your app to
div#app
- Parameters
- state or {states}
- A single state or state combining object. If passing a an object, the property names must use the state name they correspond to.
- component
- The root Jumpsuit/React Component of your app
- Single State
import { Render } from 'jumpsuit' import Counter from './containers/counter' import CounterState from './states/counter' Render(CounterState, <Counter/>)
- Combined State
import { Render } from 'jumpsuit' import App from './containers/app' import CounterState from './states/counter' import TimerState from './states/timer' const state = { counter: CounterState, // CounterState's name is 'counter' timer: TimerState // TimerState's name is 'timer' } Render(state, <App/>)
- state or {states}
- Jumpsuit's built-in router
import { Render, Router, IndexRoute, Route } from 'jumpsuit' Render(state, ( <Router> <IndexRoute component={ Home }/> <Route path="/counter" component={ Counter }/> </Router> ))
- Renders a component at the specified route
import { Render, Router, Route } from 'jumpsuit' import Counter from './components/counter' Render(state, ( <Router> <Route path="/counter" component={ Counter }/> </Router> ))
- Renders a component at the index route of your app
import { Render, Router, Route } from 'jumpsuit' import Home from './components/home' Render(state, ( <Router> <IndexRoute component={ Home }/> </Router> ))
- A method for registering middleware into the underlying redux instance
import { Render, Middleware } from 'jumpsuit' const myMiddleware = store => next => action => { let result = next(action) return result } Middleware(myMiddleware, ...OtherMiddleWares) Render(state, <App/>)
Usage:
jumpsuit <command> [options]
Commands:
new [dir] [example] start a new project at the specified
directory using an optional example template
init [example] start a new project in the current directory
using an optional example template
watch build initial app and wait for file changes
build create a production-ready version of app
serve run the static server
Options:
-p, --port specify the port you want to run on
-h, --host specify the host you want to run on
-v, --version show jumpsuit version number
An optional (but recommended) file at your project's root that can contain any of the following customizations:
// Defaults
module.exports = {
sourceDir: 'src', // Where source files are located
outputDir: 'dist', // Where your built files will be placed
assetsDir: 'assets', // Relative to your sourceDir, everything in here is placed in the root of your outputDir directory.
assetsIgnoreExtensions: [], // If you don't want any files in your assets to copy over, ignore them here. eg ['*.scss']
entry: 'app.js', // Jumpsuit starts building here, relative to your sourceDir
prodSourceMaps: true, // Whether we should output sourcemaps in your production builds
hsr: {
maxAge: 1000, // Max age for Hot State Replacement
shouldCatchErrors: true // Should Hot State replacement catch errors?
},
server: {
host: 'localhost', // The host we serve on when watching
port: 8000, // The port we serve on when watching
},
// More customizations available for browserify
browserify: {
extensions: ['.js'],
rebundles: [],
transforms: [],
globals: {}
},
// Note: By default style hooks are disabled. Standard css files should be placed in your assetsDir
// Style hooks (see Common CSS Configs for example usage)
styles: {
extensions: [], // Extensions of style files in your srcDir that will be watch and passed to the action below on change
action: null // A debounced function that should return all of your css as a string (supports a promise). Debounced by default by 300ms
}
}
jumpsuit.config.js
var fs = require('fs')
var path = require('path')
var stylus = require('stylus')
var nib = require('nib')
module.exports = {
styles: {
extensions: ['.css', '.styl'],
action: buildStyles
}
}
var stylusEntry = path.resolve('src/app.styl')
function buildStyles(){
return new Promise((resolve, reject) => {
stylus.render(fs.readFileSync(stylusEntry, 'utf8'), {
'include css': true,
'hoist atrules': true,
compress: process.env.NODE_ENV === 'production',
paths: [path.resolve('src')],
use: nib()
}, function(err, css){
if (err) reject(err)
resolve(css)
});
})
}
jumpsuit.config.js
var fs = require('fs')
var path = require('path')
var sass = require('node-sass')
module.exports = {
styles: {
extensions: ['.css', '.scss'],
action: buildStyles
}
}
var sassEntry = path.resolve('src/app.styl')
function buildStyles(){
return new Promise((resolve, reject) => {
sass.render({
file: sassEntry,
outputStyle: 'compressed'
}, function(err, res) {
if (err){
return reject(err)
}
resolve(res.css.toString())
});
})
}
Jason Maurer | Tanner Linsley |
MIT © Jumpsuit