前言

热替换(Hot Module Replacement)或热重载(Hot Reload)是指在不停机状态下,实时更新,在前端利于来说,在各大框架中及库中都有体现,比如NG从5开始就提供了热更新,RN也有对应的热更新技术,其实客户端技术很早就已经有这方面的探索,本文主要针对Vue脚手架的热更新,其实主要是Vue-hot-reload-api这个包的应用,对webpack的HMR比较感兴趣的同学推荐冉四夕大佬的这篇文章Webpack HMR 原理解析,多说一句,个人认为Webpack可能是最好的node.js的工具库应用。

目录

  • vue-cli脚手架结构
  • vue-hot-reload-api源码分析
  • vue-cli热更新 vs webpack热更新

探索案例

vue-cli脚手架结构

[目录结构]

  • bin

    • vue
    • vue-build
    • vue-init
    • vue-list
  • lib
    • ask.js (自定义工具-用于询问开发者)
    • check-version.js
    • eval.js
    • filter.js (自定义工具-用于文件过滤)
    • generate.js
    • git-user.js
    • local-path.js
    • logger.js (自定义工具-用于日志打印)
    • options.js (自定义工具-用于获取模板配置)
    • warnings.js

[目录描述] vue-cli2的目录结构就是bin下的相关模块,vue-cli最新版将各个模块又单独抽成了独立的文件,并引入了插件机pwa等相关周边的工具引入,使得脚手架更加丰富(见下图),但主要构建流程并未改变,主要就是在bin目录下去配置相关的命令,主要用到了commander处理命令行的包,对于想独立开发个人脚手架的同学可以参考这两篇文章教你从零开始搭建一款前端脚手架工具,走进Vue-cli源码,自己动手搭建前端脚手架工具

