Vue-Vue-Router-Vuex-SSR

  1. Vue+Webpack project flow setup
  2. Vue+ VUE-Router +Vuex project architecture

Server side rendering

The current front-end framework is pure client rendering, (when requesting 🤴 website, the returned HTML is no content), there is no way to SEO, white screen time is long. The content will not be displayed until the JS load is complete and the execution is complete.

Server-side rendering solves these problems.

Webpack upgrade note ⚠️ : 1. Version change 2. Configuration change 3. Plug-in changes

Vue – loader configuration

const isDev = process.env.NODE_ENV === 'development'
Copy the code
// vue-loader.config.js
const docsLoader = require.resolve('./doc-loader')
module.exports = (isDev) = > {
 return {
  preserveWhitespace: true.// Remove the space after it
  extractCSS: !isDev, // Package to a CSS file separately
  cssModules: {},
  // hotReload: false, // generated based on environment variables
  loaders: {
   'docs': docsLoader,
  },
  preLoader: {},}}Copy the code
module.exports = (isDev) = > {
 return {
  preserveWhitespace: true.extractCSS: !isDev,
  cssModules: {},}}Copy the code

CSS module configuration

module.exports = (isDev) = > {
 return {
  preserveWhitespace: true.extractCSS: !isDev,
  cssModules: {
   localIdentName: isDev ? '[path]-[name]-[hash:base64:5]' : '[hash:base64:5]'.// localIdentName: '[path]-[name]-[hash:base64:5]',
   camelCase: true}}}Copy the code

The installation uses ESLint and EditorConfig along with preCommit

npm i eslint eslint-config-standard eslint-plugin-standard eslint-plugin-promise eslint-plugin-import eslint-plugin-node
Copy the code

Create eslintrc.

{
 "extends": "standard"
}
Copy the code
npm i eslint-plugin-html
Copy the code
// package.json

"script": {
 "clean": "rimraf dist"."lint": "eslint --ext .js --ext .jsx --ext .vue client/"."build": "npm run clean && npm run build:client".// Automatic repair
 "lint-fix": "eslint --fix --ext .js --ext .jsx --ext .vue client/"
 "precommit": "npm run lint-fix",}Copy the code
npm i webpack -D
// webpack 4
npm uninstall webpack webpack-dev-server webpack-merge -D
/ / webpack upgrade
npm i webpack webpack-dev-server webpack-merge webpack-cli -D

npm uninstall babel-loader extract-text-webpack-plugin file-loader html-webpack-plugin -D
Copy the code

webpack.config.base.js

const config = {
 mode: process.env.NODE_ENV, // development || production
 target: 'web',}Copy the code
npm i eslint-loader babel-eslint
Copy the code

Some points of Vue

import Vue from 'vue'

const div = document.createElement('div')
document.body.appendChild(div)

new Vue({
 el: div,
 template: '<div>this is content</div>'
})

// webpack.config.practice.js
resolve: {
 alias: {
  'vue': path.join(__dirname, '.. /node_modules/vue/dist/vue.esm.js')}}Copy the code

index.js

import Vue from 'vue'
import App from './app.vue'

import './assets/styles/global.style'

const root = document.createElement('div')
document.body.appendChild(root)

new Vue({
 rendeer: (h) = > h(App)
}).$mount(root)
Copy the code

VUE instance

  • Vue instance creation and functions
  • Properties of the Vue instance
  • Vue instance method
import Vue from 'vue'
const app = new Vue({
 // el: '#root',
 template: '<div ref="div">{{text}}</div>'.data: {
  text: 0
 }
})

app.$mount('#root')

setInterval(() = >{
 // app.text += 1
 // app.$options.data += 1 cannot be used
 app.$data.text += 1 // It can be used
},1000)

console.log(app.$data)
console.log(app.$props)
console.log(app.$el)
console.log(app.$options)

console.log(app.$slots)
console.log(app.$scopedSlots)

console.log(app.$ref) // div node/component instance

console.log(app.$isServer) // Server render

app.$options.render = (h) = > {
 return h('div', {}, 'new render function')}Copy the code
props: {
 filter: {
  type: String.required: true
 },
 todos: {
  type: Array.required: true}}Copy the code

