vue-router source code reading-file structure and registration mechanism

vue-router source code reading-file structure and registration mechanism

Front-end routing is a concept that we often encounter in the daily development of front-end development. I know it in daily use and am curious about it. Therefore, I have read the source code of vue-router and learned some excellent ideas from the community articles. , Record the summary in this article as the output of my own thinking, my level is limited, please leave a message to discuss

Target vue-rouer version:3.0.2

Vue-router source code comment: vue-router-analysis

Disclaimer: The syntax of the source code in the article uses Flow, and the source code is truncated as needed (in order not to be confused @_@), if you want to see the full version, please enter the above github address ~

This article is a series of articles, the link is at the bottom ~

0. Preliminary knowledge

  • Flow
  • ES6 syntax
  • Design Mode-Appearance Mode
  • HTML5 History Api

If you don’t understand these yet, you can take a look at the recommended reading at the end of this article.

1. File structure

1. let's take a look at the file structure:

.
├── build//Package related configuration
├── scripts//build related
├── dist//file directory after build
├── docs//project documentation
├── docs-gitbook//gitbook configuration
├── examples//sample code, used when debugging
├── flow//Flow statement
├── src//source directory
│ ├── components//public components
│ ├── history//routing class implementation
│ ├── util//related tool library
│ ├── create-matcher.js//Create a routing mapping table based on the incoming configuration object
│ ├── create-route-map.js//Create a route mapping table based on the routes configuration object 
│ ├── index.js//main entrance
│ └── install.js//VueRouter loading entry
├── test//test file
└── types//TypeScript declaration

Our main concern is srcthe content.

2. Entry file

2.1 rollup exit and entrance

By convention, the first from the package.jsonlooks, there are about two orders worth noting:

{
    "scripts": {
        "dev:dist": "rollup -wm -c build/rollup.dev.config.js",
        "build": "node build/build.js"
  }
}

dev:distConfiguration file rollup.dev.config.jsgenerated distto facilitate development and debugging associated directory file generated corresponding to the following configuration environment development;

buildIt is noderun build/build.jsto generate official documents, including es6, commonjs, IIFEexport papers and export files after compression;

Both are ways to use build/configs.jsthis configuration file to generate, in which there is a relatively good semantic code is quite interesting, with Vue configuration files generated relatively similar:

//vue-router/build/configs.js

module.exports = [{//Package export
    file: resolve('dist/vue-router.js'),
    format:'umd',
    env:'development'
  },{
    file: resolve('dist/vue-router.min.js'),
    format:'umd',
    env:'production'
  },{
    file: resolve('dist/vue-router.common.js'),
    format:'cjs'
  },{
    file: resolve('dist/vue-router.esm.js'),
    format:'es'
  }
].map(genConfig)

function genConfig (opts) {
  const config = {
    input: {
      input: resolve('src/index.js'),//package entry
      plugins: [...]
    },
    output: {
      file: opts.file,
      format: opts.format,
      banner,
      name:'VueRouter'
    }
  }
  return config
}

You can clearly see the rolluppackaging of the inlet and outlet, the inlet is src/index.jsfile, export the configuration that is part of the above, envis the development/production environments mark, formatas compiled output mode:

  • es: ES Modules, output using ES6 template syntax
  • cjs: CommonJs Module, file output following the CommonJs Module specification
  • umd: Support external link specification file output, this file can directly use the script tag, in fact, it is the way of IIFE

Then the output is to use the official buildway, we can from the src/index.jslooks

//src/index.js

import {install} from'./install'

export default class VueRouter {...}

VueRouter.install = install

First of all the exported file a class VueRouterthat we introduced in the Vue vue-router when this project Vue.use(VueRouter)are used, and the Vue.usemain role is to find a plug-registered on the installmethod and execution, look at the last line down from one install.jsexport file the install is assigned to VueRouter.install, and this is Vue.useperformed by use of the installmethod.

2.2 Vue.use

You can simply look at the Vue in Vue.usehow this method is implemented:

//vue/src/core/global-api/use.js

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
   //... omit some heavy-duty operations
    const args = toArray(arguments, 1)
    args.unshift(this)//Note this this is a vue object
    if (typeof plugin.install ==='function') {
      plugin.install.apply(plugin, args)
    }
    return this
  }
}