vue-hot-reload-api源码分析

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) returninstalled = trueVue = vue.__esModule ? vue.default : vueversion = Vue.version.split('.').map(Number)isBrowserify = browserify// compat with < 2.0.0-alpha.7if (Vue.config._lifecycleHooks.indexOf('init') > -1) {initHookName = 'init'}exports.compatible = version[0] >= 2if (!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]) returnlet Ctor = nullif (typeof options === 'function') {Ctor = optionsoptions = Ctor.options}makeOptionsHot(id, options)map[id] = {Ctor,options,instances: []}
}/*** Check if module is recorded** @param {String} id*/exports.isRecorded = (id) => {return typeof 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.renderoptions.render = (h, ctx) => {const instances = map[id].instancesif (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].instancesinstances.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 (!(key in 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.renderrecord.Ctor.options.staticRenderFns = options.staticRenderFnsrecord.instances.slice().forEach(instance => {instance.$options.render = options.renderinstance.$options.staticRenderFns = options.staticRenderFns// reset static trees// pre 2.5, all static trees are cached together on the instanceif (instance._staticTrees) {instance._staticTrees = []}// 2.5.0if (Array.isArray(record.Ctor.options.cached)) {record.Ctor.options.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 updateconst restore = patchScopedSlots(instance)instance.$forceUpdate()instance.$nextTick(restore)})} else {// functional or no instance created yetrecord.options.render = options.renderrecord.options.staticRenderFns = options.staticRenderFns// handle functional component re-renderif (record.options.functional) {// rerender with full optionsif (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._injectStylesif (injectStyles) {const render = options.renderrecord.options.render = (h, ctx) => {injectStyles.call(ctx)return render(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 behavior for global mixin handlingrecord.Ctor.extendOptions = options}const newCtor = record.Ctor.super.extend(options)// prevent record.options._Ctor from being overwritten accidentallynewCtor.options._Ctor = record.options._Ctorrecord.Ctor.options = newCtor.optionsrecord.Ctor.cid = newCtor.cidrecord.Ctor.prototype = newCtor.prototypeif (newCtor.release) {// temporary global mixin strategy used in < 2.0.0-alpha.6newCtor.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.')}})
})// 2.6 optimizes template-compiled scoped slots and skips updates if 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.jsconst original = instance._uinstance._u = slots => {try {// 2.6.4 ~ 2.6.6return original(slots, true)} catch (e) {// 2.5 / >= 2.6.7return original(slots, null, true)}}return () => {instance._u = original}
}

整体来说vue-hot-reload-api的思路还是很清晰的,主要就是通过维护一个map映射对象,通过对component名称进行对比,这里主要维护了一个Ctor对象,通过hook的方法在vue的生命周期中进行watch监听,然后更新后进行rerender以及reload

vue-cli热更新 vs webpack热更新

vue-cli热重载和webpack热更新不同的区别主要在于:
1、依赖:vue-cli热重载是强依赖于vue框架的,利用的是vue自身的Watcher监听,通过vue的生命周期函数进行名称模块的变更的替换;而webpack则是不依赖于框架,利用的是sock.js进行浏览器端和本地服务端的通信,本地的watch监听则是webpack和webpack-dev-server对模块名称的监听,替换过程用的则是jsonp/ajax的传递;
2、粒度:vue-cli热重载主要是利用的vue自身框架的component粒度的更新,虽然vue-cli也用到了webpack,其主要是打包和本地服务的用途;而webpack的热更新则是模块粒度的,其主要是模块名称的变化定位去更新,由于其自身是一个工具应用,因而不能确定是哪个框架具体的生命周期,因而其监听内容变化就必须通过自身去实现一套类似的周期变化监听;
3、定位:vue-cli定位就是vue框架的命令行工具,因而其并不需要特别大的考虑到双方通信以及自定义扩展性等;而webpack本身定位在一个打包工具,或者说其实基于node.js运行时环境的应用,因而也就决定了它必须有更方便、更个性化的扩展和抽象性

总结

在简单小型项目中,直接使用vue-cli脚手架进行vue相关应用的开发即可,但在开发过程中遇到了相关不太明白的渲染问题,也需要弄懂弄通其深层原理(ps:这次就是基于一个生命周期渲染的问题引发的探究,大概描述下就是页面渲染在f5刷新和vue-cli热重载下会出现不同的数据形式,然后便研究了下vue-cli的源码);而对于大型定制化项目,或者说需要对前端项目组提供一整套的前端工程化工具模板的开发,webpack还是首当其冲的选择,毕竟webpack还是在node.js运行时下有压倒性优势的工具应用。

参考

  • vue-cli官网
  • Vue-hot-reload-api 源码解析
  • 走进Vue-cli源码,自己动手搭建前端脚手架工具
  • Webpack HMR 原理解析

Vue脚手架热更新技术探秘相关推荐

  1. vue init webpack缺少标识符_Vue脚手架热更新技术探秘

    前言 热替换(Hot Module Replacement)或热重载(Hot Reload)是指在不停机状态下,实时更新,在前端利于来说,在各大框架中及库中都有体现,比如NG从5开始就提供了热更新,R ...

  2. vue项目 热更新慢

    vue项目 --- 热更新慢 一.查找热更新慢是哪里慢---分析原因 二.解决办法 1.安装babel-plugin-dynamic-import-node插件 2..babelrc文件里添加配置dy ...

  3. Unity热更新技术整理

    一.热更新学习介绍 1.什么是热更新 举例来说: 游戏上线后,玩家下载第一个版本(70M左右或者更大),在运营的过程中,如果需要更换UI显示,或者修改游戏的逻辑,这个时候,如果不使用热更新,就需要重新 ...

  4. vue created 调用方法_深入解析 Vue 的热更新原理,偷学尤大的秘籍?

    大家都用过 Vue-CLI 创建 vue 应用,在开发的时候我们修改了 vue 文件,保存了文件,浏览器上就自动更新出我们写的组件内容,非常的顺滑流畅,大大提高了开发效率.想知道这背后是怎么实现的吗, ...

  5. iOS代码质量要求_Unity移动端代码热更新技术学习总结

    为什么需要热更新 游戏总是伴随着不断的开发与维护,我们不能要求玩家每次都将游戏客户端卸载重装,所以需要热更新技术来在不需要重装客户端的情况下下载更新游戏里的代码(其实资源也需要热更新,但是因为操作系统 ...

  6. Unity3D热更新技术点——ToLua(上)

    注: 本文主要介绍tolua的基本原理及其在unity中的使用,希望阅读本文的读者有lua基础,可通过Lua教程 (其中也有IDE的推荐等)或其他途径先进行lua 的学习 热更新 在介绍tolua前, ...

  7. Unity HybridCLR热更新技术实现

    最近我们在项目中遇到了一个问题:经常需要修改游戏逻辑,如果每次修改都需要重新打包发布,那将会非常耗时,于是我们开始寻找解决方案.最后我们找到了 Unity HybridCLR 热更新技术,实现了游戏逻 ...

  8. iOS 热更新技术探索

    1.什么是热更新. 受限于iOS平台需要先审核在上线,一旦线上发现bug,想要修复还需要等到下次版本提交,这无形中会带给我们一些困扰,尤其是一些BAT量APP,所以热更新技术应运而生. 2.热更新解决 ...

  9. Android热更新技术的研究与实现Sophix

    所以阿里爸爸一直在进步着呢,知道技术存在问题就要去解决问题,这不,从Dexposed-->AndFix-->HotFix-->Sophix,技术是越来越成熟了. Android热更新 ...

  10. 深入解析 Vue 的热更新原理,尤大是如何巧用源码中的细节?

    大家都用过 Vue-CLI 创建 vue 应用,在开发的时候我们修改了 vue 文件,保存了文件,浏览器上就自动更新出我们写的组件内容,非常的顺滑流畅,大大提高了开发效率.想知道这背后是怎么实现的吗, ...

最新文章

  1. HDLBits 系列(39)求解带有奇校验的串口接收数据的简化电路设计
  2. ajaxFileUpload文件上传
  3. ubuntu websocket python2
  4. pyton中的self具体含义,加self与不加self有什么区别?
  5. python 模块(Module)和包
  6. ASP.NET 生命周期(原文翻译)
  7. 在Vista系统中,Flash渲染功能无法使用,咋办?
  8. jsp的九大内置对象以及作用
  9. 数据结构之数组和列表
  10. 英语基础语法(九)-被动语态
  11. Qt 之显示网络图片
  12. java绘图-绘制图片
  13. R语言重复测量方差分析
  14. 【听课笔记】复旦大学遗传学_02非孟德尔遗传+数量性状遗传
  15. 孙剑:如何打造云、端、芯上的视觉计算 | CCF-GAIR 2018
  16. CAM350 V10.5/V14.6 导出拼板gerber文件
  17. python爬虫学习笔记分析Ajax爬取果壳网文章
  18. 给博客添加rss订阅
  19. Eigen/Matlab库矩阵运算方法
  20. python能替代sql_SQL和Python 哪个更容易自学?

热门文章

  1. Matlab 之meshgrid interp griddata interp2
  2. windows搭建wordpress方法-windows搭建wordpress教程
  3. MCE公司:新型STAT3抑制剂
  4. IJCAI2022论文合集(持续更新中)
  5. C3P0,alibaba连接池错误【已解决】-An exception occurred while acquiring a poolable resource. Will retry.
  6. 光线CMS系统,如何显示幻灯片
  7. 数学建模学习笔记(1):层次分析法(AHP)(附有详细使用步骤)
  8. Vue使用Electron获取电脑MAC地址
  9. 【Leetcode】打气球的最大分数 (暴力递归+动态规划)
  10. 微信关注公众号跳转关注页面