watch

const app = new Vue({
 // el: '#root',
 template: '<div ref="div">{{text}}</div>'.data: {
  text: 0
 },
 watch: {
  text(newText, oldText) {
   console.log('${newText}:${oldText}')
  }
 }
})
app.$mount('#root')
Copy the code
const unWatch = app.$watch('text', (newText, oldText){
 console.log(`${nextText}:${oldText}`)})setTimeout(() = >{
 unWatch() // Cancel after 2 seconds
}, 2000)
Copy the code

Event listening:

app.$on('test'.() = > {
 console.log('test emited')})// Trigger the event
app.$emit('test')

app.$on('test'.(a, b) = > {
 console.log('test emited ${a} ${b}')})// Trigger the event
app.$emit('test'.1.2)
Copy the code
$once only listens to app once.$once('test'.(a, b) = > {
 console.log('test emited ${a} ${b}')})Copy the code

ForceUpdate forces the component to render once

Nonresponsive:

// The value is changing, but does not result in re-rendering
data: {
 text: 0.obj: {}}setInterval(() = > {
 app.obj.a = app.text
 app.$forceUpdate()
},1000)
Copy the code

Rendering all the time will slow down your performance

Name of some property on some object, and give it a value:

let i = 0
setInterval(() = >{
 i++
 app.$set(app.obj, 'a', i) // Change an attribute value on an object to give it a value
},1000)
Copy the code

vm.$nextTick([callback])

Wait until the DOM node is rendered. Vue calls callback on the next update

Postponing the callback until after the next DOM update loop, using it immediately after modifying the data, and then waiting for DOM updates is the same as the global method Vue. NextTick, except that the callback’s this is automatically bound to the instance calling it.

New in 2.1.0, return a Promise in an environment that does not provide a callback and supports Promise. Pay attention to the polyfill.

Vue life cycle

