├── .browserslistrc ├── .eslintrc.js ├── .gitignore ├── Lazy Way To Performance.pptx ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── Bella.jpg │ ├── DaisyMae.jpg │ ├── logo.png │ ├── profile.jpg │ └── styles.less ├── components │ ├── bella.vue │ ├── daisy.vue │ └── dog.vue ├── main.js ├── router │ └── index.js └── views │ ├── About.vue │ └── Home.vue └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | 'extends': [ 7 | 'plugin:vue/essential', 8 | 'eslint:recommended' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /Lazy Way To Performance.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kb-coder/lazydemo/9f404353922b29d78ce62744a6f90a3abcd8389b/Lazy Way To Performance.pptx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lazydemo 2 | Watch the full presentation on [Vue Mastery](https://www.vuemastery.com/conferences/vueconf-us-2020/the-lazy-way-to-better-performance/). 3 | 4 | This is a simple demo showing how to: 5 | 1. Lazy Load Components in Vue 2 6 | 2. Lazy Load Routes in Vue 2 7 | 8 | **Bonus:** vue.config.js shows how to split out vendor's chunk 9 | 10 | ## Lazy Load Components 11 | In the /views/home.vue component, Bella is shown by default so we can lazy load the Daisy component since it's not visible on the initial page load. 12 | 13 | What to Lazy Load? Anything that isn't visible on initial page load. (using v-if) 14 | - Tabs (other than the default) 15 | - Side Panel 16 | - Modals 17 | 18 | 19 | ## Lazy Load Routes 20 | In the /router/index.js, the About page is already lazy loaded. 21 | 22 | Typcially, there's not much value in lazy loading the home page, since it is the entry point of the application. If you have multiple entry points, then you may want to lazy load those. 23 | 24 | To Lazy Load the Bella and Daisy routes, just change the import to a dynamic import. Change this: 25 | ``` 26 | import Bella from '@/components/bella' 27 | ``` 28 | to this: 29 | ``` 30 | const Bella = () => import('@/components/bella') 31 | ``` 32 | 33 | ### Specifying Webpack Chunk Name 34 | 1. Babel.config.js must have comments to true for this to work. 35 | 2. Add the webpack magic comment to the dynamic import 36 | 37 | ``` 38 | const Bella = () => import(/*webpackChunkName: "dogs" */ '@/components/bella') 39 | ``` 40 | 41 | ## Project setup 42 | ``` 43 | npm install 44 | ``` 45 | 46 | ### Compiles and hot-reloads for development 47 | ``` 48 | npm run dev 49 | ``` 50 | 51 | ### Compiles and minifies for production 52 | ``` 53 | npm run build 54 | ``` 55 | 56 | ### Lints and fixes files 57 | ``` 58 | npm run lint 59 | ``` 60 | 61 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { 4 | useBuiltIns: 'usage', 5 | modules: false, // this setting needed for tree shaking (especially with lodash) 6 | corejs: 3 7 | }] 8 | ], 9 | plugins: ['@babel/transform-runtime'], 10 | comments: true // if this is false, then webpackChunkName won't be used. 11 | // If we were using unit tests in this project, this is how it should be set up for mocha or jest 12 | // env: { 13 | // test: { 14 | // presets: ['@babel/preset-env'], 15 | // plugins: ['dynamic-import-node'] 16 | // } 17 | // } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazydemo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "analyze": "vue-cli-service build --report", 7 | "serve": "vue-cli-service serve --open --modern --port 44321", 8 | "build": "vue-cli-service build --modern --mode production", 9 | "lint": "vue-cli-service lint", 10 | "dev-build": "vue-cli-service build --mode development", 11 | "dev": "npm run dev-build && npm run serve", 12 | "start": "npm run dev" 13 | }, 14 | "dependencies": { 15 | "core-js": "^3.6.5", 16 | "moment": "2.24.0", 17 | "vue": "^2.6.11", 18 | "vue-router": "^3.1.5" 19 | }, 20 | "devDependencies": { 21 | "@babel/plugin-transform-runtime": "^7.12.17", 22 | "@babel/runtime-corejs3": "^7.8.4", 23 | "@vue/cli-plugin-babel": "^4.5.11", 24 | "@vue/cli-plugin-eslint": "^4.5.11", 25 | "@vue/cli-plugin-router": "~4.2.0", 26 | "@vue/cli-service": "~4.2.0", 27 | "@vue/preload-webpack-plugin": "1.1.1", 28 | "babel-eslint": "^10.0.3", 29 | "eslint": "^6.7.2", 30 | "eslint-plugin-vue": "^6.2.2", 31 | "less": "3.11.1", 32 | "less-loader": "5.0.0", 33 | "moment-locales-webpack-plugin": "1.1.2", 34 | "vue-template-compiler": "^2.6.11" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kb-coder/lazydemo/9f404353922b29d78ce62744a6f90a3abcd8389b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 15 | 28 | 50 | -------------------------------------------------------------------------------- /src/assets/Bella.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kb-coder/lazydemo/9f404353922b29d78ce62744a6f90a3abcd8389b/src/assets/Bella.jpg -------------------------------------------------------------------------------- /src/assets/DaisyMae.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kb-coder/lazydemo/9f404353922b29d78ce62744a6f90a3abcd8389b/src/assets/DaisyMae.jpg -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kb-coder/lazydemo/9f404353922b29d78ce62744a6f90a3abcd8389b/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/profile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kb-coder/lazydemo/9f404353922b29d78ce62744a6f90a3abcd8389b/src/assets/profile.jpg -------------------------------------------------------------------------------- /src/assets/styles.less: -------------------------------------------------------------------------------- 1 | img { 2 | object-fit: cover; 3 | object-position: 100% 0; 4 | width: 200px; 5 | height: 237px; 6 | } 7 | 8 | .row { 9 | display: flex; 10 | flex-direction: row; 11 | flex-wrap: wrap; 12 | width: 98%; 13 | padding: 10px; 14 | } 15 | .column { 16 | display: flex; 17 | flex-direction: column; 18 | align-items: center; 19 | justify-content: center; 20 | flex-basis: 100%; 21 | flex: 1; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/bella.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 27 | -------------------------------------------------------------------------------- /src/components/daisy.vue: -------------------------------------------------------------------------------- 1 | 11 | 26 | -------------------------------------------------------------------------------- /src/components/dog.vue: -------------------------------------------------------------------------------- 1 | 14 | 38 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | 5 | Vue.config.productionTip = false 6 | 7 | new Vue({ 8 | router, 9 | render: h => h(App) 10 | }).$mount('#app') 11 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '@/views/Home.vue' 4 | // import Bella from '@/components/bella' 5 | const Bella = () => import(/* webpackChunkName: "dogs" */ '@/components/bella') 6 | // import Daisy from '@/components/daisy' 7 | const Daisy = () => import(/* webpackChunkName: "dogs" */ '@/components/daisy') 8 | const About = () => import(/* webpackChunkName: "about" */ '@/views/About.vue') 9 | 10 | Vue.use(VueRouter) 11 | 12 | const routes = [ 13 | { 14 | path: '/', 15 | name: 'Home', 16 | component: Home 17 | }, 18 | { 19 | path: '/about', 20 | name: 'About', 21 | component: About 22 | }, 23 | { 24 | path: '/bella-bella', 25 | name: 'BellaBella', 26 | component: Bella 27 | }, 28 | { 29 | path: '/daisy-mae', 30 | name: 'DaisyMae', 31 | component: Daisy 32 | } 33 | ] 34 | 35 | const router = new VueRouter({ 36 | routes 37 | }) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 25 | 34 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 43 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const MomentLocalesPlugin = require('moment-locales-webpack-plugin') // for moment tree shaking locales 3 | const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin') 4 | 5 | module.exports = { 6 | publicPath: '/', 7 | runtimeCompiler: true, 8 | productionSourceMap: process.env.NODE_ENV !== 'production', 9 | crossorigin: 'use-credentials', 10 | lintOnSave: true, 11 | configureWebpack: config => { 12 | if (process.env.NODE_ENV === 'development') { 13 | config.devtool = 'source-map' 14 | } else if (process.env.NODE_ENV === 'test') { 15 | config.devtool = 'cheap-module-eval-source-map' 16 | } 17 | }, 18 | chainWebpack: config => { 19 | // for moment tree shaking locales 20 | config.resolve.alias 21 | .set('moment', 'moment/moment.js') 22 | 23 | // these settings apply to production or development, but will break mocha tests in the test NODE_ENV 24 | if (process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'development') { 25 | // By default vue-cli sets rel=prefetch on chunks-vendor.js and app.js in the index.html that gets generated in index.html. 26 | // This setting removes vue-cli's this default so we can use more chunks and let them be loaded dynamically. 27 | config.plugins.delete('prefetch') 28 | config.plugin('prefetch') 29 | .use(PreloadWebpackPlugin, [{ 30 | rel: 'prefetch', 31 | include: 'asyncChunks', 32 | // do not prefetch async routes 33 | fileBlacklist: [ 34 | /myasyncRoute(.)+?\.js$/, 35 | /\.map$/ 36 | ] 37 | }, 38 | { 39 | rel: 'preload', 40 | include: 'initial', 41 | fileWhitelist: [ 42 | /(^@vue)(.*)(\.js$)/, 43 | /(^vue)(.*)(\.js$)/ 44 | ], 45 | // do not preload map files or hot update files 46 | fileBlacklist: [ 47 | /\.map$/, 48 | /hot-update\.js$/ 49 | ] 50 | }]) 51 | 52 | // override vue's default chunks because their chunking is too big. 53 | config.optimization.delete('splitChunks') 54 | config.optimization.set('splitChunks', { 55 | cacheGroups: { 56 | // Vue modules 57 | vue: { 58 | test: /[\\/]node_modules[\\/](@vue.*|vue.*)[\\/]/, 59 | name: 'vue', 60 | enforce: true, 61 | priority: 20, 62 | chunks: 'initial' 63 | }, 64 | // all other modules modules 65 | vendors: { 66 | name: 'chunk-vendors', 67 | test (module, chunks) { 68 | // `module.resource` contains the absolute path of the file on disk. 69 | // Note the usage of `path.sep` instead of / or \, for cross-platform compatibility. 70 | const path = require('path') 71 | return module.resource && 72 | !module.resource.includes(`${path.sep}node_modules${path.sep}@vue`) && 73 | !module.resource.includes(`${path.sep}node_modules${path.sep}vue`) && 74 | !module.resource.includes(`${path.sep}src${path.sep}`) 75 | }, 76 | maxSize: 500000, 77 | priority: 10, 78 | enforce: true, 79 | chunks: 'all' // doesn't get created without 'all' here 80 | }, 81 | // default common chunk settings from Vue 82 | common: { 83 | name: 'chunk-common', 84 | minChunks: 2, 85 | priority: 5, 86 | chunks: 'initial', 87 | reuseExistingChunk: true 88 | } 89 | } 90 | }) 91 | 92 | // Webpack includes a small piece of runtime code that gets inserted into the last chunk created. This could cause our vendor 93 | // chunk to change unnecessarily. So the next line causes this runtime to be put in a separate file. 94 | config.optimization.set('runtimeChunk', true) 95 | } 96 | 97 | if (process.env.NODE_ENV === 'production') { 98 | // for moment tree shaking locales 99 | // Include only 'en-us' (which is always included) and 'es-us' locales 100 | config.plugin('moment') 101 | .use(MomentLocalesPlugin, [{ 102 | localesToKeep: ['es-us'] 103 | }]) 104 | .use(new webpack.IgnorePlugin({ 105 | resourceRegExp: /^\.\/locale$/, 106 | contextRegExp: /moment$/ 107 | })) 108 | } 109 | } 110 | } 111 | --------------------------------------------------------------------------------