├── .babelrc ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.vue ├── StoragePlugin.ts ├── Todo.ts ├── TodoStorage.ts ├── TodoStore.ts ├── assets │ └── logo.png ├── index.ts └── sfc.d.ts ├── test └── App.spec.js ├── tsconfig.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }] 4 | ], 5 | "env": { 6 | "test": { 7 | "presets": ["env"] 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vuejs-typescript 2 | 3 | ## Build Setup 4 | 5 | ``` bash 6 | # install dependencies 7 | npm install 8 | 9 | # run test suite 10 | npm run test 11 | 12 | # serve with hot reload at localhost:8080 13 | npm run dev 14 | 15 | # build for production with minification 16 | npm run build 17 | ``` 18 | 19 | For detailed explanation on how things work, consult the [docs for vue-loader](http://vuejs.github.io/vue-loader). 20 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | VueJS + TypeScript 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vuejs-typescript", 3 | "version": "1.0.0", 4 | "author": "Toni Uebernickel ", 5 | "private": true, 6 | "scripts": { 7 | "test": "./node_modules/.bin/jest", 8 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 9 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 10 | }, 11 | "jest": { 12 | "setupFiles": [ 13 | "jest-localstorage-mock" 14 | ], 15 | "moduleNameMapper": { 16 | "^vue$": "vue/dist/vue.common.js" 17 | }, 18 | "moduleFileExtensions": [ 19 | "js", 20 | "ts", 21 | "vue" 22 | ], 23 | "transform": { 24 | "^.+\\.js$": "/node_modules/babel-jest", 25 | "^.*\\.vue$": "/node_modules/vue-jest", 26 | "^.*\\.ts$": "/node_modules/ts-jest" 27 | } 28 | }, 29 | "dependencies": { 30 | "rxjs": "^5.5.2", 31 | "vue": "^2.4.4", 32 | "vue-rx": "^5.0.0", 33 | "vuex": "^3.0.0" 34 | }, 35 | "devDependencies": { 36 | "@types/jest": "^22.2.3", 37 | "@vue/test-utils": "^1.0.0-beta.16", 38 | "babel-core": "^6.26.0", 39 | "babel-jest": "^22.4.4", 40 | "babel-loader": "^7.1.2", 41 | "babel-preset-env": "^1.6.0", 42 | "cross-env": "^5.0.5", 43 | "css-loader": "^0.28.7", 44 | "file-loader": "^1.1.4", 45 | "jest": "^22.4.3", 46 | "jest-localstorage-mock": "^2.2.0", 47 | "node-sass": "^4.5.3", 48 | "sass-loader": "^6.0.6", 49 | "ts-jest": "^22.4.6", 50 | "ts-loader": "^3.1.0", 51 | "typescript": "^2.6.1", 52 | "vue-class-component": "^6.0.0", 53 | "vue-jest": "^2.6.0", 54 | "vue-loader": "^13.0.5", 55 | "vue-property-decorator": "^6.0.0", 56 | "vue-template-compiler": "^2.4.4", 57 | "vue-ts-loader": "0.0.3", 58 | "vue-typescript-jest": "^0.3.0", 59 | "vuex-class": "^0.3.0", 60 | "webpack": "^3.6.0", 61 | "webpack-dev-server": "^2.9.1" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 56 | 57 | 171 | 172 | 177 | -------------------------------------------------------------------------------- /src/StoragePlugin.ts: -------------------------------------------------------------------------------- 1 | import TodoStorage from './TodoStorage' 2 | 3 | export default function StoragePlugin (storage?: TodoStorage) { 4 | if (!storage) { 5 | storage = new TodoStorage() 6 | } 7 | 8 | return store => { 9 | store.commit('replace', storage.fetch()) 10 | 11 | store.subscribe((mutation: { type: string, payload: any }, state) => { 12 | storage.save(state.todos) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Todo.ts: -------------------------------------------------------------------------------- 1 | export default interface Todo { 2 | id: number 3 | title: string 4 | completed: boolean 5 | } 6 | -------------------------------------------------------------------------------- /src/TodoStorage.ts: -------------------------------------------------------------------------------- 1 | import Todo from './Todo' 2 | 3 | const STORAGE_KEY = 'todos-vuejs-2.0' 4 | 5 | export default class TodoStorage { 6 | fetch(): Todo[] { 7 | const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]') 8 | 9 | todos.forEach((todo, index) => { 10 | todo.id = index 11 | }) 12 | 13 | return todos 14 | } 15 | 16 | save(todos: Todo[]) { 17 | localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/TodoStore.ts: -------------------------------------------------------------------------------- 1 | import Vuex from 'vuex'; 2 | 3 | import StoragePlugin from './StoragePlugin' 4 | import Todo from './Todo' 5 | import TodoStorage from './TodoStorage' 6 | 7 | const createQuery = (todos: Todo[]) => todos.map(todo => Object.freeze(Object.assign({}, todo))) 8 | const findById = (todos: Todo[], id: number) => todos.find(todo => todo.id === id) 9 | 10 | export default function TodoStore() { 11 | return new Vuex.Store({ 12 | plugins: [StoragePlugin()], 13 | 14 | state: { 15 | uid: 1, 16 | todos: [], 17 | query: [], 18 | }, 19 | 20 | mutations: { 21 | add(state, title: string) { 22 | state.todos.push({ 23 | id: state.uid++, 24 | title: title, 25 | completed: false, 26 | }) 27 | 28 | state.query = createQuery(state.todos) 29 | }, 30 | 31 | remove(state, todo: Todo) { 32 | state.todos.splice(state.todos.indexOf(todo), 1) 33 | 34 | state.query = createQuery(state.todos) 35 | }, 36 | 37 | replace(state, todos: Todo[]) { 38 | state.todos = todos 39 | state.uid = state.todos.length 40 | 41 | state.query = createQuery(state.todos) 42 | }, 43 | 44 | update(state, payload: { todo: Todo, title: string }) { 45 | const entry = findById(state.todos, payload.todo.id) 46 | 47 | entry.title = payload.title 48 | 49 | state.query = createQuery(state.todos) 50 | }, 51 | 52 | toggle(state, todo: Todo) { 53 | const entry = findById(state.todos, todo.id) 54 | 55 | entry.completed = !todo.completed 56 | 57 | state.query = createQuery(state.todos) 58 | }, 59 | 60 | toggleAll(state, value: boolean) { 61 | state.todos.forEach(todo => todo.completed = value) 62 | 63 | state.query = createQuery(state.todos) 64 | }, 65 | 66 | removeCompleted(state) { 67 | state.todos = state.todos.filter(todo => !todo.completed) 68 | 69 | state.query = createQuery(state.todos) 70 | } 71 | }, 72 | 73 | getters: { 74 | all(state): Todo[] { 75 | return state.query 76 | }, 77 | 78 | active(state, getters): Todo[] { 79 | return getters.all.filter(todo => !todo.completed) 80 | }, 81 | 82 | completed(state, getters): Todo[] { 83 | return getters.all.filter(todo => todo.completed) 84 | }, 85 | 86 | remaining(state, getters): number { 87 | return getters.active.length 88 | } 89 | } 90 | }) 91 | } 92 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/havvg/vuejs-typescript-todo-mvc/9086099544ac84d0c05bb04bc2e42f2e38260774/src/assets/logo.png -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex'; 3 | import VueRx from 'vue-rx' 4 | import { Observable } from 'rxjs/Observable' 5 | 6 | import App from './App.vue' 7 | import TodoStore from './TodoStore' 8 | 9 | Vue.use(Vuex) 10 | Vue.use(VueRx, { 11 | Observable 12 | }) 13 | 14 | new Vue({ 15 | el: '#app', 16 | store: TodoStore(), 17 | render: h => h(App), 18 | }) 19 | -------------------------------------------------------------------------------- /src/sfc.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import Vue from 'vue' 3 | 4 | export default Vue 5 | } 6 | -------------------------------------------------------------------------------- /test/App.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex'; 3 | import VueRx from 'vue-rx' 4 | import { Observable } from 'rxjs/Observable' 5 | 6 | import { shallowMount } from '@vue/test-utils' 7 | 8 | import App from '../src/App' 9 | import TodoStore from '../src/TodoStore' 10 | 11 | Vue.use(Vuex) 12 | Vue.use(VueRx, { 13 | Observable 14 | }) 15 | 16 | describe('ToDo App', () => { 17 | let cmp, store, vm 18 | 19 | beforeEach(() => { 20 | store = TodoStore() 21 | 22 | cmp = shallowMount(App, { 23 | store: store, 24 | }) 25 | 26 | vm = cmp.vm 27 | }) 28 | 29 | it('starts without any todo', () => { 30 | expect(vm.todos).toEqual([]) 31 | 32 | expect(cmp.find('.todo-list').exists()).toBe(true) 33 | expect(cmp.find('.todo-list').isEmpty()).toBe(true) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "es5", "es2015"], 4 | "target": "es2015", 5 | "module": "es6", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "experimentalDecorators": true, 9 | "allowSyntheticDefaultImports": true, 10 | "allowJs": true 11 | }, 12 | "include": [ 13 | "./src/**/*" 14 | ], 15 | "exclude": [ 16 | "node_modules" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | 4 | module.exports = { 5 | entry: './src/index.ts', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | publicPath: '/dist/', 9 | filename: 'build.js' 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.vue$/, 15 | loader: 'vue-loader', 16 | options: { 17 | esModule: true, 18 | loaders: { 19 | 'js': 'vue-ts-loader', 20 | 21 | // Since sass-loader (weirdly) has SCSS as its default parse mode, we map 22 | // the "scss" and "sass" values for the lang attribute to the right configs here. 23 | // other preprocessors should work out of the box, no loader config like this necessary. 24 | 'scss': 'vue-style-loader!css-loader!sass-loader', 25 | 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax' 26 | } 27 | // other vue-loader options go here 28 | } 29 | }, 30 | { 31 | test: /\.ts$/, 32 | loader: 'ts-loader', 33 | exclude: /node_modules/, 34 | options: { 35 | appendTsSuffixTo: [/\.vue$/], 36 | } 37 | }, 38 | { 39 | test: /\.js$/, 40 | loader: 'babel-loader', 41 | exclude: /node_modules/ 42 | }, 43 | { 44 | test: /\.(png|jpg|gif|svg)$/, 45 | loader: 'file-loader', 46 | options: { 47 | name: '[name].[ext]?[hash]' 48 | } 49 | } 50 | ] 51 | }, 52 | resolve: { 53 | extensions: ['.ts', '.js', '.vue', '.json'], 54 | alias: { 55 | 'vue$': 'vue/dist/vue.esm.js' 56 | } 57 | }, 58 | devServer: { 59 | historyApiFallback: true, 60 | noInfo: true, 61 | overlay: true 62 | }, 63 | performance: { 64 | hints: false 65 | }, 66 | devtool: '#eval-source-map' 67 | } 68 | 69 | if (process.env.NODE_ENV === 'production') { 70 | module.exports.devtool = '#source-map' 71 | // http://vue-loader.vuejs.org/en/workflow/production.html 72 | module.exports.plugins = (module.exports.plugins || []).concat([ 73 | new webpack.DefinePlugin({ 74 | 'process.env': { 75 | NODE_ENV: '"production"' 76 | } 77 | }), 78 | new webpack.LoaderOptionsPlugin({ 79 | minimize: true 80 | }) 81 | ]) 82 | } 83 | --------------------------------------------------------------------------------