├── .editorconfig ├── .gitignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── README.md ├── config ├── storybook │ ├── addons.js │ ├── config.js │ ├── preview-head.html │ └── webpack.config.js └── webpack │ └── webpack.config.js ├── dist ├── .nojekyll └── index.html ├── package-lock.json ├── package.json └── src ├── components ├── atoms │ ├── base-button │ │ ├── index.vue │ │ └── stories.js │ ├── base-input │ │ ├── index.vue │ │ └── stories.js │ ├── base-text │ │ ├── index.vue │ │ └── stories.js │ ├── base-title │ │ ├── index.vue │ │ └── stories.js │ └── todo-row │ │ ├── index.vue │ │ └── stories.js ├── molecules │ ├── new-todo │ │ ├── index.vue │ │ └── stories.js │ └── todo │ │ ├── index.vue │ │ └── stories.js ├── organisms │ └── todo-list │ │ ├── index.vue │ │ └── stories.js ├── pages │ └── app │ │ └── index.vue └── templates │ └── app-layout │ ├── index.vue │ └── stories.js ├── containers ├── all-todos.js └── new-todo.js ├── index.js └── store ├── index.js └── modules └── todo ├── actionTypes.js ├── actions.js ├── getterTypes.js ├── getters.js ├── index.js ├── mutationTypes.js └── mutations.js /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | 7 | [*.{js,vue}] 8 | indent_size = 2 9 | indent_style = space -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/main.js 3 | storybook-static 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v10.4.0 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | before_deploy: 4 | - npm run build 5 | 6 | deploy: 7 | provider: pages 8 | skip-cleanup: true 9 | local-dir: dist 10 | github-token: $GITHUB_TOKEN 11 | keep-history: true 12 | on: 13 | branch: master 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shota Fuji(pocka) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-container-component-example 2 | 3 | Example repository for Container Component Pattern with Vue.js and Vuex 4 | 5 | - [Live demo](https://pocka.github.io/vue-container-component-example/) 6 | - [storybook(styleguide)](https://vue-container-component-example.netlify.com/) 7 | 8 | ## for developers 9 | 10 | ```sh 11 | # Starts dev server (listen 8080) 12 | npm run dev 13 | 14 | # Starts storybook (listen 9001) 15 | npm run storybook 16 | 17 | # Builds app 18 | npm run build 19 | ``` 20 | -------------------------------------------------------------------------------- /config/storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register' 2 | import '@storybook/addon-actions/register' 3 | -------------------------------------------------------------------------------- /config/storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from '@storybook/vue' 2 | 3 | const req = require.context('../../src/components', true, /\.?stories\.js$/) 4 | 5 | function loadStories() { 6 | req.keys().forEach(file => req(file)) 7 | } 8 | 9 | configure(loadStories, module) 10 | -------------------------------------------------------------------------------- /config/storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /config/storybook/webpack.config.js: -------------------------------------------------------------------------------- 1 | const appConfig = require('../webpack/webpack.config.js') 2 | 3 | const { module: $module, plugins, resolve } = appConfig 4 | 5 | module.exports = { 6 | module: { 7 | ...$module, 8 | rules: $module.rules.filter(rule => !rule.loader.includes('vue-loader')) 9 | }, 10 | plugins, 11 | resolve: { 12 | ...resolve, 13 | alias: { 14 | ...resolve.alias, 15 | vue$: 'vue/dist/vue.esm.js' 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | const VueLoaderPlugin = require('vue-loader/lib/plugin') 4 | 5 | module.exports = { 6 | entry: './src/index.js', 7 | output: { 8 | filename: 'main.js', 9 | path: path.resolve(__dirname, '../../dist') 10 | }, 11 | devServer: { 12 | contentBase: './dist' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.js$/, 18 | exclude: /node_modules/, 19 | loader: 'babel-loader' 20 | }, 21 | { 22 | test: /\.vue$/, 23 | loader: 'vue-loader' 24 | }, 25 | { 26 | test: /\.css$/, 27 | loader: 'style-loader!css-loader?modules=true' 28 | } 29 | ] 30 | }, 31 | plugins: [new VueLoaderPlugin()], 32 | resolve: { 33 | extensions: ['.js', '.vue'], 34 | alias: { 35 | '~': path.resolve(__dirname, '../../src') 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /dist/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pocka/vue-container-component-example/0922b4f77af24c3ec819d9f7663c7a3219c7440e/dist/.nojekyll -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Container Component Pattern in Vue.js 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-container-component-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/pocka/vue-container-component-example.git" 8 | }, 9 | "scripts": { 10 | "dev": "webpack-dev-server --config config/webpack/webpack.config.js --mode=development", 11 | "build": "webpack --config config/webpack/webpack.config.js --mode=production", 12 | "storybook": "start-storybook -p 9001 -c config/storybook", 13 | "build:storybook": "build-storybook -c config/storybook" 14 | }, 15 | "author": "pocka", 16 | "license": "MIT", 17 | "bugs": { 18 | "url": "https://github.com/pocka/vue-container-component-example/issues" 19 | }, 20 | "homepage": "https://github.com/pocka/vue-container-component-example#readme", 21 | "devDependencies": { 22 | "@storybook/addon-actions": "^3.4.7", 23 | "@storybook/addon-knobs": "^3.4.7", 24 | "@storybook/vue": "^4.0.0-alpha.9", 25 | "babel": "^6.23.0", 26 | "babel-core": "^6.26.3", 27 | "babel-loader": "^7.1.4", 28 | "babel-preset-env": "^1.7.0", 29 | "css-loader": "^0.28.11", 30 | "style-loader": "^0.21.0", 31 | "vue-loader": "^15.2.4", 32 | "vue-template-compiler": "^2.5.16", 33 | "webpack": "^4.12.0", 34 | "webpack-cli": "^3.0.3", 35 | "webpack-dev-server": "^3.1.4" 36 | }, 37 | "dependencies": { 38 | "babel-polyfill": "^6.26.0", 39 | "vue": "^2.5.16", 40 | "vuex": "^3.0.1", 41 | "vuex-connect": "^2.1.0" 42 | }, 43 | "prettier": { 44 | "semi": false, 45 | "singleQuote": true 46 | }, 47 | "browserslist": [ 48 | "last 2 versions" 49 | ], 50 | "babel": { 51 | "presets": [ 52 | "babel-preset-env" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/components/atoms/base-button/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 36 | 37 | 85 | -------------------------------------------------------------------------------- /src/components/atoms/base-button/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { action } from '@storybook/addon-actions' 4 | import { withKnobs, text, select, boolean } from '@storybook/addon-knobs' 5 | 6 | import BaseButton from './' 7 | 8 | storiesOf('Atoms/base-button', module) 9 | .addDecorator(withKnobs) 10 | .add('A basic button', () => ({ 11 | components: { BaseButton }, 12 | methods: { 13 | click: action('click') 14 | }, 15 | template: `${text( 20 | 'Text', 21 | 'Button' 22 | )}` 23 | })) 24 | -------------------------------------------------------------------------------- /src/components/atoms/base-input/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 27 | 28 | 55 | -------------------------------------------------------------------------------- /src/components/atoms/base-input/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { action } from '@storybook/addon-actions' 4 | import { withKnobs, text } from '@storybook/addon-knobs' 5 | 6 | import BaseInput from './' 7 | 8 | storiesOf('Atoms/base-input', module) 9 | .addDecorator(withKnobs) 10 | .add('A basic button', () => ({ 11 | components: { BaseInput }, 12 | methods: { 13 | input: action('input') 14 | }, 15 | data() { 16 | return { 17 | value: '' 18 | } 19 | }, 20 | template: `` 24 | })) 25 | -------------------------------------------------------------------------------- /src/components/atoms/base-text/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 17 | -------------------------------------------------------------------------------- /src/components/atoms/base-text/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { withKnobs, text } from '@storybook/addon-knobs' 4 | 5 | import BaseText from './' 6 | 7 | storiesOf('Atoms/base-text', module) 8 | .addDecorator(withKnobs) 9 | .add('A basic text', () => ({ 10 | components: { BaseText }, 11 | template: `${text('Text', 'Text')}` 12 | })) 13 | -------------------------------------------------------------------------------- /src/components/atoms/base-title/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 19 | -------------------------------------------------------------------------------- /src/components/atoms/base-title/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { withKnobs, text } from '@storybook/addon-knobs' 4 | 5 | import BaseTitle from './' 6 | 7 | storiesOf('Atoms/base-title', module) 8 | .addDecorator(withKnobs) 9 | .add('A title', () => ({ 10 | components: { BaseTitle }, 11 | template: `${text('Title', 'Title')}` 12 | })) 13 | -------------------------------------------------------------------------------- /src/components/atoms/todo-row/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/atoms/todo-row/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import TodoRow from './' 4 | 5 | storiesOf('Atoms/todo-row', module).add('A todo row container', () => ({ 6 | components: { TodoRow }, 7 | template: ` 8 |
foo
9 |
bar
10 |
` 11 | })) 12 | -------------------------------------------------------------------------------- /src/components/molecules/new-todo/index.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/molecules/new-todo/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { action } from '@storybook/addon-actions' 4 | import { withKnobs, text } from '@storybook/addon-knobs' 5 | 6 | import NewTodo from './' 7 | 8 | storiesOf('Molecules/new-todo', module) 9 | .addDecorator(withKnobs) 10 | .add('Todo registration form', () => ({ 11 | components: { NewTodo }, 12 | methods: { 13 | append: action('append') 14 | }, 15 | template: `` 16 | })) 17 | -------------------------------------------------------------------------------- /src/components/molecules/todo/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 28 | -------------------------------------------------------------------------------- /src/components/molecules/todo/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { action } from '@storybook/addon-actions' 4 | import { withKnobs, text } from '@storybook/addon-knobs' 5 | 6 | import Todo from './' 7 | 8 | storiesOf('Molecules/todo', module) 9 | .addDecorator(withKnobs) 10 | .add('Todo component', () => ({ 11 | components: { Todo }, 12 | methods: { 13 | complete: action('complete') 14 | }, 15 | template: `` 16 | })) 17 | -------------------------------------------------------------------------------- /src/components/organisms/todo-list/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 30 | 31 | 36 | 37 | -------------------------------------------------------------------------------- /src/components/organisms/todo-list/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import { action } from '@storybook/addon-actions' 4 | 5 | import TodoList from './' 6 | 7 | storiesOf('Organisms/todo-list', module).add('A list of todos', () => ({ 8 | components: { TodoList }, 9 | data() { 10 | return { 11 | todos: ['foo', 'bar', 'baz'] 12 | } 13 | }, 14 | methods: { 15 | complete: action('complete') 16 | }, 17 | template: `` 18 | })) 19 | -------------------------------------------------------------------------------- /src/components/pages/app/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 18 | -------------------------------------------------------------------------------- /src/components/templates/app-layout/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 34 | 35 | 83 | -------------------------------------------------------------------------------- /src/components/templates/app-layout/stories.js: -------------------------------------------------------------------------------- 1 | import { storiesOf } from '@storybook/vue' 2 | 3 | import AppLayout from './' 4 | 5 | storiesOf('Templates/app-layout', module).add('A layout of app', () => ({ 6 | components: { AppLayout }, 7 | template: `foo
bar
` 8 | })) 9 | -------------------------------------------------------------------------------- /src/containers/all-todos.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'vuex-connect' 2 | 3 | import TodoList from '~/components/organisms/todo-list' 4 | 5 | import { Todos, CompleteTodo } from '~/store/modules/todo' 6 | 7 | export default connect({ 8 | gettersToProps: { 9 | todos: Todos 10 | }, 11 | methodsToEvents: { 12 | complete: ({ dispatch }, todo) => dispatch(CompleteTodo, todo) 13 | } 14 | })('all-todos', TodoList) 15 | -------------------------------------------------------------------------------- /src/containers/new-todo.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'vuex-connect' 2 | 3 | import NewTodo from '~/components/molecules/new-todo' 4 | 5 | import { AppendTodo } from '~/store/modules/todo' 6 | 7 | export default connect({ 8 | methodsToEvents: { 9 | append: ({ dispatch }, todo) => dispatch(AppendTodo, todo) 10 | } 11 | })('new-todo', NewTodo) 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | 3 | import Vue from 'vue' 4 | 5 | import App from '~/components/pages/app' 6 | 7 | import store from './store' 8 | 9 | new Vue({ 10 | components: { App }, 11 | el: '#app', 12 | render(h) { 13 | return h(App) 14 | }, 15 | store 16 | }) 17 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | import todoModule from './modules/todo' 5 | 6 | Vue.use(Vuex) 7 | 8 | const store = new Vuex.Store({ 9 | modules: { 10 | todo: todoModule 11 | } 12 | }) 13 | 14 | export default store 15 | -------------------------------------------------------------------------------- /src/store/modules/todo/actionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Append new todo. 3 | */ 4 | export const AppendTodo = 'todo/APPEND_TODO' 5 | 6 | /** 7 | * Mark todo as completed. 8 | */ 9 | export const CompleteTodo = 'todo/COMPLETE_TODO' 10 | -------------------------------------------------------------------------------- /src/store/modules/todo/actions.js: -------------------------------------------------------------------------------- 1 | import { AppendTodo, CompleteTodo } from './actionTypes' 2 | 3 | import { SetTodos } from './mutationTypes' 4 | 5 | const actions = { 6 | async [AppendTodo]({ commit, state }, todo) { 7 | commit(SetTodos, [...state.todos, todo]) 8 | }, 9 | async [CompleteTodo]({ commit, state }, todo) { 10 | const updated = state.todos.filter(t => t !== todo) 11 | 12 | commit(SetTodos, updated) 13 | } 14 | } 15 | 16 | export default actions 17 | -------------------------------------------------------------------------------- /src/store/modules/todo/getterTypes.js: -------------------------------------------------------------------------------- 1 | export const Todos = 'todo/TODOS' 2 | -------------------------------------------------------------------------------- /src/store/modules/todo/getters.js: -------------------------------------------------------------------------------- 1 | import { Todos } from './getterTypes' 2 | 3 | const getters = { 4 | [Todos](state) { 5 | return state.todos 6 | } 7 | } 8 | 9 | export default getters 10 | -------------------------------------------------------------------------------- /src/store/modules/todo/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions' 2 | import getters from './getters' 3 | import mutations from './mutations' 4 | 5 | const store = { 6 | state() { 7 | return { 8 | todos: [] 9 | } 10 | }, 11 | actions, 12 | getters, 13 | mutations 14 | } 15 | 16 | export default store 17 | 18 | export * from './actionTypes' 19 | export * from './getterTypes' 20 | export * from './mutationTypes' 21 | -------------------------------------------------------------------------------- /src/store/modules/todo/mutationTypes.js: -------------------------------------------------------------------------------- 1 | export const SetTodos = 'todo/SET_TODOS' 2 | -------------------------------------------------------------------------------- /src/store/modules/todo/mutations.js: -------------------------------------------------------------------------------- 1 | import { SetTodos } from './mutationTypes' 2 | 3 | const mutations = { 4 | [SetTodos](state, todos) { 5 | state.todos = todos 6 | } 7 | } 8 | 9 | export default mutations 10 | --------------------------------------------------------------------------------