Vue-hot-reload-api source code parsing

Posted by host78 on Sun, 02 Jun 2019 21:43:39 +0200

Vue-hot-reload-api source code parsing

cause

In recent years, the hot loading scheme of san framework is naturally necessary to learn from mature framework (peeping). Hot loading schemes are basically done by mainstream frameworks, and they are relatively mature. Most application developers will not be exposed to this part of things, so the corresponding information is relatively small. google took a look at the library and found that someone had done the corresponding parsing and recorded it.

What is Vue-hot-reload-api?

As we all know, the *. Vue file provides a good development experience for the vast number of developers. The principle of vue-loader is not much detailed. In the scaffold of vue, web pack parses the *. Vue file through vue-loader, separates template, js and style files and lets the corresponding loader process them.

In this process, vue-loader will do other things, such as injecting hot-reload code into client side, compiling at build time, and so on.

The hmr principle of webpack is not much to say. The hot loading of vue is to update components by injecting code. Here's the document and source code used.

usage

Let's look at the official documents first.

You will only use this when developing a Vue components-based build tool. For common applications, use vue-loader or vueify.

It is clearly stated in the document that this is not needed for general use, but only for the development of the corresponding build tools.

// Define a component as an option object
// In vue-loader, this object is Component.options.
const myComponentOptions = {
  data () { ... },
  created () { ... },
  render () { ... }
}

// Detection of Webpack's HRM API
// https://doc.webpack-china.org/guides/hot-module-replacement/
if (module.hot) {
  const api = require('vue-hot-reload-api')
  const Vue = require('vue')

  // Install API to Vue and check version compatibility
  api.install(Vue)

  // Use api.compatible to check compatibility after installation
  if (!api.compatible) {
    throw new Error('vue-hot-reload-api With the present Vue Version incompatible')
  }

  // This module accepts thermal overload
  // To add a word here, webpack's documentation about hmr is just too....
  // The implementation of hmr in loader s of major frameworks is based on their own modules receiving updates.
  module.hot.accept()

  if (!module.hot.data) {
    // To make the options in each component hot-loadable,
    // You need to create a record with a non-repetitive id.
    // Just do it once at startup.
    api.createRecord('very-unique-id', myComponentOptions)
  } else {
    // If a component only modifies the template or render function,
    // Just re-render all the relevant instances without destroying and rebuilding them.
    // In this way, the current state of the application can be completely maintained.
    api.rerender('very-unique-id', myComponentOptions)

    // Or

    // If a component changes options other than template or render,
    // You need to reload the whole thing.
    // This will destroy and rebuild the entire component (including subcomponents).
    api.reload('very-unique-id', myComponentOptions)
  }
}

The interface exposed by the vue-hot-reload-api is still very clear through the use of instructions. Let's look at the specific source code implementation.

var Vue // late bind
var version

// Global object _VUE_HOT_MAP_ to save all constructors and instances
var map = window.__VUE_HOT_MAP__ = Object.create(null)
var installed = false

// This parameter determines whether vue-loader or vueify is calling
var isBrowserify = false

// The initialization hook name before version 2.0.0-alpha.7 is init, which is a parameter to distinguish
var initHookName = 'beforeCreate'

exports.install = function (vue, browserify) {
  if (installed) return
  installed = true

// Judging whether the package is esodule or normal js function
  Vue = vue.__esModule ? vue.default : vue
  version = Vue.version.split('.').map(Number)
  isBrowserify = browserify

  // compat with < 2.0.0-alpha.7
  if (Vue.config._lifecycleHooks.indexOf('init') > -1) {
    initHookName = 'init'
  }

  exports.compatible = version[0] >= 2
  // Compatibility, 1.x and 2.x frameworks and loader implementations are quite different
  if (!exports.compatible) {
    console.warn(
      '[HMR] You are using a version of vue-hot-reload-api that is ' +
      'only compatible with Vue.js core ^2.0.0.'
    )
    return
  }
}

/**
 * Create a record for a hot module, which keeps track of its constructor
 * and instances
 *
 * @param {String} id
 * @param {Object} options
 */

