Vue scaffold thermal renewal technology exploration

preface

Hot Module Replacement or Hot Reload refers to real-time update in the non-stop state. In front of the benefit, it is reflected in various frameworks and libraries. For example, NG has provided Hot update from 5, RN also has the corresponding Hot update technology. In fact, client technology has been explored in this aspect for a long time. This paper mainly focuses on the hot update of Vue scaffolding. In fact, it is mainly the application of the Vue- hot-Reload-API package. By the way, I think Webpack is probably the best Node.js library application.

directory

  • Vue-cli scaffold structure
  • Vue-hot-reload-api source code analysis
  • Vue-cli hot update vs WebPack hot update

To explore the case

Vue-cli scaffold structure

[Directory structure]

  • bin
    • vue
    • vue-build
    • vue-init
    • vue-list
  • lib
    • Ask.js (custom tool – used to ask developers)
    • check-version.js
    • eval.js
    • Filter.js (Custom tool – for file filtering)
    • generate.js
    • git-user.js
    • local-path.js
    • Logger.js (custom tool – for log printing)
    • Options.js (custom tool – used to get template configuration)
    • warnings.js

The directory structure of VUe-CLI2 is the related modules under bin. The latest version of VUe-CLI extracts each module into an independent file, and introduces the plug-in machine PWA and other related peripheral tools to make the scaffolding more rich (see the following figure), but the main construction process has not changed. Main is in the bin directory configuration of the relevant commands, mainly using commander to deal with the command line package, for the independent development of personal scaffolding students can refer to these two articles teach you to build a front-end scaffolding tools from scratch, into the Vue- CLI source code, build their own front-end scaffolding tools

Vue-hot-reload-api source code analysis

let Vue // late bind
let version
const map = Object.create(null)
if(typeof window ! = ='undefined') {
  window.__VUE_HOT_MAP__ = map
}
let installed = false
let isBrowserify = false
let initHookName = 'beforeCreate'

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

  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
  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 = (id, options) => {
  if(map[id]) return

  let Ctor = null
  if (typeof options === 'function') {
    Ctor = options
    options = Ctor.options
  }
  makeOptionsHot(id, options)
  map[id] = {
    Ctor,
    options,
    instances: []
  }
}

/**
 * Check if module is recorded
 *
 * @param {String} id
 */

exports.isRecorded = (id) => {
  returntypeof map[id] ! = ='undefined'
}

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

