├── .babelrc ├── .browserslistrc ├── .gitignore ├── .htaccess ├── LICENSE ├── README.md ├── configs └── workbox.js ├── dist ├── pwa │ └── manifest.json └── site-icon │ └── app-logo.png ├── package.json ├── postcss.config.js ├── src ├── icons │ └── technology.svg ├── imgs │ └── technology.png ├── js │ ├── App.vue │ ├── components │ │ ├── global │ │ │ ├── Footer.vue │ │ │ ├── Navbar.vue │ │ │ └── NotFound.vue │ │ └── pages │ │ │ └── Main.vue │ ├── index.js │ ├── routes │ │ └── route.js │ └── store │ │ ├── index.js │ │ ├── modules │ │ └── draft.js │ │ └── utils │ │ ├── api.js │ │ └── asyncData.js ├── scss │ ├── components │ │ ├── _global.scss │ │ ├── freq │ │ │ └── _frequest.scss │ │ ├── utils │ │ │ ├── _fonts.scss │ │ │ ├── _media.scss │ │ │ └── _variables.scss │ │ └── vendor │ │ │ └── _bootstrap-grid.scss │ └── index.scss └── template │ └── index.template.ejs ├── webpack.dev.config.js ├── webpack.prod.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "corejs": 3, 7 | "modules": false, 8 | "loose": false, 9 | "targets": { 10 | "browsers": [ 11 | "> 0.4%", 12 | "not IE 11", 13 | "not OperaMini all", 14 | "not IE_Mob 11", 15 | "edge >= 12", 16 | "chrome >= 30", 17 | "Samsung > 9.2", 18 | "chromeandroid >= 58", 19 | "android >= 10", 20 | "ff >= 60", 21 | "safari >= 11.1", 22 | "ios >= 11.1", 23 | "opera >= 62" 24 | ] 25 | } 26 | } 27 | ] 28 | ], 29 | "plugins": [ 30 | [ 31 | "@babel/plugin-transform-runtime", 32 | { 33 | "helpers": false, 34 | "regenerator": true, 35 | "useESModules": false, 36 | "version": "7.0.0-beta.0", 37 | "absolutePath": false 38 | } 39 | ] 40 | ], 41 | "minified": true 42 | } 43 | -------------------------------------------------------------------------------- /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 0.4%, 2 | not IE 11, 3 | not OperaMini all, 4 | not IE_Mob 11, 5 | edge >= 12, 6 | chrome >= 30, 7 | Samsung > 9.2, 8 | chromeandroid >= 58, 9 | android >= 10, 10 | ff >= 60, 11 | safari >= 11.1, 12 | ios >= 11.1, 13 | opera >= 62 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | lib-cov 12 | coverage 13 | .nyc_output 14 | .grunt 15 | bower_components 16 | .lock-wscript 17 | build/Release 18 | node_modules/ 19 | jspm_packages/ 20 | typings/ 21 | .npm 22 | .eslintcache 23 | .node_repl_history 24 | *.tgz 25 | .yarn-integrity 26 | .env 27 | .next 28 | .lock 29 | .idea 30 | *.lock 31 | -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | RewriteBase / 4 | RewriteRule ^index\.html$ - [L] 5 | RewriteCond %{REQUEST_FILENAME} !-f 6 | RewriteCond %{REQUEST_FILENAME} !-d 7 | RewriteRule . /dist/index.html [L] 8 | 9 | 10 | mod_gzip_on Yes 11 | mod_gzip_dechunk Yes 12 | mod_gzip_item_include file \.(html?|txt|css|js|php|pl)$ 13 | mod_gzip_item_include handler ^cgi-script$ 14 | mod_gzip_item_include mime ^text\.* 15 | mod_gzip_item_include mime ^application/x-javascript.* 16 | mod_gzip_item_exclude mime ^image\.* 17 | mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.* 18 | 19 | AddOutputFilterByType DEFLATE text/html 20 | AddOutputFilterByType DEFLATE text/plain 21 | AddOutputFilterByType DEFLATE application/javascript 22 | AddOutputFilterByType DEFLATE application/rss+xml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kenan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vue & PWA ready app template 2 | 3 | ### Main commands 4 | 5 | ```yarn || npm i ( install )``` - dowload dependencies 6 | ```yarn dev || npm run dev``` - Dev mode 7 | ```yarn build || npm run build``` - Build project 8 | ```../imgs/img.png``` - path for imgs ( SCSS ) 9 | 10 | ### Production 11 | Empty vendor bundle size - ~98.9KB min || ~33.9KB GZIP 12 | 13 | Feedback: hello@kenan.agency 14 | 15 | ### TODO 16 | 17 | * [ ] Put loaders into global config 18 | * [ ] Create axios MW for errors 19 | * [ ] Fix postcss config -------------------------------------------------------------------------------- /configs/workbox.js: -------------------------------------------------------------------------------- 1 | const { GenerateSW } = require('workbox-webpack-plugin'); 2 | 3 | const options = { 4 | importWorkboxFrom: 'cdn', 5 | include: [ 6 | /\.html$/, 7 | /\.js$/, 8 | /\.json$/, 9 | /\.(png|jpg|svg|ico|woff|woff2)$/ 10 | ], 11 | precacheManifestFilename: 'wb-manifest.[manifestHash].js', 12 | cacheId: 'my-app', 13 | }; 14 | 15 | module.exports = new GenerateSW(options); -------------------------------------------------------------------------------- /dist/pwa/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PWA Template", 3 | "short_name": "PWA", 4 | "start_url": "/", 5 | "lang": "ru-RU", 6 | "display": "fullscreen", 7 | "orientation": "portrait", 8 | "background_color": "#222", 9 | "theme_color": "#222", 10 | "icons": [ 11 | { 12 | "src": "../site-icon/app-logo.png", 13 | "sizes": "192x192", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "../site-icon/app-logo.png", 18 | "sizes": "128x128", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "../site-icon/app-logo.png", 23 | "sizes": "256x256", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "../site-icon/app-logo.png", 28 | "sizes": "524x524", 29 | "type": "image/png" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /dist/site-icon/app-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayvazov/vue-app-template/cad61b8fae9688f5923fcf292e12bcb5a4a42bfa/dist/site-icon/app-logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kenan.agency", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "", 6 | "scripts": { 7 | "dev": "webpack-dev-server --progress --port 1488 --hot --host 0.0.0.0 --config webpack.dev.config.js", 8 | "build": "webpack --progress --config webpack.prod.config.js", 9 | "babel-upgrade": "babel-upgrade --write --install" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^0.19.1", 15 | "vue": "^2.5.13", 16 | "vue-loader": "^15.3.0", 17 | "vue-router": "^3.0.1", 18 | "vue-template-compiler": "^2.5.13", 19 | "vuex": "^3.1.2", 20 | "workbox-webpack-plugin": "^4.3.1" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.7.5", 24 | "@babel/plugin-proposal-class-properties": "^7.0.0", 25 | "@babel/plugin-proposal-json-strings": "^7.0.0", 26 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 27 | "@babel/plugin-syntax-import-meta": "^7.0.0", 28 | "@babel/plugin-transform-runtime": "^7.0.0", 29 | "@babel/preset-env": "^7.0.0", 30 | "@babel/runtime": "^7.7.6", 31 | "autoprefixer": "^9.4.6", 32 | "babel-loader": "^8.0.0", 33 | "css-loader": "^3.3.2", 34 | "css-mqpacker": "^7.0.0", 35 | "fibers": "^4.0.2", 36 | "file-loader": "^1.1.11", 37 | "html-loader": "^0.5.1", 38 | "html-minifier": "^3.5.21", 39 | "html-webpack-plugin": "^3.2.0", 40 | "img-loader": "^2.0.0", 41 | "mini-css-extract-plugin": "^0.7.0", 42 | "postcss-csso": "^3.0.0", 43 | "postcss-flexbugs-fixes": "^4.1.0", 44 | "postcss-loader": "^3.0.0", 45 | "postcss-mq-keyframes": "^0.3.0", 46 | "sass": "^1.23.7", 47 | "sass-loader": "^7.1.0", 48 | "scss-loader": "^0.0.1", 49 | "style-loader": "^0.23.1", 50 | "svg-loader": "^0.0.2", 51 | "template-html-loader": "^1.0.0", 52 | "uglifyjs-webpack-plugin": "^1.1.5", 53 | "url-loader": "^1.1.2", 54 | "webpack": "^4.15.0", 55 | "webpack-bundle-analyzer": "^3.6.0", 56 | "webpack-cli": "^3.1.0", 57 | "webpack-dev-server": "^3.1.5" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | map: false, 3 | plugins: { 4 | 'postcss-flexbugs-fixes': {}, 5 | 'postcss-mq-keyframes': {}, 6 | 'css-mqpacker': { sort: true }, 7 | 'autoprefixer': { 8 | overrideBrowsersList: [ 9 | "> 0.4%", 10 | "not IE 11", 11 | "not OperaMini all", 12 | "not IE_Mob 11", 13 | "edge >= 12", 14 | "chrome >= 30", 15 | "Samsung > 9.2", 16 | "chromeandroid >= 58", 17 | "android >= 10", 18 | "ff >= 60", 19 | "safari >= 11.1", 20 | "ios >= 11.1", 21 | "opera >= 62" 22 | ], 23 | cascade: false, 24 | }, 25 | 'postcss-csso': { 26 | restructure: true, 27 | sourceMap: false, 28 | usage: null, 29 | comments: 'none' 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/icons/technology.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/imgs/technology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayvazov/vue-app-template/cad61b8fae9688f5923fcf292e12bcb5a4a42bfa/src/imgs/technology.png -------------------------------------------------------------------------------- /src/js/App.vue: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /src/js/components/global/Footer.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/js/components/global/Navbar.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/js/components/global/NotFound.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /src/js/components/pages/Main.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 32 | 33 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import router from './routes/route' 3 | import store from './store/index.js' 4 | import asyncData from './store/utils/asyncData' 5 | 6 | 7 | // router hooks 8 | router.beforeResolve(asyncData({ store, router })); 9 | 10 | 11 | // Vue instance 12 | new Vue({ 13 | el: "#app", 14 | router, 15 | store, 16 | render: h => h('router-view') 17 | }); -------------------------------------------------------------------------------- /src/js/routes/route.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import App from '../App' 4 | import NotFound from '../components/global/NotFound' 5 | import Main from '../components/pages/Main' 6 | 7 | Vue.use(VueRouter); 8 | 9 | export default new VueRouter({ 10 | base: '/', 11 | // remove comments for production tip 12 | // mode: 'history', 13 | routes: [{ 14 | path: '/', 15 | component: App, 16 | children: [ 17 | { 18 | path: '/', 19 | component: Main 20 | } 21 | ] 22 | }, 23 | { 24 | path: '*', 25 | component: NotFound 26 | } 27 | ], 28 | scrollBehavior() { 29 | return { 30 | x: 0, 31 | y: 0 32 | } 33 | } 34 | }); -------------------------------------------------------------------------------- /src/js/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import api from './utils/api'; 4 | 5 | // modules 6 | import draft from './modules/draft' 7 | 8 | Vue.use(Vuex); 9 | 10 | export default new Vuex.Store({ 11 | modules: { 12 | draft 13 | } 14 | }) -------------------------------------------------------------------------------- /src/js/store/modules/draft.js: -------------------------------------------------------------------------------- 1 | export default { 2 | state: { 3 | test: 'correct' 4 | }, 5 | actions: {}, 6 | mutations: {} 7 | // ... 8 | } -------------------------------------------------------------------------------- /src/js/store/utils/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default axios.create({ 4 | baseURL: 'https://your-api-endpoint.com:3000', 5 | }); 6 | -------------------------------------------------------------------------------- /src/js/store/utils/asyncData.js: -------------------------------------------------------------------------------- 1 | export default ({ store, router }) => async (to, from, next) => { 2 | const matched = router.getMatchedComponents(to); 3 | const prevMatched = router.getMatchedComponents(from); 4 | let diffed = false; 5 | 6 | const activated = matched.filter((c, i) => { 7 | const isActivated = diffed || (diffed = (prevMatched[i] !== c)); 8 | return isActivated; 9 | }); 10 | 11 | if (!activated.length) { 12 | // push last match for loading asyncData 13 | activated.push(matched[matched.length - 1]); 14 | } 15 | 16 | const asyncDataHooks = [router.app.$options, ...activated] 17 | .filter((_) => _) 18 | .map((c) => c.asyncData) 19 | .filter((_) => _); 20 | 21 | if (!asyncDataHooks.length) { 22 | next(); 23 | return; 24 | } 25 | 26 | try { 27 | const hooks = asyncDataHooks 28 | .map((hook) => hook({ store, router, route: to })) 29 | .filter((_) => _); 30 | 31 | await Promise.all(hooks); 32 | next(); 33 | } catch (error) { 34 | if (process.env.NODE_ENV !== 'production') { 35 | console.warn('Error while asyncData:'); 36 | console.error(error); 37 | } 38 | 39 | next(); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /src/scss/components/_global.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | width: 100%; 4 | height: 100%; 5 | margin: 0; 6 | font-size: 14px; 7 | box-sizing: border-box; 8 | color: #fff; 9 | font-family: -apple-system, BlinkMacSystemFont, sans-serif !important; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | scroll-behavior: smooth; 13 | -webkit-overflow-scrolling: touch; 14 | } 15 | 16 | ul { 17 | padding: 0; 18 | margin: 0; 19 | } 20 | 21 | ul li { 22 | list-style-type: none; 23 | } 24 | 25 | a { 26 | text-decoration: none; 27 | } 28 | 29 | input, 30 | button, 31 | textarea, 32 | select, 33 | option { 34 | &:focus { 35 | outline: none; 36 | } 37 | } 38 | 39 | input, 40 | button, 41 | textarea { 42 | border-radius: 0; 43 | -webkit-appearance: none; 44 | -moz-appearance: none; 45 | 46 | &::-ms-expand { 47 | display: none; 48 | } 49 | } 50 | 51 | // Demo styles 52 | body { 53 | display: flex; 54 | justify-content: center; 55 | align-items: center; 56 | } 57 | 58 | p { 59 | color: #000; 60 | font-size: 24px; 61 | text-decoration: underline; 62 | } 63 | 64 | // Example 65 | .png { 66 | width: 120px; 67 | height: 120px; 68 | background: url('../imgs/technology.png') no-repeat center / cover; 69 | } 70 | -------------------------------------------------------------------------------- /src/scss/components/freq/_frequest.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayvazov/vue-app-template/cad61b8fae9688f5923fcf292e12bcb5a4a42bfa/src/scss/components/freq/_frequest.scss -------------------------------------------------------------------------------- /src/scss/components/utils/_fonts.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayvazov/vue-app-template/cad61b8fae9688f5923fcf292e12bcb5a4a42bfa/src/scss/components/utils/_fonts.scss -------------------------------------------------------------------------------- /src/scss/components/utils/_media.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayvazov/vue-app-template/cad61b8fae9688f5923fcf292e12bcb5a4a42bfa/src/scss/components/utils/_media.scss -------------------------------------------------------------------------------- /src/scss/components/utils/_variables.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kayvazov/vue-app-template/cad61b8fae9688f5923fcf292e12bcb5a4a42bfa/src/scss/components/utils/_variables.scss -------------------------------------------------------------------------------- /src/scss/components/vendor/_bootstrap-grid.scss: -------------------------------------------------------------------------------- 1 | html{-webkit-box-sizing:border-box;box-sizing:border-box;-ms-overflow-style:scrollbar}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}.container{position:relative;margin-left:auto;margin-right:auto;padding-right:10px;padding-left:10px}@media (min-width:576px){.container{padding-right:10px;padding-left:10px}}@media (min-width:768px){.container{padding-right:10px;padding-left:10px}}@media (min-width:992px){.container{padding-right:10px;padding-left:10px}}@media (min-width:1200px){.container{padding-right:10px;padding-left:10px}}@media (min-width:576px){.container{width:540px;max-width:100%}}@media (min-width:768px){.container{width:720px;max-width:100%}}@media (min-width:992px){.container{width:960px;max-width:100%}}@media (min-width:1200px){.container{width:1280px;max-width:100%}}.container-fluid{position:relative;margin-left:auto;margin-right:auto;padding-right:10px;padding-left:10px}@media (min-width:576px){.container-fluid{padding-right:10px;padding-left:10px}}@media (min-width:768px){.container-fluid{padding-right:10px;padding-left:10px}}@media (min-width:992px){.container-fluid{padding-right:10px;padding-left:10px}}@media (min-width:1200px){.container-fluid{padding-right:10px;padding-left:10px}}.row{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-10px;margin-left:-10px}@media (min-width:576px){.row{margin-right:-10px;margin-left:-10px}}@media (min-width:768px){.row{margin-right:-10px;margin-left:-10px}}@media (min-width:992px){.row{margin-right:-10px;margin-left:-10px}}@media (min-width:1200px){.row{margin-right:-10px;margin-left:-10px}}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{position:relative;width:100%;min-height:1px;padding-right:10px;padding-left:10px}@media (min-width:576px){.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{padding-right:10px;padding-left:10px}}@media (min-width:768px){.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{padding-right:10px;padding-left:10px}}@media (min-width:992px){.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{padding-right:10px;padding-left:10px}}@media (min-width:1200px){.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9{padding-right:10px;padding-left:10px}}.col{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-0{right:auto}.pull-1{right:8.333333%}.pull-2{right:16.666667%}.pull-3{right:25%}.pull-4{right:33.333333%}.pull-5{right:41.666667%}.pull-6{right:50%}.pull-7{right:58.333333%}.pull-8{right:66.666667%}.pull-9{right:75%}.pull-10{right:83.333333%}.pull-11{right:91.666667%}.pull-12{right:100%}.push-0{left:auto}.push-1{left:8.333333%}.push-2{left:16.666667%}.push-3{left:25%}.push-4{left:33.333333%}.push-5{left:41.666667%}.push-6{left:50%}.push-7{left:58.333333%}.push-8{left:66.666667%}.push-9{left:75%}.push-10{left:83.333333%}.push-11{left:91.666667%}.push-12{left:100%}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-sm-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-sm-0{right:auto}.pull-sm-1{right:8.333333%}.pull-sm-2{right:16.666667%}.pull-sm-3{right:25%}.pull-sm-4{right:33.333333%}.pull-sm-5{right:41.666667%}.pull-sm-6{right:50%}.pull-sm-7{right:58.333333%}.pull-sm-8{right:66.666667%}.pull-sm-9{right:75%}.pull-sm-10{right:83.333333%}.pull-sm-11{right:91.666667%}.pull-sm-12{right:100%}.push-sm-0{left:auto}.push-sm-1{left:8.333333%}.push-sm-2{left:16.666667%}.push-sm-3{left:25%}.push-sm-4{left:33.333333%}.push-sm-5{left:41.666667%}.push-sm-6{left:50%}.push-sm-7{left:58.333333%}.push-sm-8{left:66.666667%}.push-sm-9{left:75%}.push-sm-10{left:83.333333%}.push-sm-11{left:91.666667%}.push-sm-12{left:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-md-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-md-0{right:auto}.pull-md-1{right:8.333333%}.pull-md-2{right:16.666667%}.pull-md-3{right:25%}.pull-md-4{right:33.333333%}.pull-md-5{right:41.666667%}.pull-md-6{right:50%}.pull-md-7{right:58.333333%}.pull-md-8{right:66.666667%}.pull-md-9{right:75%}.pull-md-10{right:83.333333%}.pull-md-11{right:91.666667%}.pull-md-12{right:100%}.push-md-0{left:auto}.push-md-1{left:8.333333%}.push-md-2{left:16.666667%}.push-md-3{left:25%}.push-md-4{left:33.333333%}.push-md-5{left:41.666667%}.push-md-6{left:50%}.push-md-7{left:58.333333%}.push-md-8{left:66.666667%}.push-md-9{left:75%}.push-md-10{left:83.333333%}.push-md-11{left:91.666667%}.push-md-12{left:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-lg-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-lg-0{right:auto}.pull-lg-1{right:8.333333%}.pull-lg-2{right:16.666667%}.pull-lg-3{right:25%}.pull-lg-4{right:33.333333%}.pull-lg-5{right:41.666667%}.pull-lg-6{right:50%}.pull-lg-7{right:58.333333%}.pull-lg-8{right:66.666667%}.pull-lg-9{right:75%}.pull-lg-10{right:83.333333%}.pull-lg-11{right:91.666667%}.pull-lg-12{right:100%}.push-lg-0{left:auto}.push-lg-1{left:8.333333%}.push-lg-2{left:16.666667%}.push-lg-3{left:25%}.push-lg-4{left:33.333333%}.push-lg-5{left:41.666667%}.push-lg-6{left:50%}.push-lg-7{left:58.333333%}.push-lg-8{left:66.666667%}.push-lg-9{left:75%}.push-lg-10{left:83.333333%}.push-lg-11{left:91.666667%}.push-lg-12{left:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-webkit-flex-basis:0;-ms-flex-preferred-size:0;flex-basis:0;-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-xl-1{-webkit-box-flex:0;-webkit-flex:0 0 8.333333%;-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-webkit-box-flex:0;-webkit-flex:0 0 16.666667%;-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-webkit-box-flex:0;-webkit-flex:0 0 33.333333%;-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-webkit-box-flex:0;-webkit-flex:0 0 41.666667%;-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-webkit-box-flex:0;-webkit-flex:0 0 58.333333%;-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-webkit-box-flex:0;-webkit-flex:0 0 66.666667%;-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-webkit-box-flex:0;-webkit-flex:0 0 75%;-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-webkit-box-flex:0;-webkit-flex:0 0 83.333333%;-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-webkit-box-flex:0;-webkit-flex:0 0 91.666667%;-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.pull-xl-0{right:auto}.pull-xl-1{right:8.333333%}.pull-xl-2{right:16.666667%}.pull-xl-3{right:25%}.pull-xl-4{right:33.333333%}.pull-xl-5{right:41.666667%}.pull-xl-6{right:50%}.pull-xl-7{right:58.333333%}.pull-xl-8{right:66.666667%}.pull-xl-9{right:75%}.pull-xl-10{right:83.333333%}.pull-xl-11{right:91.666667%}.pull-xl-12{right:100%}.push-xl-0{left:auto}.push-xl-1{left:8.333333%}.push-xl-2{left:16.666667%}.push-xl-3{left:25%}.push-xl-4{left:33.333333%}.push-xl-5{left:41.666667%}.push-xl-6{left:50%}.push-xl-7{left:58.333333%}.push-xl-8{left:66.666667%}.push-xl-9{left:75%}.push-xl-10{left:83.333333%}.push-xl-11{left:91.666667%}.push-xl-12{left:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}} 2 | -------------------------------------------------------------------------------- /src/scss/index.scss: -------------------------------------------------------------------------------- 1 | // Utils 2 | @import "components/utils/fonts"; 3 | @import "components/utils/variables"; 4 | // Bootstrap ( can delete if you dont use ) 5 | @import "components/vendor/bootstrap-grid"; 6 | // Frequest 7 | @import "components/freq/frequest"; 8 | // Global styles 9 | @import "components/global"; 10 | // Media rules 11 | @import "components/utils/media"; -------------------------------------------------------------------------------- /src/template/index.template.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= htmlWebpackPlugin.options.meta.title %> 5 | 6 | 7 | 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 | 55 | 56 | 57 | 63 |
64 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /webpack.dev.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const TerserJSPlugin = require('terser-webpack-plugin'); 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 8 | // config parts 9 | const Workbox = require('./configs/workbox.js'); 10 | 11 | module.exports = { 12 | entry: [ 13 | "./src/js/index.js", 14 | './src/scss/index.scss' 15 | ], 16 | output: { 17 | path: path.resolve(__dirname, 'dist'), 18 | filename: 'js/[name].js' 19 | }, 20 | devServer: { 21 | compress: true, 22 | historyApiFallback: true, 23 | //https: true for https 24 | }, 25 | optimization: { 26 | minimizer: [new TerserJSPlugin({})], 27 | splitChunks: { 28 | cacheGroups: { 29 | // js splitting 30 | vendor: { 31 | name: 'vendor', 32 | test: /node_modules/, 33 | chunks: 'all', 34 | enforce: true 35 | }, 36 | global: { 37 | name: 'global', 38 | test: /global/, 39 | chunks: 'all', 40 | enforce: true 41 | }, 42 | pages: { 43 | name: 'pages', 44 | test: /pages/, 45 | chunks: 'all', 46 | enforce: true 47 | }, 48 | // css splitting 49 | // future :) 50 | } 51 | } 52 | }, 53 | mode: 'development', 54 | module: { 55 | rules: [ 56 | { 57 | test: /\.vue$/, 58 | exclude: /node_modules/, 59 | loader: 'vue-loader', 60 | options: { 61 | loaders: { 62 | css: '' 63 | } 64 | } 65 | }, 66 | { 67 | test: /\.css$/, 68 | use: ['style-loader', 'css-loader'] 69 | }, 70 | { 71 | test: /\.(sa|sc|c)ss$/, 72 | use: [ 73 | { 74 | loader: MiniCssExtractPlugin.loader, 75 | options: { 76 | publicPath: '../', 77 | hmr: true, 78 | // force reload 79 | reloadAll: true, 80 | } 81 | }, 82 | // работаем с @import и url() 83 | { 84 | loader: 'css-loader' 85 | }, 86 | // далее, по .css файлу проходиться postcss-loader да бы, проставить все нужные полифиллы и префиксы к свойствам 87 | 'postcss-loader', 88 | // в начале sass-loader переводить .scss файл в .css 89 | { 90 | loader: 'sass-loader', 91 | options: { 92 | implementation: require('sass'), 93 | sassOptions: { 94 | fiber: require('fibers'), 95 | indentedSyntax: true // optional 96 | }, 97 | } 98 | } 99 | ] 100 | }, 101 | { 102 | test: /\.js?$/, 103 | exclude: /node_modules/, 104 | loader: 'babel-loader' 105 | }, 106 | { 107 | test: /\.(woff|woff2)$/, 108 | loader: 'url-loader', 109 | options: { 110 | limit: 1, 111 | publicPath: 'fonts', 112 | name: '[name].[sha1:hash:base64:5].[ext]' 113 | }, 114 | }, 115 | { 116 | test: /\.(png|jpg)$/, 117 | loader: 'file-loader', 118 | options: { 119 | outputPath: 'dist/imgs', 120 | name: '[name].[ext]' 121 | }, 122 | }, 123 | { 124 | test: /\.(svg)$/, 125 | loader: 'url-loader', 126 | options: { 127 | limit: 1, 128 | name: 'dist/icons/[name].[sha1:hash:base64:5].[ext]' 129 | }, 130 | }, 131 | ] 132 | }, 133 | plugins: [ 134 | new MiniCssExtractPlugin({ 135 | filename: 'css/main.[hash:4].css', 136 | chunkFilename: 'css/[name].[hash:4].css' 137 | }), 138 | new VueLoaderPlugin(), 139 | new webpack.NoEmitOnErrorsPlugin(), 140 | new webpack.HotModuleReplacementPlugin(), 141 | new HtmlWebpackPlugin({ 142 | filename: './index.html', 143 | template: './src/template/index.template.ejs', 144 | children: false, 145 | // template info 146 | manifest: './dist/pwa/manifest.json', 147 | icon: { 148 | shortcut: './dist/site-icon/app-logo.png', 149 | apple: { 150 | '57x57': './dist/site-icon/app-logo.png', 151 | '60x60': './dist/site-icon/app-logo.png', 152 | '72x72': './dist/site-icon/app-logo.png', 153 | '76x76': './dist/site-icon/app-logo.png', 154 | '114x114': './dist/site-icon/app-logo.png', 155 | '120x120': './dist/site-icon/app-logo.png', 156 | '144x144': './dist/site-icon/app-logo.png', 157 | '152x152': './dist/site-icon/app-logo.png', 158 | '180x180': './dist/site-icon/app-logo.png', 159 | } 160 | }, 161 | meta: { 162 | description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur at blanditiis consectetur cupiditate dignissimos esse, fugiat illum laudantium nihil, nisi numquam obcaecati officiis optio placeat, quas quasi sequi soluta voluptatibus?', 163 | title: 'Vue & PWA app', 164 | url: '//your-site-link.com' 165 | } 166 | }), 167 | new webpack.ProvidePlugin({ 168 | $: "jquery", 169 | jQuery: "jquery", 170 | "window.jQuery": "jquery" 171 | }), 172 | new UglifyJsPlugin({ 173 | uglifyOptions: { 174 | ie8: false, 175 | ecma: 8, 176 | output: { 177 | comments: false, 178 | beautify: false 179 | } 180 | } 181 | }), 182 | //Workbox 183 | ], 184 | resolve: { 185 | extensions: [".js", ".vue", ".json"], 186 | alias: { 187 | vue: 'vue/dist/vue.runtime.min.js', 188 | 'vue-router': 'vue-router/dist/vue-router.min.js', 189 | 'vuex': 'vuex/dist/vuex.min.js', 190 | 'images': path.resolve(__dirname, './src/imgs'), 191 | 'icons': path.resolve(__dirname, './src/icons') 192 | } 193 | }, 194 | node: { 195 | module: 'empty', 196 | fsevents: 'empty', 197 | net: 'empty', 198 | tls: 'empty' 199 | } 200 | }; 201 | -------------------------------------------------------------------------------- /webpack.prod.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const VueLoaderPlugin = require('vue-loader/lib/plugin'); 4 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 6 | const TerserJSPlugin = require('terser-webpack-plugin'); 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 8 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 9 | // config parts 10 | const Workbox = require('./configs/workbox.js'); 11 | 12 | const PUBLIC_PATH = 'URL'; 13 | 14 | module.exports = { 15 | entry: [ 16 | "./src/js/index.js", 17 | './src/scss/index.scss' 18 | ], 19 | output: { 20 | path: path.resolve(__dirname, './dist'), 21 | filename: "js/[name].[hash:5].js", 22 | }, 23 | optimization: { 24 | minimizer: [new TerserJSPlugin({})], 25 | splitChunks: { 26 | cacheGroups: { 27 | vendor: { 28 | name: 'vendor', 29 | test: /node_modules/, 30 | chunks: 'all', 31 | enforce: true 32 | }, 33 | global: { 34 | name: 'global', 35 | test: /global/, 36 | chunks: 'all', 37 | enforce: true 38 | }, 39 | pages: { 40 | name: 'pages', 41 | test: /pages/, 42 | chunks: 'all', 43 | enforce: true 44 | } 45 | } 46 | } 47 | }, 48 | mode: 'production', 49 | module: { 50 | rules: [ 51 | { 52 | test: /\.vue$/, 53 | exclude: /node_modules/, 54 | loader: 'vue-loader', 55 | }, 56 | { 57 | test: /\.css$/i, 58 | use: [ 59 | { 60 | loader: MiniCssExtractPlugin.loader, 61 | options: { 62 | publicPath: '../', 63 | } 64 | }, 65 | 'css-loader' 66 | ] 67 | }, 68 | { 69 | test: /\.s(c|a)ss$/, 70 | use: [ 71 | { 72 | loader: MiniCssExtractPlugin.loader, 73 | options: { 74 | publicPath: '../', 75 | } 76 | }, 77 | // работаем с @import и url() 78 | { 79 | loader: 'css-loader', 80 | options: { 81 | modules: 'global' 82 | } 83 | }, 84 | // далее, по .css файлу проходиться postcss-loader да бы, проставить все нужные полифиллы и префиксы к свойствам 85 | 'postcss-loader', 86 | // в начале sass-loader переводить .scss файл в .css 87 | { 88 | loader: 'sass-loader', 89 | options: { 90 | implementation: require('sass'), 91 | sassOptions: { 92 | fiber: require('fibers'), 93 | indentedSyntax: true // optional 94 | }, 95 | } 96 | } 97 | ] 98 | }, 99 | { 100 | test: /\.js?$/, 101 | exclude: /node_modules/, 102 | loader: 'babel-loader' 103 | }, 104 | // fonts 105 | { 106 | test: /\.(woff|woff2)$/, 107 | loader: 'url-loader', 108 | options: { 109 | limit: 1, 110 | publicPath: 'fonts', 111 | outputPath: 'fonts', 112 | name: '[name].[sha1:hash:base64:5].[ext]' 113 | }, 114 | }, 115 | { 116 | test: /\.(svg)$/, 117 | loader: 'url-loader', 118 | options: { 119 | limit: 1, 120 | publicPath: 'dist/icons', 121 | outputPath: 'icons', 122 | name: '[name].[sha1:hash:base64:5].[ext]' 123 | }, 124 | }, 125 | { 126 | test: /\.(png|jpg)$/, 127 | loader: 'url-loader', 128 | options: { 129 | limit: 1, 130 | publicPath: 'dist/imgs', 131 | outputPath: 'imgs', 132 | name: '[name].[sha1:hash:base64:5].[ext]' 133 | }, 134 | } 135 | ] 136 | }, 137 | plugins: [ 138 | new MiniCssExtractPlugin({ 139 | filename: 'css/main.[hash:4].css', 140 | }), 141 | 142 | new VueLoaderPlugin(), 143 | new webpack.NoEmitOnErrorsPlugin(), 144 | new HtmlWebpackPlugin({ 145 | minify: { 146 | removeComments: true, 147 | collapseWhitespace: true 148 | }, 149 | filename: '../index.html', 150 | template: './src/template/index.template.ejs', 151 | children: false, 152 | // template info 153 | manifest: './dist/pwa/manifest.json', 154 | icon: { 155 | shortcut: './dist/site-icon/app-logo.png', 156 | apple: { 157 | '57x57': './dist/site-icon/app-logo.png', 158 | '60x60': './dist/site-icon/app-logo.png', 159 | '72x72': './dist/site-icon/app-logo.png', 160 | '76x76': './dist/site-icon/app-logo.png', 161 | '114x114': './dist/site-icon/app-logo.png', 162 | '120x120': './dist/site-icon/app-logo.png', 163 | '144x144': './dist/site-icon/app-logo.png', 164 | '152x152': './dist/site-icon/app-logo.png', 165 | '180x180': './dist/site-icon/app-logo.png', 166 | } 167 | }, 168 | meta: { 169 | description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur at blanditiis consectetur cupiditate dignissimos esse, fugiat illum laudantium nihil, nisi numquam obcaecati officiis optio placeat, quas quasi sequi soluta voluptatibus?', 170 | title: 'Vue & PWA app', 171 | url: '//your-site-link.com' 172 | } 173 | }), 174 | new webpack.ProvidePlugin({ 175 | $: "jquery", 176 | jQuery: "jquery", 177 | "window.jQuery": "jquery" 178 | }), 179 | new UglifyJsPlugin({ 180 | uglifyOptions: { 181 | ie8: false, 182 | ecma: 8, 183 | output: { 184 | comments: false, 185 | beautify: false 186 | } 187 | } 188 | }), 189 | //new BundleAnalyzerPlugin(), 190 | //Workbox 191 | ], 192 | resolve: { 193 | extensions: [".js", ".vue", ".json"], 194 | alias: { 195 | vue: 'vue/dist/vue.runtime.min.js', 196 | 'vue-router': 'vue-router/dist/vue-router.min.js', 197 | 'vuex': 'vuex/dist/vuex.min.js', 198 | 'images': path.resolve(__dirname, './src/imgs'), 199 | 'icons': path.resolve(__dirname, './src/icons') 200 | } 201 | }, 202 | node: { 203 | module: 'empty', 204 | fsevents: 'empty', 205 | net: 'empty', 206 | tls: 'empty', 207 | setImmediate: false, 208 | process: false 209 | } 210 | }; 211 | --------------------------------------------------------------------------------