exports.createRecord = function (id, options) {
  var Ctor = null
  // Determine whether the incoming options are objects or functions
  if (typeof options === 'function') {
    Ctor = options
    options = Ctor.options
  }
  // Dry up, this function injects hook functions into the lifecycle of components when they are initialized and terminated
  // When instantiated, hook function calls record instances in the map
  // destroy then deletes the instance itself from the map
  makeOptionsHot(id, options)
  
  map[id] = {
    Ctor: Vue.extend(options),
    instances: []
  }
}

/**
 * Make a Component options object hot.
 *
 * @param {String} id
 * @param {Object} options
 */

function makeOptionsHot (id, options) {
// Inject hook function to execute after the corresponding declaration period
  injectHook(options, initHookName, function () {
    map[id].instances.push(this)
  })
  injectHook(options, 'beforeDestroy', function () {
    var instances = map[id].instances
    instances.splice(instances.indexOf(this), 1)
  })
}

/**
 * Inject a hook to a hot reloadable component so that
 * we can keep track of it.
 *
 * @param {Object} options
 * @param {String} name
 * @param {Function} hook
 */

function injectHook (options, name, hook) {
// Determine whether the life cycle init/before Destroy already has a function when it is not injected
// If it does not exist, set the life cycle function to [hook]
// If it exists, judge whether it's Array, and connect existing functions to hook s.
  var existing = options[name]
  options[name] = existing
    ? Array.isArray(existing)
      ? existing.concat(hook)
      : [existing, hook]
    : [hook]
}

// I have to say that at first I really didn't understand why I had to cover it up.
// When you realize it, you know when an error pops up
// If you don't catch the error manually, the webpack receives it and immediately locates. reload ()
// It's too late to look at the tips given before reload.
// So you have to handle the error manually.
function tryWrap (fn) {
  return function (id, arg) {
    try { fn(id, arg) } catch (e) {
      console.error(e)
      console.warn('Something went wrong during Vue component hot-reload. Full reload required.')
    }
  }
}

exports.rerender = tryWrap(function (id, options) {
  var record = map[id]
  // Boundary Processing
  // If no options are passed or empty
  // All instances generated by this constructor are forced to refresh and return
  if (!options) {
    record.instances.slice().forEach(function (instance) {
      instance.$forceUpdate()
    })
    return
  }
  // Determine whether it is a constructor or proto?
  if (typeof options === 'function') {
    options = options.options
  }
  
  // Modify the Ctor in the map object to record
  record.Ctor.options.render = options.render
  record.Ctor.options.staticRenderFns = options.staticRenderFns
  // The slice method ensures that the length of instances is effective
  record.instances.slice().forEach(function (instance) {
    // Point updated module render functions and static methods to old instances
    // reset static trees
    // Then refresh
    instance.$options.render = options.render
    instance.$options.staticRenderFns = options.staticRenderFns
    instance._staticTrees = [] // reset static trees
    instance.$forceUpdate()
  })
})

exports.reload = tryWrap(function (id, options) {
  var record = map[id]
  if (options) {
    if (typeof options === 'function') {
      options = options.options
    }
    makeOptionsHot(id, options)
    if (version[1] < 2) {
      // preserve pre 2.2 behavior for global mixin handling
      record.Ctor.extendOptions = options
    }
    
    // In fact, the original commit did not inherit the parent class of Ctor, but directly Vue.extend(options)
    // I don't know very much about vue. I don't know why I changed it to this way.
    // Interested students can think about it.
    var newCtor = record.Ctor.super.extend(options)
    record.Ctor.options = newCtor.options
    record.Ctor.cid = newCtor.cid
    record.Ctor.prototype = newCtor.prototype
    // 2.0 Early Version Compatibility
    if (newCtor.release) {
      // temporary global mixin strategy used in < 2.0.0-alpha.6
      newCtor.release()
    }
  }
  record.instances.slice().forEach(function (instance) {
  // Judging whether vNode and context exist
  // Non-existent need to be refreshed manually
    if (instance.$vnode && instance.$vnode.context) {
      instance.$vnode.context.$forceUpdate()
    } else {
      console.warn('Root or manually mounted instance modified. Full reload required.')
    }
  })
})

More than 100 lines of code, starting with the library's first commit supporting 2.x, slowly evolved from simple implementation to coverage of most boundaries and compatibility considerations, to vue-loader calls, web pack hmr pits and debug, which were inspiring.

More front-end, fitness content, please click Blogs to be blogged See

Topics: Javascript Vue Webpack Google