function makeOptionsHot(id, options) {
  if (options.functional) {
    const render = options.render
    options.render = (h, ctx) => {
      const instances = map[id].instances
      if (ctx && instances.indexOf(ctx.parent) < 0) {
        instances.push(ctx.parent)
      }
      return render(h, ctx)
    }
  } else {
    injectHook(options, initHookName, function() {
      const record = map[id]
      if(! record.Ctor) { record.Ctor = this.constructor } record.instances.push(this) }) injectHook(options,'beforeDestroy'.function() {
      const 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) {
  const existing = options[name]
  options[name] = existing
    ? Array.isArray(existing) ? existing.concat(hook) : [existing, hook]
    : [hook]
}

function tryWrap(fn) {
  return (id, arg) => {
    try {
      fn(id, arg)
    } catch (e) {
      console.error(e)
      console.warn(
        'Something went wrong during Vue component hot-reload. Full reload required.')}}}function updateOptions (oldOptions, newOptions) {
  for (const key in oldOptions) {
    if(! (keyin newOptions)) {
      delete oldOptions[key]
    }
  }
  for (const key in newOptions) {
    oldOptions[key] = newOptions[key]
  }
}

exports.rerender = tryWrap((id, options) => {
  const record = map[id]
  if(! options) { record.instances.slice().forEach(instance => { instance.$forceUpdate()})return
  }
  if (typeof options === 'function') {
    options = options.options
  }
  if (record.Ctor) {
    record.Ctor.options.render = options.render
    record.Ctor.options.staticRenderFns = options.staticRenderFns
    record.instances.slice().forEach(instance => {
      instance.$options.render = options.render
      instance.$options.staticRenderFns = options.staticRenderFns
      // reset static trees
      // pre 2.5, all static trees are cached together on the instance
      if(instance._statictrees) {instance._statictrees = []} // 2.5.0if(Array. IsArray (record. Ctor. Options. The cached)) {record. Ctor. Options. The cached = []} / / 2.5.3if (Array.isArray(instance.$options.cached)) {
        instance.$options.cached = []} // post 2.5.4: v-once trees are cached on instance._staticTrees. // Pure static trees are cached on the staticRenderFns array // (both Already reset above) // 2.6: temporarily mark rendered scoped slots as unstable so that // child components can be forced to update const restore = patchScopedSlots(instance) instance.$forceUpdate()
      instance.$nextTick(restore)
    })
  } else {
    // functional or no instance created yet
    record.options.render = options.render
    record.options.staticRenderFns = options.staticRenderFns

    // handle functional component re-render
    if (record.options.functional) {
      // rerender with full options
      if (Object.keys(options).length > 2) {
        updateOptions(record.options, options)
      } else {
        // template-only rerender.
        // need to inject the style injection code for CSS modules
        // to work properly.
        const injectStyles = record.options._injectStyles
        if (injectStyles) {
          const render = options.render
          record.options.render = (h, ctx) => {
            injectStyles.call(ctx)
            returnRender (h, CTX)}}} record.options._Ctor = null // 2.5.3if (Array.isArray(record.options.cached)) {
        record.options.cached = []
      }
      record.instances.slice().forEach(instance => {
        instance.$forceUpdate()
      })
    }
  }
})

exports.reload = tryWrap((id, options) => {
  const record = map[id]
  if (options) {
    if (typeof options === 'function') {
      options = options.options
    }
    makeOptionsHot(id, options)
    if (record.Ctor) {
      if(version[1] < 2) {// Preserve pre 2.2 behaviorforglobal mixin handling record.Ctor.extendOptions = options } const newCtor = record.Ctor.super.extend(options) // prevent  record.options._Ctor from being overwritten accidentally newCtor.options._Ctor = record.options._Ctor record.Ctor.options = newCtor.options record.Ctor.cid = newCtor.cid record.Ctor.prototype = newCtor.prototypeif (newCtor.release) {
        // temporary global mixin strategy used in< 2.0.0-alpha.6 newctor.release ()}}else {
      updateOptions(record.options, options)
    }
  }
  record.instances.slice().forEach(instance => {
    if (instance.$vnode && instance.$vnode.context) {
      instance.$vnode.context.$forceUpdate()}else {
      console.warn(
        'Root or manually mounted instance modified. Full reload required.')}})}) optimizes template-compiled scoped slots and skips updatesif child
// only uses scoped slots. We need to patch the scoped slots resolving helper
// to temporarily mark all scoped slots as unstable in order to force child
// updates.
function patchScopedSlots (instance) {
  if(! instance._u)return// https://github.com/vuejs/vue/blob/dev/src/core/instance/render-helpers/resolve-scoped-slots.js const original = _u instance._u = slots => {try {// 2.6.4 to 2.6.6return original(slots, true)} catch (e) {// 2.5 / >= 2.6.7return original(slots, null, true)}}return () => {
    instance._u = original
  }
}
Copy the code

Overall, the concept of vue-hot-reload-API is clear. The main idea is to maintain a map object, compare the component name, and maintain a Ctor object. Hook the method to watch the vUE during its life cycle. After the update, rerender and Reload are performed

Vue-cli hot update vs WebPack hot update

The differences between VUe-CLI hot overload and Webpack hot update are as follows: 1. Dependency: Vue-CLI hot overload is strongly dependent on vUE framework. It uses vUE’s own Watcher to monitor and replace the name module changes through vue’s life cycle function. While Webpack does not depend on the framework, it uses sock.js to communicate between the browser side and the local server side. The local watch listener is webpack and webpack-dev-server to listen to the module name, and the replacement process uses jSONp/Ajax pass. 2. Granularity: Vue-CLI hot overloading mainly takes advantage of the component granularity updates of vUE’s own framework, although Vue-CLI also uses Webpack, which is mainly for packaging and local service purposes. Webpack’s hot update is module granularity, which is mainly based on the change of module name. As it is a tool application, it cannot determine the specific life cycle of the framework, so it must implement a similar set of periodic change monitoring through its own changes. 3. Positioning: VUE – CLI positioning is the command line tool of vUE framework, so it does not need to be particularly large for communication and custom extensibility. Webpack itself is positioned as a packaging tool, or rather an application based on the Node.js runtime environment, so it has to have more convenient and personalized extensions and abstractions

conclusion

In simple and small projects, vUE – CLI scaffolding can be directly used for the development of VUE related applications, but in the development process encountered related rendering problems are not clear, but also need to understand the deep principle (PS: this is based on a life cycle rendering problem triggered by the inquiry, The vue-CLI hot reload and the f5 refresh of the page render show different data forms. Webpack is the first choice for large custom projects, or for those that require a full set of front-end engineering tool templates for the front-end project team, since webPack is the predominant tool application when Node.js is running.

reference

  • Vue – cli’s official website
  • Vue-hot-reload-api source code parsing
  • Step into Vue- CLI source code, build their own front-end scaffolding tools
  • Principle analysis of Webpack HMR