
eva.js is a complete solution to
building modern webs with Vue.js.
// model
app.model()
// router
app.router()
// bootstrap
app.start()Play with the JSBin example or the simple webpack example π
Table of Contents
Feel free to add your project here!
- Battery included, Vue 2 and its friends (Vuex & Vue-Router)
- Small APIs, just Vue and the official plugins you already play well with!
- Support server-side rendering, of course!
- Inspired by the choo framework which is inpired by the elm architecture
$ npm install --save eva.jsIn case you may want to use it directly in browser instead, view https://unpkg.com/eva.js/dist/, and add:
<!-- global variable `EVA` is available as a constructor(class) -->
<!-- note that, you should use new EVA.default() to create app instance in browser -->
<script src="/path/to/eva.js"></script>If you use the commonjs version and wanna include the runtime for vue template, follow the official guide:
import EVA from 'eva.js'
// Create app instance
const app = new EVA()
// A counter model
app.model({
state: {count: 0},
mutations: {
INCREMENT(state) {state.count++}
}
})
// A home view
const Home = {
computed: {
count() {
return this.$store.state.count
}
},
render(h) {
return (
<div>
<h1>Home</h1>
<button on-click={() => this.$store.commit('INCREMENT')}>
{this.count}
</button>
</div>
)
}
}
// Apply views to relevant routes
// route(path, view, child_routes)
app.router(route => [
route('/', Home)
])
// Start app
const App = {
render(h) {
return (
<div id="app">
<router-view></router-view>
</div>
)
}
}
app.start(App, '#app')
// equal to
// app.start(App)
// app.mount('#app')A model contains it's initial state and the methods you use to update its state, in fact, it's a typical Vuex module too.
// An app instance only have at most one top-level model
app.model({
state: {count: 0},
mutations: {
INCREMENT(state) {state.count++}
}
})// An app could have multiple named models
app.model({
name: 'user',
state: {login: false},
mutations: {
LOG_IN(state) {state.login = true}
}
})By default only state are registered locally under provided name, eg state.user.login. But mutations actions getters are still in global namespace, to enforce name for those too, please change name to namespace:
app.model({
namespace: 'user',
state: {login: false},
mutations: {
LOG_IN(state) {state.login = true}
},
actions: {
login({commit}) {
commit('LOG_IN') //=> user/LOG_IN
}
}
})Check out official docs for modules in vuex: http://vuex.vuejs.org/en/modules.html
In most cases using namespaces is beneficial, as having clear boundaries makes it easier to follow logic.
As how you use Vuex^2, you can use its helpers too:
const {mapState, mapActions, mapGetters} = require('eva.js')
// or ES6 modules
import {mapState, mapActions, mapGetters} from 'eva.js'
// of course you can directly import from 'vuex' too
import {mapState, mapActions, mapGetters} from 'vuex'The router could render the component which matches the URL path. It has a route helper for creating an actual route object used in vue-router. routes are passed in as a nested array.
app.router(route => [
route('/', Home),
route('/settings', Settings, [
route('/profile', SettingsProfile),
route('/password', SettingsPassword)
])
])
// use an object as route argument:
route({path: '/', component: Home, /*...*/})
// use an object as router argument:
app.router({
mode: 'history',
routes: []
})The router state is effortlessly synced in vuex store.
A view is a simple Vue component, that easy :)
If you wan to access Vue constructor directly, simply do:
import {Vue} from 'eva.js'
// or without any change
// import Vue from 'vue'
// works too
Vue.use(yourPlugin)You can initialize your app and bootstrap it later:
// ./src/app.js
import EVA from 'eva.js'
const app = new EVA()
app.model() //...
app.router() //...
export default app.start()
// ./src/index.js
import app from './app'
app.mount('#app')
// ./some/other/file.js
import app from './path/to/src/app.js'
app.$router.push('/somewhere')Similar to the official hackernews example:
// ./src/app.js
import EVA from 'eva.js'
import App from './App.vue'
const app = new EVA()
export default app.start(App)
// without selector!
// otherwise it will be mounted to the selectorThen for the server-entry.js:
// ./src/server-entry.js
import app from './app'
export default context => {
// you can access app.$router / app.$store
return Promise.all(operations)
.then(() => {
return app.instance
})
}For client-entry.js:
import app from './app'
app.mount('#app')Some browsers do not have native Promise, like IE, but vuex requires Promise. Thus eva.js provides an lightweight Promise polyfill with promise-polyfill.
import 'eva.js/promise-polyfill'
import EVA from 'eva.js'
// ... your app codeCreate an app instance.
The router mode, can be either hash (default) or history.
Register a model, a.k.a. store module in Vuex. You can omit the name and namespace property to make it top-level.
Register routes.
The same as Vue.use, you can apply any Vue plugin.
Create app instance. Optionally mount App component to a domNode if selector is defined.
If App is not specified, we use a default value:
const defaultApp = {
render(h) {
return <div id="app"><router-view></router-view></div>
}
}If selector is not specified, we won't mount the app instance to dom.
Mounted app instance to dom, must be call after app.start([App]) (without selector argument). Default selector is #app
keep vue-router and vuex store in sync, i.e. keep router state in vuex store.
The method will be called automatically in app.start(), you can also call it manually before app.start() and app.start() won't call it again.
The vuex store instance.
The vue-router instance.
The Vue instance created by app.start(), most likely you will use this in server-side rendering.
# build and watch source files
$ npm run watch
# launch server for simple html example
$ http-server .
# run webpack example
$ npm run webpack
# build for publish to npm
# cjs and umd and compressed umd
$ npm run buildMIT Β© EGOIST