├── .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 |
2 |
3 |
4 |
12 |
13 |
36 |
37 |
46 |
47 |
48 |
54 |
55 |
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 |
--------------------------------------------------------------------------------