Above you can see Vue.usethis is to be executed on a registered plug-in installmethod, and saved the plug-in instance. It is noted that installthe first parameter is a method performed through the unshiftpushed this, and therefore installcan get Vue object execution.

Corresponding to the previous section, where plugin.installis VueRouter.install.

3. Route registration

3.1 install

Before then, a look install.jsinside how to register routing plug-in:

//vue-router/src/install.js

/* The registration process of vue-router Vue.use(VueRouter) */
export function install(Vue) {
  _Vue = Vue//This way getting Vue will not increase the packaging volume caused by import
  
  const isDef = v => v !== undefined
  
  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode//The _parentVnode property only exists when there is at least one VueComponent
   //registerRouteInstance in src/components/view.js
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }
  
 //When new Vue or when creating a new component, call in the beforeCreate hook
  Vue.mixin({
    beforeCreate() {
      if (isDef(this.$options.router)) {//Whether the component exists $options.router, the object is only on the root component
        this._routerRoot = this//here this is the root vue instance
        this._router = this.$options.router//VueRouter instance
        this._router.init(this)
        Vue.util.defineReactive(this,'_route', this._router.history.current)
      } else {//Component instance will enter, get _routerRoot through $parent level one level
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destroyed() {
      registerInstance(this)
    }
  })
  
 //This.$router in all instances is equivalent to access this._routerRoot._router
  Object.defineProperty(Vue.prototype,'$router', {
    get() {return this._routerRoot._router}
  })
  
 //This.$route in all instances is equivalent to access this._routerRoot._route
  Object.defineProperty(Vue.prototype,'$route', {
    get() {return this._routerRoot._route}
  })
  
  Vue.component('RouterView', View)//Register the public component router-view
  Vue.component('RouterLink', Link)//Register the public component router-link
  
  const strats = Vue.config.optionMergeStrategies
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

install The method is mainly divided into several parts:

  1. Through Vue.mixinthe beforeCreate, destroyedwhen some of the routing method to mount in each instance vue
  2. Vue mounted to each route object instance to ensure that methodsother places can this.$router, this.$routeaccess to information
  3. Register public components router-view,router-link
  4. Register the life cycle function of the route

Vue.mixinThe two hook assembly defined extendtime incorporated into the assembly options, thereby to register each component instance. Look beforeCreate, at the beginning of a visit this.$options.routerthat is inside Vue project app.jsin new Vue({ router })here passed this router, of course, only new Vuethen will pass router, that is this.$options.routeran example only and only on the root. What exactly is this incoming router? Let's see how it is used:

const router = new VueRouter({
  mode:'hash',
  routes: [{ path:'/', component: Home },
        {path:'/foo', component: Foo },
        {path:'/bar', component: Bar }]
})

new Vue({
  router,
  template: `<div id="app"></div>`
}).$mount('#app')

You can see this this.$options.routeris an example of the Vue this._routeis actually an example of VueRouter.

Dazzling meal remaining operations, Vue to each component instance may be by _routerRoota root access Vue example, thereon _route, _routeris assigned to the prototype Vue, so that in each instance are by Vue this.$route, this.$routeraccess root instance to mount _routerRooton _route, _routerfollowed by the method responsive Vue defineReactiveto be _routeresponsive of the other components used in the root this._router.init()performed initialization operation.

Just find a Vue component and print it _routerRoot:

You can see that this is the root component of Vue.

3.2 VueRouter

Before we have seen src/index.js, and here a detailed look at this class VueRouter

//vue-router/src/index.js

export default class VueRouter {  
  constructor(options: RouterOptions = {}) {}
  
 /* The install method will call init to initialize*/
  init(app: any/* Vue component instance*/) {}
  
 /* The match method returned by the createMatcher method*/
  match(raw: RawLocation, current?: Route, redirectedFrom?: Location) {}
  
 /* Current routing object*/
  get currentRoute() {}
  
 /* Register beforeHooks event*/
  beforeEach(fn: Function): Function {}
  
 /* Register resolveHooks event*/
  beforeResolve(fn: Function): Function {}
  
 /* Register afterHooks event*/
  afterEach(fn: Function): Function {}
  
 /* onReady event*/
  onReady(cb: Function, errorCb?: Function) {}
  
 /* onError event*/
  onError(errorCb: Function) {}
  
 /* Call transitionTo to jump route*/
  push(location: RawLocation, onComplete?: Function, onAbort?: Function) {}
  
 /* Call transitionTo to jump route*/
  replace(location: RawLocation, onComplete?: Function, onAbort?: Function) {}
  
 /* Jump to the specified history*/
  go(n: number) {}
  
 /* Back*/
  back() {}
  
 /* go ahead*/
  forward() {}
  
 /* Get the components matched by the route*/
  getMatchedComponents(to?: RawLocation | Route) {}
  
 /* Return information such as the browser path according to the routing object*/
  resolve(to: RawLocation, current?: Route, append?: boolean) {}
  
 /* Add routing dynamically*/
  addRoutes(routes: Array<RouteConfig>) {}
}

In addition to a bunch of instance methods in the VueRouter class, the main concern is its constructor and initialization method init.

First look at the constructor, in which the modepattern is created on behalf of the route is determined by user configuration and application scenarios, there are three main History, Hash, Abstract, the first two we are very familiar with, Abstract representatives of non-browser environment, such as Node, weex, etc.; this.historymainly specific examples of routing. The implementation is as follows:

//vue-router/src/index.js

export default class VueRouter {  
  constructor(options: RouterOptions = {}) {
    this.matcher = createMatcher(options.routes || [], this)//add route matcher
    let mode = options.mode ||'hash'//route matching method, the default is hash
    this.fallback = mode ==='history' && !supportsPushState && options.fallback !== false
    if (this.fallback) {mode ='hash'}//If history is not supported, it will be reduced to hash
    if (!inBrowser) {mode ='abstract'}//Mandatory abstract in a non-browser environment, such as in node
    this.mode = mode
    
    switch (mode) {//Appearance mode
      case'history'://history method
        this.history = new HTML5History(this, options.base)
        break
      case'hash'://hash method
        this.history = new HashHistory(this, options.base, this.fallback)
        break
      case'abstract'://abstract way
        this.history = new AbstractHistory(this, options.base)
        break
      default: ...
    }
  }
}

initInitialization method is when the install Vue.mixinregistered beforeCreatecall hooks, you can turn up and see; invocation is this._router.init(this), because in the Vue.mixincall in, so that this is the current Vue instance. In addition, the initialization method needs to be responsible for the routing initialization when jumping from any path to the project. Take the Hash mode as an example. At this time, the related events have not been bound, so the event binding is required when the first execution is performed. And popstate, hashchangeevent trigger, and then manually trigger a route jump. The implementation is as follows:

//vue-router/src/index.js

export default class VueRouter {  
 /* The install method will call init to initialize*/
  init(app: any/* Vue component instance*/) {
    const history = this.history
    
    if (history instanceof HTML5History) {
     //Call the transitionTo method of the history instance
      history.transitionTo(history.getCurrentLocation())
    } else if (history instanceof HashHistory) { 
      const setupHashListener = () => {
          history.setupListeners()//Set popstate/hashchange event listener
      }
      history.transitionTo(//call the transitionTo method of the history instance
          history.getCurrentLocation(),//the hash value of the browser window address
          setupHashListener,//success callback
          setupHashListener//failed callback
      )
    }
  }
}

In addition, VueRouter also has many example methods to implement various functions, and the rest will be shared in the series of articles ~

This article is a series of articles . Later parts will be updated to make progress together~

  1. vue-router source code reading-file structure and registration mechanism

Most of the posts on the Internet are of different depths, and even some are inconsistent. The articles below are summaries of the learning process. If you find errors, please leave a message to point out~

Recommended reading:

  1. H5 History Api-MDN
  2. Introduction to ECMAScript 6-Ruan Yifeng
  3. JS static type checking tool Flow develop paper
  4. JS Appearance Mode-SegmentFault 思不
  5. The basic principle of front-end routing jump-Nuggets

reference:

  1. Vue.js technology revealed
Reference: https://cloud.tencent.com/developer/article/1451724 vue-router source code reading-file structure and registration mechanism-cloud + community-Tencent Cloud