├── .babelrc ├── .editorconfig ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.html ├── App.vue ├── components │ ├── Asset.vue │ └── Modal.vue ├── directives │ └── index.js ├── filters │ ├── capitalize.js │ └── money.js ├── main.js ├── modules │ ├── app │ │ ├── actions.js │ │ ├── components │ │ │ ├── End.vue │ │ │ ├── Goal.vue │ │ │ ├── HelpModal.vue │ │ │ ├── Home.vue │ │ │ └── Navigation.vue │ │ ├── index.js │ │ └── mutations.js │ ├── persistence │ │ ├── actions.js │ │ ├── api.js │ │ ├── components │ │ │ ├── LoadModal.vue │ │ │ ├── PersistenceNavItem.vue │ │ │ └── SaveModal.vue │ │ ├── index.js │ │ └── mutations.js │ ├── portfolio │ │ ├── components │ │ │ └── Portfolio.vue │ │ ├── getters.js │ │ ├── index.js │ │ └── mutations.js │ └── stock │ │ ├── actions.js │ │ ├── components │ │ └── Stocks.vue │ │ ├── getters.js │ │ ├── index.js │ │ └── mutations.js ├── routes.js ├── store.js └── utils │ └── index.js ├── vercel.json └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-3" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.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 | # Vue.js Stock Trader Game 2 | 3 | A simple game created for learning the Vue.js ecosystem. This project makes use of following: 4 | 5 | ### Modules: 6 | 7 | * [Vue.js](https://github.com/vuejs/vue) - Framework 8 | * [Vue Router](https://github.com/vuejs/vue-router) - Routing 9 | * [Vuex](https://github.com/vuejs/vuex) - State Management 10 | * [Vuelidate](https://github.com/monterail/vuelidate) - Form validation 11 | 12 | ### Features 13 | 14 | * [Mixins](https://vuejs.org/v2/guide/mixins.html) 15 | * [Filters](https://vuejs.org/v2/guide/filters.html) 16 | * [Custom directives](https://vuejs.org/v2/guide/custom-directive.html) 17 | * [Single file components](https://vuejs.org/v2/guide/single-file-components.html) 18 | * [Component scoped CSS](https://vue-loader.vuejs.org/en/features/scoped-css.html) 19 | * [Enter/Leave transitions](https://vuejs.org/v2/guide/transitions.html) 20 | 21 | The project was made while trying to follow the best practices found on the web. Although many patterns were created right here. 22 | 23 | ## How to run 24 | 25 | ```bash 26 | # Install dependencies 27 | npm install 28 | 29 | # Serve with hot reload at localhost:8080 30 | npm run dev 31 | 32 | # Build for production with minification 33 | npm run build 34 | ``` 35 | 36 | ## Things to add 37 | 38 | * Testing 39 | * State transitions 40 | 41 | ## Contribution 42 | 43 | Feel free to contribute to this project in order to introduce better practices building `Vue.js` apps. Create an issue with a question/bug to discuss certain problems or just introduce a pull request right away! 44 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Development - Stock Trader 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "training-vuejs", 3 | "description": "A Vue.js project", 4 | "version": "1.0.0", 5 | "author": "Jakub Sarnowski ", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot", 10 | "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" 11 | }, 12 | "dependencies": { 13 | "lodash": "^4.17.4", 14 | "random-words": "0.0.1", 15 | "vue": "^2.5.11", 16 | "vue-router": "^3.0.1", 17 | "vuelidate": "^0.6.1", 18 | "vuex": "^3.0.1" 19 | }, 20 | "browserslist": [ 21 | "> 1%", 22 | "last 2 versions", 23 | "not ie <= 8" 24 | ], 25 | "devDependencies": { 26 | "babel-core": "^6.26.0", 27 | "babel-loader": "^7.1.2", 28 | "babel-preset-env": "^1.6.0", 29 | "babel-preset-stage-3": "^6.24.1", 30 | "copy-webpack-plugin": "^4.3.1", 31 | "cross-env": "^5.0.5", 32 | "css-loader": "^0.28.7", 33 | "file-loader": "^1.1.4", 34 | "html-webpack-include-assets-plugin": "^1.0.2", 35 | "html-webpack-plugin": "^2.30.1", 36 | "pug": "^2.0.0-rc.4", 37 | "vue-loader": "^13.0.5", 38 | "vue-template-compiler": "^2.4.4", 39 | "webpack": "^3.6.0", 40 | "webpack-dev-server": "^2.9.1" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/App.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue.js - Stock Trader 7 | 8 | 9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | 35 | 48 | -------------------------------------------------------------------------------- /src/components/Asset.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 145 | -------------------------------------------------------------------------------- /src/components/Modal.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 26 | 27 | -------------------------------------------------------------------------------- /src/directives/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | 3 | const isActiveElementAnInput = () => document.activeElement.tagName === 'INPUT'; 4 | 5 | Vue.directive('focus', { 6 | inserted(el) { 7 | if (!isActiveElementAnInput()) { 8 | el.focus(); 9 | } 10 | }, 11 | update(el) { 12 | if (!isActiveElementAnInput()) { 13 | Vue.nextTick(() => el.focus()); 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/filters/capitalize.js: -------------------------------------------------------------------------------- 1 | import capitalize from 'lodash/capitalize'; 2 | 3 | export default value => capitalize(value); 4 | -------------------------------------------------------------------------------- /src/filters/money.js: -------------------------------------------------------------------------------- 1 | export default value => value.toLocaleString(); 2 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueRouter from 'vue-router'; 3 | 4 | import './directives'; 5 | import routes from './routes'; 6 | import store from './store'; 7 | 8 | import App from './App.vue'; 9 | import utils from './utils'; 10 | 11 | Vue.use(VueRouter); 12 | 13 | const router = new VueRouter({ 14 | routes, 15 | mode: 'history' 16 | }); 17 | 18 | new Vue({ 19 | el: '#app', 20 | router, 21 | store, 22 | render: h => h(App), 23 | mounted() { 24 | const vm = this; 25 | utils.attachKeyboardShortcuts(vm); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /src/modules/app/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | buy: ({ state, commit }, { price, quantity }) => { 3 | commit('decreaseFunds', price * quantity); 4 | commit('updateProgress'); 5 | 6 | if (state.progress >= 100) { 7 | commit('endgame'); 8 | } 9 | }, 10 | sell: ({ state, commit }, { price, quantity }) => { 11 | commit('increaseFunds', price * quantity); 12 | commit('updateProgress'); 13 | 14 | if (state.progress >= 100) { 15 | commit('endgame'); 16 | } 17 | }, 18 | endday: ({ commit }) => { 19 | commit('endday'); 20 | commit('stock/endday', null, { root: true }); 21 | }, 22 | invokeHelpModal: ({ commit }) => commit('invokeHelpModal'), 23 | revokeHelpModal: ({ commit }) => commit('revokeHelpModal') 24 | }; 25 | -------------------------------------------------------------------------------- /src/modules/app/components/End.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/app/components/Goal.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 29 | -------------------------------------------------------------------------------- /src/modules/app/components/HelpModal.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 36 | -------------------------------------------------------------------------------- /src/modules/app/components/Home.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/app/components/Navigation.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 44 | 45 | 50 | -------------------------------------------------------------------------------- /src/modules/app/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions'; 2 | import mutations from './mutations'; 3 | 4 | import Home from './components/Home'; 5 | import End from './components/End'; 6 | import Navigation from './components/Navigation'; 7 | import Goal from './components/Goal'; 8 | import HelpModal from './components/HelpModal'; 9 | 10 | const defaultState = { 11 | day: 1, 12 | funds: 10000, 13 | goal: 1000000, 14 | progress: 1, 15 | finished: false, 16 | showHelpModal: false 17 | }; 18 | 19 | const module = { 20 | namespaced: true, 21 | state: defaultState, 22 | mutations, 23 | actions 24 | }; 25 | 26 | export { module as default, Navigation, Home, Goal, End, HelpModal }; 27 | -------------------------------------------------------------------------------- /src/modules/app/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | state(state, newState) { 3 | state = Object.assign(state, newState); 4 | }, 5 | decreaseFunds(state, amount) { 6 | state.funds -= amount; 7 | }, 8 | increaseFunds(state, amount) { 9 | state.funds += amount; 10 | }, 11 | updateProgress(state) { 12 | state.progress = state.funds / state.goal * 100; 13 | }, 14 | endgame(state) { 15 | state.finished = true; 16 | }, 17 | endday(state) { 18 | state.day++; 19 | }, 20 | invokeHelpModal(state) { 21 | state.showHelpModal = true; 22 | }, 23 | revokeHelpModal(state) { 24 | state.showHelpModal = false; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /src/modules/persistence/actions.js: -------------------------------------------------------------------------------- 1 | import pick from 'lodash/pick'; 2 | import api from './api'; 3 | 4 | export default { 5 | invokeSaveModal: ({ commit }) => commit('invokeSaveModal'), 6 | save: ({ rootState, commit }, name) => { 7 | commit('saveStart'); 8 | 9 | const stateToSave = { 10 | name, 11 | timestamp: Date.now(), 12 | state: pick(rootState, ['app', 'stock', 'portfolio']) 13 | }; 14 | 15 | return api 16 | .save(stateToSave) 17 | .then(response => response.json()) 18 | .then(() => { 19 | commit('saveEnd'); 20 | commit('revokeSaveModal'); 21 | }) 22 | .catch(() => commit('saveError')); 23 | }, 24 | cancelSave: ({ commit }) => { 25 | commit('revokeSaveModal'); 26 | }, 27 | invokeLoadModal: ({ commit }) => { 28 | commit('loadStart'); 29 | 30 | return api 31 | .load() 32 | .then(response => response.json()) 33 | .then(data => { 34 | commit('loadEnd', Object.values(data)); 35 | commit('invokeLoadModal'); 36 | }) 37 | .catch(() => commit('loadError')); 38 | }, 39 | load: ({ commit }, { state }) => { 40 | Object.keys(state).forEach(key => { 41 | commit(`${key}/state`, state[key], { root: true }); 42 | }); 43 | commit('revokeLoadModal'); 44 | }, 45 | cancelLoad: ({ commit }) => { 46 | commit('revokeLoadModal'); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /src/modules/persistence/api.js: -------------------------------------------------------------------------------- 1 | const apiUrl = 'https://vuejs-stock-trader-23217.firebaseio.com'; 2 | 3 | const resource = { 4 | save: state => 5 | fetch(`${apiUrl}/state.json`, { 6 | method: 'POST', 7 | body: JSON.stringify(state) 8 | }), 9 | load: () => 10 | fetch(`${apiUrl}/state.json`, { 11 | method: 'GET' 12 | }) 13 | }; 14 | 15 | export default { 16 | save(state) { 17 | return resource.save(state); 18 | }, 19 | load() { 20 | return resource.load(); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/modules/persistence/components/LoadModal.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 34 | 35 | 40 | -------------------------------------------------------------------------------- /src/modules/persistence/components/PersistenceNavItem.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 18 | -------------------------------------------------------------------------------- /src/modules/persistence/components/SaveModal.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 43 | -------------------------------------------------------------------------------- /src/modules/persistence/index.js: -------------------------------------------------------------------------------- 1 | import mutations from './mutations'; 2 | import actions from './actions'; 3 | import './api'; 4 | 5 | import PersistenceNavItem from './components/PersistenceNavItem'; 6 | import SaveModal from './components/SaveModal'; 7 | import LoadModal from './components/LoadModal'; 8 | 9 | const defaultState = { 10 | load: { 11 | items: [], 12 | inProgress: false, 13 | showModal: false 14 | }, 15 | save: { 16 | inProgress: false, 17 | showModal: false 18 | } 19 | }; 20 | 21 | const module = { 22 | namespaced: true, 23 | state: defaultState, 24 | mutations, 25 | actions 26 | }; 27 | 28 | export { module as default, PersistenceNavItem, SaveModal, LoadModal }; 29 | -------------------------------------------------------------------------------- /src/modules/persistence/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | state(state, newState) { 3 | state = Object.assign(state, newState); 4 | }, 5 | invokeSaveModal(state) { 6 | state.save.showModal = true; 7 | }, 8 | revokeSaveModal(state) { 9 | state.save.showModal = false; 10 | }, 11 | saveStart(state) { 12 | state.save.inProgress = true; 13 | }, 14 | saveEnd(state) { 15 | state.save.inProgress = false; 16 | }, 17 | saveError(state) { 18 | state.save.inProgress = false; 19 | }, 20 | invokeLoadModal(state) { 21 | state.load.showModal = true; 22 | }, 23 | revokeLoadModal(state) { 24 | state.load.showModal = false; 25 | }, 26 | loadStart(state) { 27 | state.load.inProgress = true; 28 | }, 29 | loadEnd(state, data) { 30 | state.load.items = data 31 | .map(item => ({ 32 | ...item, 33 | timestamp: new Date(item.timestamp).toLocaleString() 34 | })) 35 | .reverse(); 36 | 37 | state.load.inProgress = false; 38 | }, 39 | loadError(state) { 40 | state.load.inProgress = false; 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /src/modules/portfolio/components/Portfolio.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 35 | -------------------------------------------------------------------------------- /src/modules/portfolio/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | items(state, getters, rootState, rootGetters) { 3 | return state.items.map(item => ({ 4 | ...item, 5 | price: rootGetters['stock/getPriceByName'](item.name) 6 | })); 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/modules/portfolio/index.js: -------------------------------------------------------------------------------- 1 | import getters from './getters'; 2 | import mutations from './mutations'; 3 | 4 | import Portfolio from './components/Portfolio'; 5 | 6 | const defaultState = { 7 | items: [] 8 | }; 9 | 10 | const module = { 11 | namespaced: true, 12 | state: defaultState, 13 | getters, 14 | mutations 15 | }; 16 | 17 | export { 18 | module as default, 19 | Portfolio 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/portfolio/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | state(state, newState) { 3 | state = Object.assign(state, newState); 4 | }, 5 | buy(state, { name, quantity, price }) { 6 | const asset = state.items.find(asset => asset.name === name); 7 | 8 | if (asset) { 9 | asset.quantity += quantity; 10 | } else { 11 | state.items.push({ name, quantity }); 12 | } 13 | }, 14 | sell(state, { name, quantity, price }) { 15 | const asset = state.items.find(asset => asset.name === name); 16 | const assetQuantity = asset.quantity - quantity; 17 | 18 | if (assetQuantity === 0) { 19 | const assetIndex = state.items.indexOf(asset); 20 | state.items.splice(assetIndex, 1); 21 | } else { 22 | asset.quantity = assetQuantity; 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/modules/stock/actions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | generateData: ({ commit }) => commit('generate'), 3 | buy: ({ commit, dispatch, getters }, { name, quantity }) => { 4 | const payload = { 5 | ...getters.getByName(name), 6 | quantity 7 | }; 8 | 9 | dispatch('app/buy', payload, { root: true }); 10 | commit('portfolio/buy', payload, { root: true }); 11 | }, 12 | sell: ({ commit, dispatch, getters }, { name, quantity }) => { 13 | const payload = { 14 | ...getters.getByName(name), 15 | quantity 16 | }; 17 | 18 | dispatch('app/sell', payload, { root: true }); 19 | commit('portfolio/sell', payload, { root: true }); 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /src/modules/stock/components/Stocks.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 37 | -------------------------------------------------------------------------------- /src/modules/stock/getters.js: -------------------------------------------------------------------------------- 1 | export default { 2 | getByName: state => name => { 3 | return state.items.find(item => item.name === name); 4 | }, 5 | getPriceByName: (state, getters) => name => { 6 | return getters.getByName(name).price; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /src/modules/stock/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions'; 2 | import mutations from './mutations'; 3 | import getters from './getters'; 4 | 5 | import Stocks from './components/Stocks'; 6 | 7 | const defaultState = { 8 | initialized: false, 9 | items: [ 10 | { name: 'Apple', price: 25 }, 11 | { name: 'Microsoft', price: 15 }, 12 | { name: 'Oracle', price: 50 } 13 | ] 14 | }; 15 | 16 | const module = { 17 | namespaced: true, 18 | state: defaultState, 19 | mutations, 20 | getters, 21 | actions 22 | }; 23 | 24 | export { module as default, Stocks }; 25 | -------------------------------------------------------------------------------- /src/modules/stock/mutations.js: -------------------------------------------------------------------------------- 1 | import random from 'lodash/random'; 2 | import capitalize from 'lodash/capitalize'; 3 | import randomWords from 'random-words'; 4 | 5 | export default { 6 | state(state, newState) { 7 | state = Object.assign(state, newState); 8 | }, 9 | generate(state) { 10 | state.items = randomWords(10).map(name => ({ 11 | name: capitalize(name), 12 | price: random(15, 100) 13 | })); 14 | state.initialized = true; 15 | }, 16 | endday(state) { 17 | state.items = state.items.map(stock => ({ 18 | ...stock, 19 | price: random(15, 100) 20 | })); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import { Home } from './modules/app'; 2 | import { Portfolio } from './modules/portfolio'; 3 | import { Stocks } from './modules/stock'; 4 | 5 | export default [ 6 | { path: '/', component: Home }, 7 | { path: '/portfolio', component: Portfolio }, 8 | { path: '/stocks', component: Stocks } 9 | ]; 10 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import app from './modules/app'; 5 | import portfolio from './modules/portfolio'; 6 | import stock from './modules/stock'; 7 | import persistence from './modules/persistence'; 8 | 9 | Vue.use(Vuex); 10 | 11 | export default new Vuex.Store({ 12 | modules: { 13 | app, 14 | portfolio, 15 | stock, 16 | persistence 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | attachKeyboardShortcuts: vm => { 3 | window.addEventListener('keydown', event => { 4 | switch (event.key) { 5 | case 'p': 6 | vm.$router.push('/portfolio'); 7 | event.preventDefault(); 8 | break; 9 | case 's': 10 | vm.$router.push('/stocks'); 11 | event.preventDefault(); 12 | break; 13 | case 'd': 14 | vm.$store.dispatch('app/endday'); 15 | event.preventDefault(); 16 | break; 17 | } 18 | }); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/(.*)", 5 | "destination": "/" 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | var HtmlWebpackIncludeAssetsPlugin = require('html-webpack-include-assets-plugin'); 5 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | 7 | module.exports = { 8 | entry: './src/main.js', 9 | output: { 10 | path: path.resolve(__dirname, './dist'), 11 | publicPath: '/dist/', 12 | filename: 'build.js' 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.css$/, 18 | use: ['vue-style-loader', 'css-loader'] 19 | }, 20 | { 21 | test: /\.vue$/, 22 | loader: 'vue-loader' 23 | }, 24 | { 25 | test: /\.js$/, 26 | loader: 'babel-loader', 27 | exclude: /node_modules/ 28 | }, 29 | { 30 | test: /\.(png|jpg|gif|svg)$/, 31 | loader: 'file-loader', 32 | options: { 33 | name: '[name].[ext]?[hash]' 34 | } 35 | } 36 | ] 37 | }, 38 | resolve: { 39 | alias: { 40 | vue$: 'vue/dist/vue.esm.js' 41 | }, 42 | extensions: ['*', '.js', '.vue', '.json'] 43 | }, 44 | devServer: { 45 | historyApiFallback: true, 46 | noInfo: true, 47 | overlay: true 48 | }, 49 | performance: { 50 | hints: false 51 | }, 52 | devtool: '#eval-source-map' 53 | }; 54 | 55 | if (process.env.NODE_ENV === 'production') { 56 | delete module.exports.output.publicPath; 57 | 58 | module.exports.devtool = '#source-map'; 59 | // http://vue-loader.vuejs.org/en/workflow/production.html 60 | module.exports.plugins = (module.exports.plugins || []).concat([ 61 | new webpack.DefinePlugin({ 62 | 'process.env': { 63 | NODE_ENV: '"production"' 64 | } 65 | }), 66 | new webpack.optimize.UglifyJsPlugin({ 67 | sourceMap: true, 68 | compress: { 69 | warnings: false 70 | } 71 | }), 72 | new webpack.LoaderOptionsPlugin({ 73 | minimize: true 74 | }), 75 | new HtmlWebpackPlugin({ 76 | template: 'src/App.html' 77 | }), 78 | new HtmlWebpackIncludeAssetsPlugin({ 79 | assets: [ 80 | 'https://cdnjs.cloudflare.com/ajax/libs/bulma/0.6.1/css/bulma.min.css', 81 | 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css' 82 | ], 83 | append: false 84 | }), 85 | new CopyWebpackPlugin([{ from: 'config/*', to: '[name].[ext]' }]) 86 | ]); 87 | } 88 | --------------------------------------------------------------------------------