new Vue({
 el: '#root'.template: '<div>{{text}}</div>'.data: {
  text: 'jeskson'
 },
 beforeCreate() {
  console.log(this.'beforeCreate')},created() {
  console.log(this.'created')},beforeMount() {
  console.log(this.'beforeMount')},mounted() {
  console.log(this.'beforeCreate')},beforeUpdate() {
  console.log(this.'beforeUpdate')},updated() {
  console.log(this.'updated')},activated() {
  console.log(this.'activated')},deactivated() {
  console.log(this.'deactivated')},beforeDestroy() {
  console.log(this.'beforeDestroy')},destroyed() {
  console.log(this.'destroyed')},render(h) {
  console.log('render function invoked')
  return h('div', {}, this.text)
 },
 renderError(h, err) {
  return h('div', {}, err.stack)
 },
 errorCaptured() {
  // Bubbles up, and the formal environment can be used}})Copy the code
undefined 'beforeCreate'
undefined 'created'
<div id="root"></div> "beforeMount"
<div>0</div> "mounted"

template: `<div> name: {{name}} </div>`

computed: {
 name() {
  return `The ${this.firstName} The ${this.lastName}`
 } 
}

<li v-for="(item, index) in arr"> </li><li v-for="(val, key) in obj"></li>V-model. number v-model.trim trim <div><input type="radio" value="one" v-model="pick"/>
 <input type="radio" value="two" v-model="pick"/>
</div>
Copy the code

Define the components

import Vue from 'vue'

const compoent = {
 template: '<div>This is compoent</div>'
}

// Vue.component('CompOne', compoent)

new Vue({
 components: {
  CompOne: compoent
 },
 el: '#root'.template: '<comp-one></comp-one>'
})

/ / an error
const compoent = {
 template: '<div>{{text}}</div>'.data: {
  text: 123
 }
}

[vue warn] the "data" option should be a function that returns a per-instance value in component definitions.
Copy the code

props

/ / child component
const compoent = {
 props: {
  active: Boolean.propOne: String
 },
 template: ` 
      
{{propOne}} see me if active
`
.data() { return { text: 0}}}// Parent component pass <comp-one :active="true" prop-one="text1"></comp-one> Copy the code

It is not allowed to modify this.propOne=’inner content’ inside a component,

vue warn: avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "propOne"
Copy the code
// The child component triggers props modification

/ / child component
methods: {
 handleChange() {
  this.$emit('change')}}// Subcomponent props

props: ['active'.'propOne'].props: {
 active: {
  type: Boolean.required: true.default: true}}props: {
 active: {
  // type: Boolean,
  // required: true,
  validator(value) {
   return typeof value === 'boolean'}}}Copy the code
const CompVue = Vue.extend(compoent)

new CompVue({
 el: '#root'.propsData: {
  propOne: 'xxx'}})Copy the code

🌰

const parent = new Vue({
 name: 'parent'
})

const componet2 = {
 extends: compoent,
 data () {
  return {
   text: 1
  }
 },
 mounted () {
  console.log(this.$parent.$options.name) // Root
  this.$parent.text = '12345'}}new Vue({
 parent: parent,
 name: 'Root'.el: '#root',
 mounted () {
  console.log(this.$parent.$options.name) // parent
 },
 components: {
  Comp: component2
 },
 data: {
  text: 2333
 },
 template: ` 
      
{{text}}
`
}) Copy the code

🌰 chestnuts input

/ / child component
handleInput (e) {
 this.$emit('input', e.target.value)
}

/ / the parent component. :value="value" @input="value = arguments[0]"

/ / 🌰
const component = {
 props: ['value'].template: ` 
      
`
.methods: { handleInput (e) { this.$emit('input', e.target.value) } } } const component = { model:; { prop: 'value1'.event: 'change' }, props: ['value1'].template: `
`
.methods: { handleInput (e) { this.$emit('change', e.target.value) } } } Copy the code

Attribute ✍ ️

slot

const component = {
 template: ` 
      
`
, data () { return { style: { width: '200px'; height: '200px'; border: '1px solid #aaa' } } } } <comp-one> <span slot="header"> this is header </span> <span slot="body"> this is body </span> </comp-one> Copy the code

slot-scope

Special 🌰

template: ` 
      
`
.// Parent component - a variable used inside the component <comp-one> <span slot-scope="props"> {{props.value}} {{props.aaa}} </span> </comp-one> Copy the code

provide inject

provide : {
 yeye: this.value: this.value, / / error
},

provide() {
 const data = {}

 Object.defineProperty(data, 'value', {
  get: () = > this.value,
  enumerable: true
 })

 return {
  yeye: this,
  data
  // value: this.value}}/ / child component
inject: ['yeye'.'data']
template: '<div>child component: {{data.value}}</div>'
Copy the code

new Vue, beforeCreate, created, beforeMount, mounted

Without EL, no beforeMount, mounted is mounted

$mount(‘#root’) is executed if a const app is used

setInterval(() = > {
 app.text = app.text += 1
},1000)

// Active destruction
app.$destroy()
Copy the code

Components:

activated() {
 / / in the component
 console.log(this.'activated')}deactivated() {
 / / in the component
 console.log(this.'deactivated')}Copy the code

Changes:

el:

undefined 'beforeCreate'
undefined 'created'
<div id="root"></div> "beforeMount"
<div>0</div> "mounted"
Copy the code

Dom objects are stored in mounted. Data can be created in mounted

BeforeMount, Mounted, create, beforeCreate will not perform server rendering (because the server does not have a Dom environment, all databases do not have this content)

The life cycle

new Vue()

Init Events(Events are ok) & Lifecycle

BeforeCreate (do not modify the data in the data)

Init injections & reactivity

Created (Data manipulation)

Has ‘el’ option ? No when vm.$mount(el) is called

  • // el: ‘#root’

YES Has ‘template’ option ?

  • // template: ‘<div> {{text}} </div>

Has the template attribute:

Parsing the render

render(h) {
 console.log('render function invoked')
 return h('div', {}, this.text)
}

Waiting for update signal form WDS...

undefined "beforeCreate"
undefined "created"
 <div id="root"></div> "beforeMount"
render function invoked
 <div< / a > 0div>"mounted"
Copy the code

Yes: Compile template into render function is available

No: No Compile el’s outerHTML as template

beforeMount

Create vm.$el and replace ‘el’ with it

mounted

Mounted(The instance is created successfully)

  • when data changes (beforeUpdate) Virtual DOM re-render and patch (updated)
  • Walk when wm.$destroy() is called

beforeDestroy

Teardown watchers, child components and event listeners

destroyed

renderError

Package officially online will not call, development will use

renderError (h, err) {
 return h('div', {}, err.stack)
}
Copy the code

Formally develop the environment to collect errors on the line

errorCaptured(){<! -- bubbles up -->}Copy the code

Data in vue is bound to template

Watch listens for a change in data, specifies an operation, (server use)

Vue’s native instruction

Vue component render function

render (createElement) {
 return createElement('comp-one', {
  ref: 'comp'
 }, [
  createElement('span', {
   ref: 'span'
  }, this.value)
 ])
}

render (createElement) {
 return createElement('div', {
  style: this.style,
  on: {
   click: () = > { this.$emit('click')}}},this.$slots.default)
}
Copy the code

Vue-Router && Vuex

import Router from 'vue-router'

import routers from './routes'

exports default() = > {return new Router({
  routers,
  mode: 'history'.// base: '/base/',
  linkActiveClass: 'active-link'./ / the subset
  linkExactActiveClass: 'exact-active-link'.// Accurate target
  scrollBehavior (to, from, savedPosition) {
   if (savedPosition) {
    return savedPosition
   } else {
    return { x: 0.y: 0}}},// parseQuery (query) {
  // },
  // stringifyQuery (obj) {
  // }})}Copy the code

transition

<transition name="fade">
 <router-view />
</transition>
Copy the code
this.$route
// path: '/app/:id', to="/app/123"
fullPath: '/app/123'
hash: ""
matched: [{}]
meta: {title:' '}
name: 'app'
params: {id: '123'}
path: '/app/123'
query: {}
Copy the code
// routes.js
{
 path: '/app/:id'.props: true.component: Todo,
 name: 'app'.meta: {
  title: 'this is app'.description: 'asdasd'}}// todo.vue

props: ['id'],
mounted () {
 console.log(this.id)
}
Copy the code
{
 path: '/login'.components: {
  default: Login,
  a: Todo
 }
}
Copy the code

Vue-router Navigation guard

import createRouter from './config/router'

Vue.use(VueRouter)

const router = createRouter()

router.beforeEach((to, from, next) = > {
 // Perform login authentication
 console.log('before each invoked')
 // next()
 if (to.fullPath === '/app') {
  next('/login') Next ({path: '/login'})
 } else {
  next() // Meet the criteria
 }
})

router.beforeResolve((to, from, next) = > {
 console.log('before resolve invoked')
 next()
})

router.afterEach((to, from) = > {
 console.log('after each invoked')})Copy the code
// routes.js

{
 path: '/app',
 beforeEnter (to, from, next) {
  console.log('app route before enter')
  next() // It will only be called if you click enter}}// before each invoked
// app route before enter
// before resolve invoked
// after each invoked
Copy the code
// todo.vue
export default {
 beforeRouteEnter (to, from, next) {
  console.log('todo before enter')
  next()
 },
 
 beforeRouteUpdate (to, from, next) {
  console.log('todo update enter')
  next()
 }, 
 
 beforeRouteLeave (to, from, next) {
  console.log('todo leave enter')
  next()
 },

}
Copy the code

Leave:

todo leave enter
before each invoked
before resolve invoked
after each invoked
Copy the code

To:

before each invoked
app route before enter
todo before enter
before resolve invoked
after each invoked
Copy the code

Vuex integration

import Vuex from 'vuex'

const store = new Vuex.Store({
 state: {
  count: 0
 },
 mutations: {
  updateCount (state, num) {
   state.count = num
  }
 }
})

export default store
Copy the code

Server side rendering

import createRouter from './config/router'
import createStore from './store/store'

Vue.use(VueRouter)
Vue.use(Vuex)

const router = createRouter()
const store = createStore()
Copy the code

State and getters in Vuex

// store.js
import Vuex from 'vuex'
import defaultState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'

export default() = > {return new Vuex.Store({
  state: defaultState,
  mutations,
  getters
 })
}
Copy the code
// state/state.js
export default {
 count: 0.firstName: 'dada'.lastName: 'dada'
}
Copy the code
// mutations/mutations.js
export default {
 updateCount (state, num) {
  state.count = num
 }
}
Copy the code

getters

// getters/getters.js =========== computed
export default {
 fullName(state) {
  return `${state.firstName} ${state.lastName}`}}Copy the code
// app.vue
computed: {
 count () {
  return this.$store.state.count
 },
 
 fullName () {
  return this.$store.getters.fullName
 }
}
Copy the code

Quick to use

import {
 mapState,
 mapGetters
} from 'vuex'

computed: {
 / /... mapState(['count']),
 / /... mapState({
 // counter: 'count'
 // }),. mapState({counter: (state) = >state.count }), ... mapGetters(['fullName'])}Copy the code

Mutation and Action in Vuex

// Development environment store.js
const isDev = process.env.NODE_ENV === 'development'

export default() = > {return new Vuex.Store({
  strict: isDev,
  state: defaultState,
  mutations,
  getters
 })
}
Copy the code
// actions/actions.js
// dispatch triggers actions
/ / asynchronous
export default {
 updateCountAsync (store, data) {
  setTimeout(() = > {
   store.commit('updateCount', data.num)
  }, data.time)
 } 
}
Copy the code
// store.js
import Vuex from 'vuex'
import defaultState from './state/state'
import mutations from './mutations/mutations'
import getters from './getters/getters'
import actions from './actions/actions'

export default() = > {return new Vuex.Store({
  state: defaultState,
  mutations,
  getters,
  actions
 })
}
Copy the code
import {
 mapState,
 mapGetters,
 mapActions,
 mapMutations
} from 'vuex'

mounted () {
 this.updateCountAsync({
  num: 5.time: 2000})}// open mutations
methods: {
 ...mapActions(['updateCountAsync']),
 ...mapMutations(['updateCount'])}Copy the code

Modules in Vuex

// app.vue
mounted () {
 this['a/updateText'] ('123')}methods: {
 ...mapActions(['updateCountAsync']),
 ...mapMutations(['updateCount'.'a/updateText'])}computed: {
 ...mapState({
  counter: (state) = > state.count,
  textA: state= >state.a.text }), ... mapGetters(['fullName'.'a/textPlus'])
 textA () {
  return this.$store.state.a.text
 }
}

// store.js
modules {
 a: {
  namespaced: true
  state: {
   text: 1
  },
  mutations: {
   updateText (state, text) {
    console.log('a.state', state)
    state.text = text
   }
  },
  getters: {
   textPlus (state, getters, rootState) {
    return state.text + rootState.count + rootState.b.text
   }
  },
  actions: {
   add ({ state, commit, rootState }) {
    commit('updateText', rootState.count) {root: true}
    // commit('updateCount', rootState. Count, {root: true})}}},b: {
  state: {
   text: 2
  },
  actions: {
   testAction ({ commit }) {
    commit('a/updateText'.'test text', { root: true })
   }
  }
 }
}

store.hotUpdate({})
Copy the code

The API Vuex

// index.js
const router = createRouter()
const store = createStore()

store.registerModule('c', {
 state: {
  text: 3}})// Listen for changes in this value
store.watch((state) = > state.count + 1.(newCount) = > {
 console.log(newCount)
})

/ / subscribe
store.subscribe((mutation, state) = > {
 console.log(mutation.type) // which mutation to call
 console.log(mutation.payload) // mutation Incoming value of the received parameter
})

store.subscribeAction((action, state) = > {
 console.log(action.type)
 console.log(action.payload)
})

store.unregisterModule('c')
Copy the code
// store.js
export default() = > {const store = new Vuex.Store({
  strict: isDev,
  state: defaultState,
  mutations,
  getters,
  actions,
  plugins: [
   (store) = > {
    console.log('my plugin invoked')}]})}// my plugin invoked
// before each invoked
// before resolve invoked
// after each invoked
Copy the code

Server-side render build process

Access the server rendering page: Webpack Server Compiler -> NodeJS Server 3333 port

  1. Pure front-end rendering: WebPack Dev Server 8000 port
  2. Server bundle -> NodeJS Server 3333
npm i vue -D // devDependencies

npm i vue -S // dependencies

npm i vue-server-renderer

npm i koa-router -S

npm i axios -S
Copy the code

Server Server rendering

const koa = require('koa')
const app = new Koa()
const isDev = process.env.NODE_ENV = 'development'
Copy the code

dev-ssr.js

const Router = require('koa-router')
const axios = require('axios')
const MemoryFS = require('memory-fs')
const webpack = require('webpack')
const VueServerRenderer = require('vue-server-render')
Copy the code

Component development

Notification noticeCopy the code
<template>
 <transition name="fade" @after-leave="afterLeave" @after-enter="afterEnter">
  <div class="notification" :style="style" v-show="visible" @mouseenter="clearTimer" @mouseleave="createTimer">
   <span class="content">{{content}}</span>
   <a class="btn" @click="handleClose">{{btn}}</a>
  </div>
 </transition>
</template>

<script>
export default {
 name: 'Notification'.props: {
  content: {
   type: String.require: true
  },
  btn: {
   type: String.default: 'off'}},data() {
 return {
  visible: true}},computed: {
 style () {
  return{}}},methods: {
  handleClose (e) {
   e.preventDefault() // The default event is blocked
   this.$emit("close")},afterLeave() {
   this.$emit("closed")},afterEnter(){},clearTimer(){},
  createTimer(){},}}</script>
Copy the code
// index.js
/ / global
import Notification from './notification.vue'
import notify from './function'

export default (Vue) => {
 Vue.component(Notification.name, Notification)
 Vue.prototype.$notify = notify
}
Copy the code
<notification content="test notify">
Copy the code
// func-notification.js
import Notification from './notification.vue'

export default {
 extends: Notification,
 computed: {
  style() {
   return {
    position:; 'fixed'.right: '20px'.bottom: `The ${this.verticalOffset}px`
   }
  }
 },
 mounted () {
  this.createTimer()
 },
 methods: {
  createTimer() {
   if (this.autoClose) {
    this.timer = setTimeout(() = > {
     this.visible = false
    }, this.autoClose)
   }
  },
  clearTimer () {
   if (this.timer) {
    clearTImeout(this.timer)
   }
  },
  afterEnter() {
   this.height = this.$el.offsetHeight
  }
 },
beforeDestory () {
 this.clearTimer()
},
 data() {
  return {
   verticalOffset: 0.autoClose:  3000.height: 0.visible: false}}}Copy the code
// function.js
import Vue from 'vue'
import Component from './func-notification'

const NotificationConstructor = Vue.extend(Component)

const instances = []
let seed = 1 // Of the component id

const removeInstance = (instance) = > {s
 if(! instance)return
 const len = instances.length
 const index = instances.findIndex(inst= > instance.id === inst.id)

 instance.splice(index, 1)

 if (len <= 1) return
 const removeHeight = instance.vm.height
 for (let i = index; i < len - 1; i++) {
  instances[i].verticalOffset = parseInt(instances[i].verticalOffset) - removeHeight - 16}}const notify = (options) = > {
 if (Vue.prototype.$isServer) return
 
 const{ autoClose, ... rest } = optionsconst instance = new NotificationConstructor({
  // propsData: options
  propsData: {
   ...rest
  },
  data: {
   autoClose: autoClose === undefined ? 3000 : autoClose
  }
 })

 const id = `notification_${seed++}`
 instance.id = id
 instance.vm = instance.$mount() // the node is available, div😊 is available

 document.body.appendChild(instance.vm.$el)
 instance.vm.visible = true

 let verticalOffset = 0
 instances.forEach(item= > {
  verticalOffset += item.$el.offsetHeight + 16
 })
 verticalOffset += 16
 instance.verticalOffset = verticalOffset
 instances.push(instance)
 
 instance.vm.$on('close'.() = > {
  removeInstance(instance)
  document.body.removeChild(instance.vm.$el)
  instance.vm.$destroy()
 })
 instance.vm.$on('close'.() = > {
  instance.vm.visible = false
 })
 return instance.vm
}
Copy the code
// "precommit": "npm run lint-fix",
Copy the code

The deployment of

Github.com/webVueBlog/…

ok!