├── static ├── fonts │ └── .keep ├── images │ ├── .keep │ └── favicon.ico └── robots.txt ├── src ├── helpers │ ├── SoundManager.js │ ├── Emitter.js │ └── AssetsLoader.js ├── stylesheets │ ├── common │ │ ├── _text.scss │ │ ├── _fonts.scss │ │ ├── _keyframes.scss │ │ ├── _layout.scss │ │ ├── _base.scss │ │ ├── _reset.scss │ │ ├── _mixins.scss │ │ └── _variables.scss │ └── main.scss ├── containers │ ├── Application │ │ ├── template.html │ │ └── index.js │ └── Homepage │ │ ├── template.html │ │ ├── styles.scss │ │ └── index.js ├── vuex │ ├── example │ │ ├── getters.js │ │ ├── actions.js │ │ └── store.js │ ├── mutation-types.js │ └── store.js ├── config │ ├── messages │ │ ├── index.js │ │ └── global.js │ └── resources.js ├── components │ └── Example │ │ ├── styles.scss │ │ ├── template.html │ │ └── index.js ├── utils │ └── maths │ │ ├── random-hex-color.js │ │ ├── diagonal.js │ │ ├── to-type.js │ │ ├── random-int.js │ │ ├── lerp.js │ │ ├── normalize.js │ │ ├── clamp.js │ │ ├── smooth-step.js │ │ ├── loop-index.js │ │ ├── parabola.js │ │ ├── random-float.js │ │ ├── distance.js │ │ ├── shader-parse.js │ │ ├── map.js │ │ ├── index.js │ │ └── lighten-darken-color.js ├── main.js ├── mixins │ ├── FadeTransitionMixin.js │ └── EventManagerMixin.js ├── core │ ├── Router.js │ └── States.js └── template │ └── index.tpl.html ├── .gitignore ├── .babelrc ├── .eslintignore ├── .editorconfig ├── LICENSE.md ├── .eslintrc ├── server.js ├── README.md ├── .gitattributes ├── package.json └── webpack ├── webpack.dev.config.babel.js └── webpack.prod.config.babel.js /static/fonts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/helpers/SoundManager.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/stylesheets/common/_text.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/stylesheets/common/_fonts.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/stylesheets/common/_keyframes.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/stylesheets/common/_layout.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100vh; 4 | } -------------------------------------------------------------------------------- /static/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-webpack-boilerplate/HEAD/static/images/.keep -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | node_modules/ 4 | dist/ 5 | doc/ 6 | *.log 7 | 8 | src/components/WebGLBase 9 | -------------------------------------------------------------------------------- /static/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["es2015"], 4 | "stage-0" 5 | ], 6 | "plugins": ["add-module-exports"] 7 | } -------------------------------------------------------------------------------- /src/containers/Application/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
6 | -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patrickheng/vuejs-webpack-boilerplate/HEAD/static/images/favicon.ico -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Ignore some folders 2 | dist 3 | doc 4 | 5 | # Ignore config stuff 6 | server.*.js 7 | *config.js 8 | serviceworker.js -------------------------------------------------------------------------------- /src/vuex/example/getters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export const countExample = (state) => { 4 | return state.exampleStore.count; 5 | }; 6 | -------------------------------------------------------------------------------- /src/vuex/mutation-types.js: -------------------------------------------------------------------------------- 1 | const mutationTypes = { 2 | COUNTER_INCREMENT: 'COUNTER_INCREMENT' 3 | }; 4 | 5 | module.exports = mutationTypes; 6 | -------------------------------------------------------------------------------- /src/config/messages/index.js: -------------------------------------------------------------------------------- 1 | import global from './global'; 2 | 3 | const messages = { 4 | ...global 5 | }; 6 | 7 | module.exports = messages; 8 | -------------------------------------------------------------------------------- /src/components/Example/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~stylesheets/common/variables'; 2 | 3 | .example { 4 | display: inline-block; 5 | margin-top: 3rem; 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Example/template.html: -------------------------------------------------------------------------------- 1 |
2 |
Counter example = {{ count }}
3 | 4 |
5 | -------------------------------------------------------------------------------- /src/helpers/Emitter.js: -------------------------------------------------------------------------------- 1 | import EventEmitter from 'event-emitter'; 2 | 3 | const Emitter = new EventEmitter; 4 | 5 | window.Emitter = Emitter; 6 | 7 | export default Emitter; 8 | -------------------------------------------------------------------------------- /src/vuex/example/actions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import * as types from '../mutation-types'; 4 | 5 | export const counterIncrement = ({ dispatch }) => { 6 | dispatch(types.COUNTER_INCREMENT); 7 | }; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /src/utils/maths/random-hex-color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random hexadecimal color 3 | * 4 | * @return {string} Hexadecimal color 5 | */ 6 | export default function randomHexColor() { 7 | return '0x' + Math.floor( Math.random() * 16777215 ).toString( 16 ); 8 | } -------------------------------------------------------------------------------- /src/utils/maths/diagonal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Diagonal of a rectangle 3 | * 4 | * @param {number} w Width 5 | * @param {number} h Height 6 | * @return {number} Diagonal length 7 | */ 8 | export default function diagonal(w, h) { 9 | return Math.sqrt(w * w + h * h); 10 | } -------------------------------------------------------------------------------- /src/utils/maths/to-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Get the type of an object 3 | * 4 | * @param {object} obj Object 5 | * @return {string} Type of the object 6 | */ 7 | export default function toType(obj) { 8 | return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase(); 9 | } -------------------------------------------------------------------------------- /src/utils/maths/random-int.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random integer 3 | * 4 | * @param {number} min Minimum boundary 5 | * @param {number} max Maximum boundary 6 | * @return {number} Generated integer 7 | */ 8 | export default function randomInt(min, max) { 9 | return Math.floor( Math.random() * ( max - min + 1 ) + min ); 10 | } -------------------------------------------------------------------------------- /src/vuex/example/store.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import { 4 | COUNTER_INCREMENT 5 | } from '../mutation-types'; 6 | 7 | const state = { 8 | count: 0 9 | }; 10 | 11 | const mutations = { 12 | [ COUNTER_INCREMENT ] (state) { 13 | state.count++; 14 | } 15 | }; 16 | 17 | export default { 18 | state, 19 | mutations 20 | }; 21 | -------------------------------------------------------------------------------- /src/utils/maths/lerp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Linear interpolation between two values (lerping) 3 | * 4 | * @param {number} x First point 5 | * @param {number} y Second point 6 | * @param {number} r Value to interpolate 7 | * @return {number} Lerped value 8 | */ 9 | export default function lerp(x, y, r) { 10 | return x + ((y - x) * r); 11 | } -------------------------------------------------------------------------------- /src/utils/maths/normalize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Normalize a value between two bounds 3 | * 4 | * @param {number} min Minimum boundary 5 | * @param {number} max Maximum boundary 6 | * @param {number} x Value to normalize 7 | * @return {number} Normalized value 8 | */ 9 | export default function normalize(min, max, x) { 10 | return (x - min) / (max - min); 11 | } -------------------------------------------------------------------------------- /src/utils/maths/clamp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clamp a value between two bounds 3 | * 4 | * @param {number} min Minimum boundary 5 | * @param {number} max Maximum boundary 6 | * @param {number} v Value to clamp 7 | * @return {number} Clamped value 8 | */ 9 | export default function clamp(min, max, v) { 10 | if (v < min) return min; 11 | if (v > max) return max; 12 | return v; 13 | } -------------------------------------------------------------------------------- /src/config/messages/global.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const globalMessages = { 4 | 5 | /* 6 | * WINDOW 7 | */ 8 | 9 | WINDOW_RESIZE: 'WINDOW_RESIZE', 10 | WINDOW_ON_FOCUS: 'WINDOW_ON_FOCUS', 11 | WINDOW_ON_BLUR: 'WINDOW_ON_BLUR', 12 | 13 | /* 14 | * RESOURCES 15 | */ 16 | RESOURCES_READY: 'RESOURCES_READY' 17 | 18 | }; 19 | 20 | module.exports = globalMessages; 21 | -------------------------------------------------------------------------------- /src/config/resources.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | // ===== Example of manifest 3 | // // ==== Textures 4 | // { 5 | // type: 'texture', 6 | // id: 'heightMapTexture', 7 | // url: './textures/heightmap.png' 8 | // }, 9 | // // ==== Sounds 10 | // { 11 | // type: 'audio', 12 | // id: 'soundExample', 13 | // url: './audios/soundExample.mp3' 14 | // } 15 | ]; 16 | -------------------------------------------------------------------------------- /src/stylesheets/main.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | @import '../../node_modules/breakpoint-sass/stylesheets/breakpoint'; 4 | 5 | @import 'common/reset'; 6 | 7 | @import 'common/fonts'; 8 | 9 | @import 'common/mixins'; 10 | 11 | @import 'common/variables'; 12 | 13 | @import 'common/base'; 14 | 15 | @import 'common/text'; 16 | 17 | @import 'common/layout'; 18 | 19 | @import 'common/keyframes'; 20 | -------------------------------------------------------------------------------- /src/utils/maths/smooth-step.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Smooth a value 3 | * 4 | * @param {number} min Minimum boundary 5 | * @param {number} max Maximum boundary 6 | * @param {number} v Value 7 | * @return {number} Smoothed value 8 | */ 9 | export default function smoothStep(min, max, v) { 10 | const x = Math.max( 0, Math.min( 1, ( v - min ) / ( max - min ) ) ); 11 | return x * x * ( 3 - 2 * x ); 12 | } -------------------------------------------------------------------------------- /src/containers/Homepage/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |

Vue Js / Webpack Boilerplate

6 | 7 |

8 | 9 | Boilerplate by @Pat_Hg 10 | 11 |

12 | 13 | 14 | 15 |
16 | 17 |
18 | -------------------------------------------------------------------------------- /src/utils/maths/loop-index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loop on an index value 3 | * 4 | * @param {number} index Index 5 | * @param {number} length Length 6 | * @return {number} Looped index 7 | */ 8 | export default function loopIndex(index, length) { 9 | if (index < 0) { 10 | index = length + index % length; 11 | } 12 | if (index >= length) { 13 | return index % length; 14 | } 15 | return index; 16 | } -------------------------------------------------------------------------------- /src/vuex/store.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Vuex from 'vuex'; 4 | import exampleStore from 'vuex/example/store'; 5 | import createLogger from 'vuex/logger'; 6 | 7 | Vue.use(Vuex); 8 | 9 | const debug = process.env.NODE_ENV !== 'production'; 10 | 11 | export default new Vuex.Store({ 12 | modules: { 13 | exampleStore 14 | }, 15 | strict: debug, 16 | middlewares: debug ? [createLogger()] : [] 17 | }); 18 | -------------------------------------------------------------------------------- /src/containers/Homepage/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~stylesheets/common/variables'; 2 | @import '~stylesheets/common/mixins'; 3 | 4 | .homepage { 5 | text-align: center; 6 | } 7 | 8 | .homepage__title { 9 | font-size: 1.8rem; 10 | font-weight: bold; 11 | color: $color-green; 12 | } 13 | 14 | .homepage__paragraph { 15 | font-size: 1.4rem; 16 | margin-top: 1rem; 17 | } 18 | 19 | .homepage__content { 20 | @include center-xy; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/maths/parabola.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Remap the 0..1 interval into 0..1 parabola, such that the corners are remaped to 0 and the center to 1. 3 | * In other words, parabola(0) = parabola(1) = 0, and parabola(1/2) = 1. 4 | * 5 | * @param {number} k Value to map 6 | * @param {number} x Coordinate on X axis 7 | * @return {number} Mapped value 8 | */ 9 | export default function parabola(k, x) { 10 | return Math.pow( 4 * x * ( 1 - x ), k ); 11 | } -------------------------------------------------------------------------------- /src/utils/maths/random-float.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate a random float 3 | * 4 | * @param {number} minValue Minimum boundary 5 | * @param {number} maxValue Maximum boundary 6 | * @param {number} precision Precision 7 | * @return {number} Generated float 8 | */ 9 | export default function randomFloat( minValue, maxValue, precision = 2 ) { 10 | return parseFloat( Math.min( minValue + ( Math.random() * ( maxValue - minValue ) ), maxValue ).toFixed( precision ) ); 11 | } -------------------------------------------------------------------------------- /src/utils/maths/distance.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Distance between two points 3 | * 4 | * @param {number} x1 X coord of the first point 5 | * @param {number} y1 Y coord of the first point 6 | * @param {number} x2 X coord of the second point 7 | * @param {number} y2 Y coord of the second point 8 | * @return {number} Computed distance 9 | */ 10 | export default function distance(x1, y1, x2, y2) { 11 | const dx = x1 - x2; 12 | const dy = y1 - y2; 13 | return Math.sqrt(dx * dx + dy * dy); 14 | } -------------------------------------------------------------------------------- /src/stylesheets/common/_base.scss: -------------------------------------------------------------------------------- 1 | html { 2 | width: 100%; 3 | height: 100%; 4 | font-family: arial, sans-serif; 5 | font-size: 62.5%; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | body { 11 | width: 100%; 12 | height: 100%; 13 | } 14 | 15 | * { 16 | outline: none; 17 | box-sizing: border-box; 18 | } 19 | 20 | button { 21 | border: 0; 22 | background: transparent; 23 | } 24 | 25 | a { 26 | text-decoration: none; 27 | } 28 | 29 | canvas { 30 | vertical-align: middle; 31 | } -------------------------------------------------------------------------------- /src/utils/maths/shader-parse.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Replace THREE chunk 3 | * 4 | * @param {string} a Match 5 | * @param {string} b First match 6 | * @return {string} Parsed GLSL 7 | */ 8 | function replaceThreeChunkFn(a, b) { 9 | return THREE.ShaderChunk[b] + '\n'; 10 | } 11 | 12 | /** 13 | * Parse shader and replace THREE chunk 14 | * 15 | * @param {string} glsl GLSL 16 | * @return {string} Parsed GLSL 17 | */ 18 | export default function shaderParse(glsl) { 19 | return glsl.replace(/\/\/\s?chunk\(\s?(\w+)\s?\);/g, replaceThreeChunkFn); 20 | } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Application from 'containers/Application'; 2 | 3 | import Router from 'core/Router'; 4 | 5 | import 'stylesheets/main.scss'; 6 | 7 | import domready from 'domready'; 8 | 9 | class Main { 10 | 11 | constructor() { 12 | 13 | this.bind(); 14 | 15 | this.addEventListeners(); 16 | 17 | this.router = Router; 18 | 19 | this.start(); 20 | } 21 | 22 | bind() {} 23 | 24 | addEventListeners() {} 25 | 26 | start() { 27 | 28 | this.router.start(Application, '#application'); 29 | 30 | } 31 | } 32 | 33 | domready(() => { 34 | 35 | new Main(); 36 | }); 37 | -------------------------------------------------------------------------------- /src/utils/maths/map.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Re-maps a number from one range to another 3 | * 4 | * @param {number} value The incoming value to be converted 5 | * @param {number} start1 Lower bound of the value's current range 6 | * @param {number} stop1 Upper bound of the value's current range 7 | * @param {number} start2 Lower bound of the value's target range 8 | * @param {number} stop2 Upper bound of the value's target range 9 | * @return {number} Remapped number 10 | */ 11 | export default function map(value, start1, stop1, start2, stop2) { 12 | return ((value - start1) / (stop1 - start1)) * (stop2 - start2) + start2; 13 | } -------------------------------------------------------------------------------- /src/mixins/FadeTransitionMixin.js: -------------------------------------------------------------------------------- 1 | import { TweenMax, Expo } from 'gsap'; 2 | 3 | /* 4 | * ==== FadeTransitionMixin ==== 5 | */ 6 | 7 | const FadeTransitionMixin = { 8 | 9 | route: { 10 | 11 | activate: function( { next } ) { 12 | 13 | TweenMax.fromTo(this.$el, 0.7, { 14 | opacity: 0 15 | }, { 16 | opacity: 1, 17 | ease: Expo.easeOut 18 | }); 19 | 20 | next(); 21 | }, 22 | 23 | deactivate: function( { next } ) { 24 | 25 | TweenMax.to(this.$el, 0.7, { 26 | opacity: 0, 27 | onComplete: next, 28 | ease: Expo.easeOut 29 | }); 30 | } 31 | 32 | } 33 | 34 | }; 35 | 36 | module.exports = FadeTransitionMixin; 37 | -------------------------------------------------------------------------------- /src/core/Router.js: -------------------------------------------------------------------------------- 1 | import VueRouter from 'vue-router'; 2 | 3 | import HomePageComponent from 'containers/Homepage'; 4 | 5 | Vue.use( VueRouter ); 6 | 7 | class Router extends VueRouter { 8 | 9 | constructor() { 10 | 11 | super({ 12 | hashbang: false, 13 | pushState: true, 14 | history: true, 15 | abstract: false, 16 | saveScrollPosition: false, 17 | transitionOnLoad: false 18 | }); 19 | 20 | this.path = '/'; 21 | this.firstRoute = true; 22 | this.routeTimeout = null; 23 | 24 | 25 | this.map({ 26 | 27 | '*': { 28 | name: "home", 29 | component: HomePageComponent 30 | } 31 | 32 | }); 33 | } 34 | } 35 | 36 | export default new Router; 37 | -------------------------------------------------------------------------------- /src/utils/maths/index.js: -------------------------------------------------------------------------------- 1 | export clamp from './clamp'; 2 | export diagonal from './diagonal'; 3 | export distance from './distance'; 4 | export lerp from './lerp'; 5 | export lightenDarkenColor from './lighten-darken-color'; 6 | export loopIndex from './loop-index'; 7 | export map from './map'; 8 | export normalize from './normalize'; 9 | export parabola from './parabola'; 10 | export randomFloat from './random-float'; 11 | export randomHexColor from './random-hex-color'; 12 | export randomInt from './random-int'; 13 | export shaderParse from './shader-parse'; 14 | export smoothStep from './smooth-step'; 15 | export toType from './to-type'; -------------------------------------------------------------------------------- /src/utils/maths/lighten-darken-color.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Lighten or darken a color 3 | * 4 | * @param {string} col Color 5 | * @param {number} amt Amount 6 | * @return {string} Computed hexadecimal 7 | */ 8 | export default function lightenDarkenColor(col, amt) { 9 | 10 | let usePound = false; 11 | 12 | if (col[0] == "#") { 13 | col = col.slice(1); 14 | usePound = true; 15 | } 16 | 17 | const num = parseInt(col,16); 18 | 19 | let r = (num >> 16) + amt; 20 | 21 | if (r > 255) r = 255; 22 | else if (r < 0) r = 0; 23 | 24 | let b = ((num >> 8) & 0x00FF) + amt; 25 | 26 | if (b > 255) b = 255; 27 | else if (b < 0) b = 0; 28 | 29 | let g = (num & 0x0000FF) + amt; 30 | 31 | if (g > 255) g = 255; 32 | else if (g < 0) g = 0; 33 | 34 | return (usePound?"#":"") + (g | (b << 8) | (r << 16)).toString(16); 35 | } -------------------------------------------------------------------------------- /src/components/Example/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import './styles.scss'; 4 | 5 | import EventManagerMixin from 'mixins/EventManagerMixin'; 6 | 7 | import { 8 | counterIncrement 9 | } from 'vuex/example/actions'; 10 | 11 | import { 12 | countExample 13 | } from 'vuex/example/getters'; 14 | 15 | 16 | export default Vue.extend({ 17 | 18 | mixins: [ EventManagerMixin ], 19 | 20 | template: require( './template.html' ), 21 | 22 | vuex: { 23 | getters: { 24 | count: countExample 25 | }, 26 | actions: { 27 | counterIncrement 28 | } 29 | }, 30 | 31 | emitterEvents: [], 32 | 33 | domEvents: [], 34 | 35 | data() { 36 | 37 | return { 38 | _hidden: null 39 | }; 40 | }, 41 | 42 | ready() {}, 43 | 44 | methods: {}, 45 | 46 | transitions: {}, 47 | 48 | components: {} 49 | }); 50 | -------------------------------------------------------------------------------- /src/core/States.js: -------------------------------------------------------------------------------- 1 | import MobileDetect from 'mobile-detect'; 2 | import browser from 'detect-browser'; 3 | 4 | class States { 5 | 6 | constructor() { 7 | 8 | this.userAgent = window.navigator.userAgent; 9 | this.mobileDetect = new MobileDetect( this.userAgent ); 10 | this.deviceType = this.getDeviceType(); 11 | this.browserName = browser.name; 12 | 13 | this.isDesktop = ( this.deviceType === 'desktop' ); 14 | this.isTablet = ( this.deviceType === 'tablet' ); 15 | this.isMobile = ( this.deviceType === 'mobile' ); 16 | } 17 | 18 | getDeviceType() { 19 | if( this.mobileDetect.tablet() ) { 20 | return "tablet"; 21 | } else if ( this.mobileDetect.mobile() ) { 22 | return "mobile"; 23 | } else { 24 | return "desktop"; 25 | } 26 | } 27 | 28 | isIE() { 29 | return ( this.userAgent.indexOf( 'MSIE ' ) > 0 || this.userAgent.indexOf( 'Trident/' ) > 0 || this.userAgent.indexOf( 'Edge/' ) > 0 ); 30 | } 31 | } 32 | 33 | 34 | export default new States(); 35 | -------------------------------------------------------------------------------- /src/containers/Homepage/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import './styles.scss'; 4 | 5 | import EventManagerMixin from 'mixins/EventManagerMixin'; 6 | import FadeTransitionMixin from 'mixins/FadeTransitionMixin'; 7 | 8 | import { 9 | WINDOW_RESIZE 10 | } from 'config/messages'; 11 | 12 | import ExampleComponent from 'components/Example'; 13 | 14 | export default Vue.extend({ 15 | 16 | mixins: [ EventManagerMixin, FadeTransitionMixin ], 17 | 18 | template: require( './template.html' ), 19 | 20 | emitterEvents: [{ 21 | message: WINDOW_RESIZE, 22 | method: 'onWindowResize' 23 | }], 24 | 25 | data() { 26 | 27 | return { 28 | _hidden: null 29 | }; 30 | }, 31 | 32 | created() { 33 | 34 | }, 35 | 36 | methods: { 37 | 38 | onWindowResize( {width, height} ) { 39 | /*eslint-disable */ 40 | console.log( `Window resize from application with debounce -> width: ${width}px || height: ${ height }` ); 41 | /*eslint-enable */ 42 | } 43 | 44 | }, 45 | 46 | components: { 47 | 'example-component': ExampleComponent 48 | } 49 | }); 50 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © 2016 Heng Patrick 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /src/stylesheets/common/_reset.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | canvas { 45 | padding: 0; 46 | margin: 0; 47 | } 48 | -------------------------------------------------------------------------------- /src/stylesheets/common/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin full-absolute { 2 | position: absolute; 3 | z-index: 0; 4 | top: 0; 5 | right: 0; 6 | bottom: 0; 7 | left: 0; 8 | } 9 | 10 | @mixin center-x { 11 | position: absolute; 12 | left: 50%; 13 | transform: translate3d(-50%, 0%, 0); 14 | } 15 | 16 | @mixin center-y { 17 | position: absolute; 18 | top: 50%; 19 | transform: translate3d(0%, -50%, 0); 20 | } 21 | 22 | @mixin center-xy { 23 | position: absolute; 24 | top: 50%; 25 | left: 50%; 26 | transform: translate3d(-50%, -50%, 0); 27 | } 28 | 29 | @mixin clearfix { 30 | &:after { 31 | display: table; 32 | clear: both; 33 | width: 100%; 34 | height: 1px; 35 | content: ''; 36 | } 37 | } 38 | 39 | @mixin sharp-translate { 40 | -webkit-filter: blur(0); 41 | filter:blur(radius); 42 | -webkit-font-smoothing: subpixel-antialiased; 43 | -webkit-perspective: 1000; 44 | } 45 | 46 | @mixin aspect-ratio($width, $height) { 47 | position: relative; 48 | &:before { 49 | display: block; 50 | width: 100%; 51 | padding-top: ($height / $width) * 100%; 52 | content: ''; 53 | } 54 | > .aspect-ratio { 55 | position: absolute; 56 | top: 0; 57 | right: 0; 58 | bottom: 0; 59 | left: 0; 60 | } 61 | } 62 | 63 | @function ease($key) { 64 | @if map-has-key($ease, $key) { 65 | @return map-get($ease, $key); 66 | } 67 | 68 | @warn "Unkown '#{$key}' in $ease."; 69 | @return null; 70 | } 71 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true 8 | }, 9 | "ecmaFeatures": { 10 | "classes": true, 11 | "forOf": true, 12 | "es6": true, 13 | "blockBindings": true 14 | }, 15 | "rules": { 16 | "indent": [2, 2, {"SwitchCase": 1}], 17 | "semi": 1, 18 | "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 0}], 19 | "no-var": 1 20 | }, 21 | "globals": { 22 | "__DEV__": true, 23 | "__PROD__": true, 24 | "THREE": true, 25 | "React": true, 26 | "Vue": true, 27 | "TimelineLite": false, 28 | "TimelineMax": false, 29 | "TweenLite": false, 30 | "TweenMax": false, 31 | "Back": false, 32 | "Bounce": false, 33 | "Circ": false, 34 | "Cubic": false, 35 | "Ease": false, 36 | "EaseLookup": false, 37 | "Elastic": false, 38 | "Expo": false, 39 | "Linear": false, 40 | "Power0": false, 41 | "Power1": false, 42 | "Power2": false, 43 | "Power3": false, 44 | "Power4": false, 45 | "Quad": false, 46 | "Quart": false, 47 | "Quint": false, 48 | "RoughEase": false, 49 | "Sine": false, 50 | "SlowMo": false, 51 | "SteppedEase": false, 52 | "Strong": false, 53 | "Draggable": false, 54 | "SplitText": false, 55 | "VelocityTracker": false, 56 | "CSSPlugin": false, 57 | "ThrowPropsPlugin": false, 58 | "BezierPlugin": false 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/template/index.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vue Js - Boilerplate 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 | 49 |
50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node*/ 2 | import express from 'express'; 3 | import webpack from 'webpack'; 4 | import history from 'connect-history-api-fallback'; 5 | import portNumber from 'port-number'; 6 | import webpackDevMiddleware from 'webpack-dev-middleware'; 7 | import webpackHotMiddleware from 'webpack-hot-middleware'; 8 | import devConfig from './webpack/webpack.dev.config.babel.js'; 9 | 10 | const env = process.env.NODE_ENV; 11 | const isDeveloping = env !== 'production'; 12 | const port = process.env.PORT ? process.env.PORT : portNumber(); 13 | const ip = isDeveloping ? '0.0.0.0' : '0.0.0.0'; 14 | 15 | const app = express(); 16 | 17 | app.use( history() ); 18 | 19 | if( isDeveloping ) { 20 | 21 | const compiler = webpack( devConfig ); 22 | 23 | app.use( webpackDevMiddleware( compiler, { 24 | publicPath: devConfig.output.publicPath, 25 | headers: { 'Access-Control-Allow-Origin': '*' }, 26 | stats: { 27 | colors: true, 28 | hash: false, 29 | timings: true, 30 | chunks: false, 31 | chunkModules: false, 32 | modules: false 33 | } 34 | }) ); 35 | 36 | app.use( webpackHotMiddleware( compiler ) ); 37 | 38 | // Serve pure static assets 39 | app.use( express.static( './static' ) ); 40 | 41 | } else { 42 | 43 | app.use( express.static( __dirname + '/dist' ) ); 44 | } 45 | 46 | app.listen( port, ip, error => { 47 | if ( error ) throw error; 48 | 49 | /*eslint-disable no-console */ 50 | console.info( `[${env}] Listening on port ${port}. Open up http://${ip}:${port}/ in your browser.` ); 51 | /*eslint-enable no-console */ 52 | }); 53 | -------------------------------------------------------------------------------- /src/helpers/AssetsLoader.js: -------------------------------------------------------------------------------- 1 | import AWDLoader from 'common/utils/AWDLoader'; 2 | import Emitter from 'helpers/Emitter'; 3 | import SoundManager from 'helpers/SoundManager'; 4 | import files from 'config/resources'; 5 | 6 | import { RESOURCES_READY } from 'config/messages'; 7 | 8 | /** 9 | * Loader class 10 | */ 11 | class Loader { 12 | 13 | /** 14 | * constructor function 15 | * @param {array} files Files to load 16 | * @param {function} onResourceLoaded Called everytime a resource is loaded 17 | */ 18 | constructor() { 19 | 20 | this.promises = []; 21 | this.totalProgress = files.length; 22 | this.currentProgress = 0; 23 | 24 | const getLoader = type => { 25 | switch( type ) { 26 | case 'model': return new AWDLoader(); 27 | case 'texture': return new THREE.TextureLoader(); 28 | case 'audio': return SoundManager; 29 | } 30 | }; 31 | 32 | files.map( file => { 33 | 34 | const { type, id, url } = file; 35 | 36 | const promise = new Promise( ( resolve, reject ) => { 37 | 38 | getLoader( type ).load( 39 | url, 40 | resource => { 41 | resolve({ id, resource }); 42 | this.currentProgress++; 43 | }, 44 | () => null, 45 | () => reject, 46 | id 47 | ); 48 | }); 49 | 50 | this.promises.push( promise ); 51 | 52 | }); 53 | } 54 | 55 | /** 56 | * load function 57 | * @return {promise} Promise 58 | */ 59 | load() { 60 | 61 | Emitter.emit( RESOURCES_READY ); 62 | 63 | return Promise.all( this.promises ); 64 | } 65 | 66 | } 67 | 68 | export default new Loader(); 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [ OUTDATED ] VueJs Boilerplate - Babel / Vuex / Webpack / SCSS 2 | 3 | ## Warning 4 | This boilerplate is no longer updated. 5 | 6 | ![Banner](http://i.imgur.com/kJepFbl.png) 7 | 8 | 9 | #### TECHNOLOGIES 10 | 11 | * VueJs 12 | * Vuex 13 | * EventEmitter 14 | * GSAP - TweenMax 15 | * Babel | ES2015 16 | * Webpack 17 | * ESLint 18 | * SCSS 19 | 20 | ##### Install dependancies : 21 | ```shell 22 | npm i 23 | ``` 24 | 25 | 26 | ##### Launch the project : 27 | ```shell 28 | npm start 29 | ``` 30 | 31 | 32 | The project will be launched at http://localhost:3000/ 33 | 34 | 35 | ##### Build for production : 36 | ```shell 37 | npm run build 38 | ``` 39 | 40 | ##### EventManagerMixin 41 | 42 | EventManagerMixin handle Emitter events and DOM events easily. It will auto bind the context, and dettach events when the component will unmount. 43 | 44 | If you want to use the EventManager mixin, you need to define emitterEvents / domEvents in your vue component like so : 45 | 46 | ``` 47 | export default Vue.extend({ 48 | 49 | mixins: [EventManagerMixin], 50 | 51 | emitterEvents: [{ 52 | message: WINDOW_RESIZE, // Event Message 53 | method: 'onWindowResize' // Name of the method 54 | once: false, // Optional - Listen to the event once or not, default: false 55 | }], 56 | 57 | domEvents: [{ 58 | target: document, // DOM element to attach the event 59 | event: 'mousemove', // Event type 60 | method: 'handleDocumentMouseMove' // Name of the method 61 | }], 62 | 63 | ... 64 | } 65 | ``` 66 | 67 | #### CONTRIBUTORS 68 | * [Patrick Heng](http://hengpatrick.fr "Patrick Heng") 69 | * [Fabien Motte](http://fabienmotte.com "Fabien Motte") 70 | * [Nox](https://github.com/3kynox "3kynox") 71 | 72 | 73 | Hope you like it <3 74 | -------------------------------------------------------------------------------- /src/containers/Application/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import EventManagerMixin from 'mixins/EventManagerMixin'; 4 | 5 | import States from 'core/States'; 6 | 7 | import debounce from 'lodash.debounce'; 8 | 9 | import store from 'vuex/store'; 10 | 11 | import { 12 | WINDOW_RESIZE, 13 | WINDOW_ON_FOCUS, 14 | WINDOW_ON_BLUR 15 | } from 'config/messages'; 16 | 17 | export default Vue.extend({ 18 | 19 | store, 20 | 21 | mixins: [EventManagerMixin], 22 | 23 | template: require('./template.html'), 24 | 25 | emitterEvents: [], 26 | 27 | domEvents: [{ 28 | target: window, 29 | event: 'resize', 30 | method: 'handleWindowResize' 31 | },{ 32 | target: window, 33 | event: 'blur', 34 | method: 'handleWindowBlur' 35 | },{ 36 | target: window, 37 | event: 'focus', 38 | method: 'handleWindowFocus' 39 | }], 40 | 41 | data() { 42 | 43 | return { 44 | }; 45 | }, 46 | 47 | ready() { 48 | 49 | this.addDeviceClass(); 50 | this.addBrowserClass(); 51 | }, 52 | 53 | methods: { 54 | 55 | bind() { 56 | 57 | this.handleWindowResize = debounce(this.broadcastWindowSize, 200); 58 | }, 59 | 60 | addBrowserClass() { 61 | this.$el.classList.add(States.browserName + '-browser'); 62 | }, 63 | 64 | addDeviceClass() { 65 | this.$el.classList.add(States.deviceType + '-device'); 66 | }, 67 | 68 | handleWindowBlur() { 69 | this.emitter.emit( WINDOW_ON_BLUR ); 70 | }, 71 | 72 | handleWindowFocus() { 73 | this.emitter.emit( WINDOW_ON_FOCUS ); 74 | }, 75 | 76 | broadcastWindowSize() { 77 | 78 | this.emitter.emit(WINDOW_RESIZE, { 79 | width: window.innerWidth, 80 | height: window.innerHeight 81 | }); 82 | } 83 | } 84 | }); 85 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | 53 | # git config 54 | .gitattributes text 55 | .gitignore text 56 | .gitconfig text 57 | 58 | # code analysis config 59 | .jshintrc text 60 | .jscsrc text 61 | .jshintignore text 62 | .csslintrc text 63 | 64 | # misc config 65 | *.yaml text 66 | *.yml text 67 | .editorconfig text 68 | 69 | # build config 70 | *.npmignore text 71 | *.bowerrc text 72 | 73 | # Heroku 74 | Procfile text 75 | .slugignore text 76 | 77 | # Documentation 78 | *.md text 79 | LICENSE text 80 | AUTHORS text 81 | 82 | 83 | # 84 | ## These files are binary and should be left untouched 85 | # 86 | 87 | # (binary is a macro for -text -diff) 88 | *.png binary 89 | *.jpg binary 90 | *.jpeg binary 91 | *.gif binary 92 | *.ico binary 93 | *.mov binary 94 | *.mp4 binary 95 | *.mp3 binary 96 | *.flv binary 97 | *.fla binary 98 | *.swf binary 99 | *.gz binary 100 | *.zip binary 101 | *.7z binary 102 | *.ttf binary 103 | *.eot binary 104 | *.woff binary 105 | *.pyc binary 106 | *.pdf binary -------------------------------------------------------------------------------- /src/stylesheets/common/_variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * Breakpoints 3 | */ 4 | 5 | $bp-mobile: (min-device-width 320px) (max-device-width 736px); 6 | $bp-mobile-portrait: (min-device-width 320px) (max-device-width 736px) (orientation portrait); 7 | $bp-mobile-landscape: (min-device-width 320px) (max-device-width 736px) (orientation landscape); 8 | 9 | $bp-tablet: (min-device-width 768px) (max-device-width 1024px); 10 | $bp-tablet-portrait: (min-device-width 768px) (max-device-width 1024px) (orientation portrait); 11 | $bp-tablet-landscape: (min-device-width 768px) (max-device-width 1024px) (orientation landscape); 12 | 13 | $bp-tablet-portrait-mobile: (min-device-width 320px) (max-device-width 736px), (min-device-width 768px) (max-device-width 1024px) (orientation portrait); 14 | 15 | $bp-desktop-large: (min-width 1600px); 16 | 17 | /* 18 | * Ease 19 | */ 20 | 21 | $ease: ( 22 | in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530), 23 | in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190), 24 | in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220), 25 | in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060), 26 | in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715), 27 | in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035), 28 | in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335), 29 | in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045), 30 | out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940), 31 | out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000), 32 | out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000), 33 | out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000), 34 | out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000), 35 | out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000), 36 | out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000), 37 | out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275), 38 | in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955), 39 | in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000), 40 | in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000), 41 | in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000), 42 | in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950), 43 | in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000), 44 | in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860), 45 | in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550) 46 | ); 47 | 48 | /* 49 | * Colors 50 | */ 51 | 52 | $color-black: #000000; 53 | $color-white: #FFFFFF; 54 | 55 | $color-green: #4CB084; 56 | 57 | /* 58 | * Fonts 59 | */ 60 | 61 | 62 | /* 63 | * Size 64 | */ 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vue-Js-Webpack-boilerplate", 3 | "version": "1.0.0", 4 | "description": "My vue.js boilerplate using webpack, gsap written in es2015", 5 | "keywords": [ 6 | "vue.js", 7 | "vuejs", 8 | "express", 9 | "webpack", 10 | "sass", 11 | "eslint" 12 | ], 13 | "license": "MIT", 14 | "private": true, 15 | "main": "index.js", 16 | "author": { 17 | "name": "Patrick Heng", 18 | "url": "http://hengpatrick.fr/", 19 | "mail": "hengpatrick.pro@gmail.com" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/patrickheng/vuejs-webpack-boilerplate.git" 24 | }, 25 | "scripts": { 26 | "lint": "eslint .", 27 | "start": "cross-env NODE_ENV=development PORT=3000 babel-node server", 28 | "build": "cross-env NODE_ENV=production webpack --config webpack/webpack.prod.config.babel.js --progress --profile --colors", 29 | "test": "echo \"Error: no test specified\" && exit 1" 30 | }, 31 | "dependencies": { 32 | "detect-browser": "^1.3.1", 33 | "domready": "^1.0.8", 34 | "gsap": "^1.18.5", 35 | "howler": "^1.1.29", 36 | "lodash.debounce": "^4.0.3", 37 | "mobile-detect": "^1.3.2", 38 | "style-loader": "^0.13.0", 39 | "vue": "^1.0.20", 40 | "vue-router": "^0.7.11", 41 | "vuex": "^0.8.2" 42 | }, 43 | "devDependencies": { 44 | "autoprefixer-loader": "^3.2.0", 45 | "babel-cli": "^6.23.0", 46 | "babel-core": "^6.23.1", 47 | "babel-eslint": "^7.1.1", 48 | "babel-loader": "^6.3.2", 49 | "babel-plugin-add-module-exports": "^0.2.1", 50 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 51 | "babel-preset-es2015": "^6.22.0", 52 | "babel-preset-stage-0": "^6.22.0", 53 | "breakpoint-sass": "^2.7.0", 54 | "clean-webpack-plugin": "^0.1.6", 55 | "connect-history-api-fallback": "^1.1.0", 56 | "copy-webpack-plugin": "^1.0.0", 57 | "cross-env": "^3.1.4", 58 | "css-loader": "^0.23.1", 59 | "eslint": "2.4.0", 60 | "eslint-loader": "^1.2.0", 61 | "express": "^4.13.4", 62 | "extract-text-webpack-plugin": "^2.0.0", 63 | "html-loader": "^0.4.3", 64 | "html-webpack-plugin": "^2.7.2", 65 | "ify-loader": "^1.0.3", 66 | "json-loader": "^0.5.4", 67 | "node-sass": "^3.4.2", 68 | "port-number": "^1.0.0", 69 | "raw-loader": "^0.5.1", 70 | "rimraf": "^2.5.1", 71 | "sass-loader": "^3.1.2", 72 | "stats-webpack-plugin": "^0.4.3", 73 | "webpack": "^2.2.1", 74 | "webpack-dev-middleware": "^1.10.1", 75 | "webpack-hot-middleware": "^2.17.1" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/mixins/EventManagerMixin.js: -------------------------------------------------------------------------------- 1 | import Emitter from 'helpers/Emitter'; 2 | 3 | /* 4 | * ==== EventManagerMixin ==== 5 | */ 6 | 7 | const EventManagerMixin = { 8 | 9 | emitterEvents: [], 10 | 11 | domEvents: [], 12 | 13 | created() { 14 | 15 | this.emitter = Emitter; 16 | this.emitterEvents = this.$options.emitterEvents; 17 | this.domEvents = this.$options.domEvents; 18 | 19 | this.bind(); 20 | }, 21 | 22 | ready() { 23 | this.addEventListeners(); 24 | }, 25 | 26 | beforeDestroy() { 27 | this.removeEventListeners(); 28 | }, 29 | 30 | methods: { 31 | 32 | bind() { 33 | 34 | this.emitterEvents.forEach((event) => { 35 | if(typeof this[event.method] === 'undefined') { 36 | /*eslint-disable */ 37 | console.error('Missing method attribute for ', this); 38 | /*eslint-enable */ 39 | } else { 40 | this[event.method] = ::this[event.method]; 41 | } 42 | }); 43 | }, 44 | 45 | addEventListeners() { 46 | 47 | // Add EventEmitter events 48 | this.emitterEvents.forEach((emitterEvent) => { 49 | 50 | if(typeof emitterEvent.once !== 'undefined') { 51 | if(emitterEvent.once) { 52 | this.emitter.once(emitterEvent.message, this[emitterEvent.method]); 53 | } 54 | } else { 55 | 56 | /*eslint-disable */ 57 | if(typeof emitterEvent.message === 'undefined') { 58 | console.error('Missing message attribute for ', emitterEvent); 59 | } else if (typeof this[emitterEvent.method] === 'undefined') { 60 | console.error('Missing method attribute for ', emitterEvent); 61 | } else { 62 | this.emitter.on(emitterEvent.message, this[emitterEvent.method]); 63 | } 64 | /*eslint-enable */ 65 | 66 | } 67 | }); 68 | 69 | // Add DOM events 70 | this.domEvents.forEach((domEvent) => { 71 | 72 | if( typeof domEvent.target === 'undefined') { 73 | domEvent.target = document; 74 | } 75 | 76 | domEvent.target.addEventListener(domEvent.event, this[domEvent.method], false); 77 | }); 78 | }, 79 | 80 | removeEventListeners() { 81 | 82 | // Remove EventEmitter events 83 | this.emitterEvents.forEach((emitterEvent) => { 84 | 85 | this.emitter.off(emitterEvent.message, this[emitterEvent.method]); 86 | }); 87 | 88 | // Remove DOM events 89 | this.domEvents.forEach((domEvent) => { 90 | 91 | domEvent.target.removeEventListener(domEvent.event, this[domEvent.method], false); 92 | }); 93 | } 94 | } 95 | }; 96 | 97 | module.exports = EventManagerMixin; 98 | -------------------------------------------------------------------------------- /webpack/webpack.dev.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | export default { 11 | context: path.resolve(__dirname, '..'), 12 | devtool: 'inline-source-map', 13 | entry: [ 14 | 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=true', 15 | './src/main.js' 16 | ], 17 | output: { 18 | path: __dirname, 19 | publicPath: '/', 20 | filename: 'bundle.js' 21 | }, 22 | resolve: { 23 | modules: [ 24 | resolve('src'), 25 | resolve('node_modules') 26 | ], 27 | alias: { 28 | 'Container': 'helpers/Container' 29 | }, 30 | extensions: [ 31 | '.js', 32 | '.jsx', 33 | '.json' 34 | ] 35 | }, 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.js?$/, 40 | exclude: /node_modules/, 41 | enforce: "pre", 42 | loader: 'eslint-loader' 43 | }, 44 | { 45 | test: /\.html?$/, 46 | exclude: /node_modules/, 47 | loader: 'html-loader' 48 | }, 49 | { 50 | test: /\.js$/, 51 | exclude: /node_modules/, 52 | loader: 'babel-loader', 53 | options: { 54 | "presets": [["es2015", {"modules": false}]] 55 | }, 56 | }, 57 | { 58 | test: /node_modules/, 59 | loader: 'ify-loader' 60 | }, 61 | { 62 | test: /\.css$/, 63 | exclude: /node_modules/, 64 | use: [{ 65 | loader: 'style-loader' 66 | }, 67 | { 68 | loader: 'css-loader' 69 | }] 70 | }, 71 | { 72 | test: /\.scss$/, 73 | exclude: /node_modules/, 74 | use: [{ 75 | loader: 'style-loader' 76 | }, 77 | { 78 | loader: 'css-loader' 79 | }, 80 | { 81 | loader: 'sass-loader' 82 | }] 83 | } 84 | ] 85 | }, 86 | plugins: [ 87 | new HtmlWebpackPlugin({ 88 | template: 'src/template/index.tpl.html', 89 | inject: 'body', 90 | filename: 'index.html' 91 | }), 92 | new webpack.HotModuleReplacementPlugin(), 93 | new webpack.DefinePlugin({ 94 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 95 | '__DEV__': JSON.stringify(true), 96 | '__PROD__': JSON.stringify(false) 97 | }), 98 | new webpack.ProvidePlugin({ 99 | 'Vue': 'vue' 100 | }), 101 | new CopyWebpackPlugin([ 102 | { from: 'static' } 103 | ], 104 | { ignore: ['.DS_Store', '.keep'] }) 105 | ] 106 | }; 107 | -------------------------------------------------------------------------------- /webpack/webpack.prod.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 4 | import CopyWebpackPlugin from 'copy-webpack-plugin'; 5 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 6 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 7 | import StatsWebpackPlugin from 'stats-webpack-plugin'; 8 | 9 | function resolve (dir) { 10 | return path.join(__dirname, '..', dir) 11 | } 12 | 13 | export default { 14 | context: path.resolve(__dirname, '..'), 15 | entry: './src/main.js', 16 | output: { 17 | path: path.join(__dirname, '..', 'dist'), 18 | filename: '[name]-[hash].min.js' 19 | }, 20 | resolve: { 21 | modules: [ 22 | resolve('src'), 23 | resolve('node_modules') 24 | ], 25 | alias: { 26 | 'Container': 'helpers/Container' 27 | }, 28 | extensions: [ 29 | '.js', 30 | '.jsx', 31 | '.json' 32 | ] 33 | }, 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.html?$/, 38 | exclude: /node_modules/, 39 | loader: 'html-loader' 40 | }, 41 | { 42 | test: /\.js$/, 43 | exclude: /node_modules/, 44 | loader: 'babel-loader', 45 | options: { 46 | "presets": [["es2015", {"modules": false}]] 47 | } 48 | }, 49 | { 50 | test: /node_modules/, 51 | loader: 'ify-loader' 52 | }, 53 | { 54 | test: /\.css$/, 55 | use: ExtractTextPlugin.extract({ 56 | fallback: 'style-loader', 57 | use : [{ 58 | loader: 'css-loader', 59 | options: { 60 | plugins: function () { 61 | return [ 62 | require('autoprefixer')({ browsers: 'last 2 versions' }) 63 | ]; 64 | } 65 | } 66 | }] 67 | }) 68 | }, 69 | { 70 | test: /\.scss$/, 71 | use: ExtractTextPlugin.extract({ 72 | fallback: 'style-loader', 73 | use: [{ 74 | loader: 'css-loader', 75 | options: { 76 | plugins: function () { 77 | return [ 78 | require('autoprefixer')({ browsers: 'last 2 versions' }) 79 | ]; 80 | } 81 | } 82 | }, 83 | { 84 | loader: 'sass-loader', 85 | options: { 86 | plugins: function () { 87 | return [ 88 | require('autoprefixer')({ browsers: 'last 2 versions' }) 89 | ]; 90 | } 91 | } 92 | }] 93 | }) 94 | } 95 | ] 96 | }, 97 | plugins: [ 98 | new HtmlWebpackPlugin({ 99 | template: 'src/template/index.tpl.html', 100 | inject: 'body', 101 | filename: 'index.html' 102 | }), 103 | new webpack.NoEmitOnErrorsPlugin(), 104 | new webpack.DefinePlugin({ 105 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), 106 | '__DEV__': JSON.stringify(false), 107 | '__PROD__': JSON.stringify(true) 108 | }), 109 | new webpack.ProvidePlugin({ 110 | 'Vue': 'vue' 111 | }), 112 | new CopyWebpackPlugin([ 113 | { from: 'static' } 114 | ], 115 | { ignore: ['.DS_Store', '.keep'] }), 116 | new webpack.optimize.UglifyJsPlugin({ 117 | compress: { 118 | warnings: true, 119 | drop_console: true, 120 | pure_funcs: ['console.log'] 121 | } 122 | }), 123 | new ExtractTextPlugin({ 124 | filename : '[name]-[hash].min.css', 125 | allChunks: true 126 | }), 127 | new CleanWebpackPlugin(['dist'], { root: path.join(__dirname, '..') }), 128 | new StatsWebpackPlugin('webpack.stats.json') 129 | ] 130 | }; --------------------------------------------------------------------------------