前言热模块更换或热重载是指在不停机的情况下实时更新。它对前端以及主要框架和库都有好处。两者都反映出来了。比如NG从5开始就提供了热更新,RN也有相应的热更新技术。事实上,客户端技术在这方面已经进行了很长时间的探索。本文主要针对Vue脚手架的热更新,其实主要是针对Vue-hot-reload-api包的应用,对webpack的HMR比较感兴趣的同学推荐冉思喜的这篇文章,原理分析WebpackHMR。再说一句,我个人认为Webpack可能是最好的node.js工具库应用。目录vue-cli脚手架结构vue-hot-reload-api源码分析vue-cli热更新vswebpack热更新探索案例vue-cli脚手架结构【目录结构】binvue-buildvue-initvue-listlibask.js(自定义工具-使用询问开发者)check-version.jseval.jsfilter.js(自定义工具-文件过滤)generate.jsgit-user.jslocal-path.jslogger.js(自定义工具-日志打印)options.js(自定义工具-用于获取模板配置)warnings.js[目录说明]vue-cli2的目录结构是bin下的相关模块。最新版本的vue-cli将各个模块抽离成一个独立的文件,并导入了插件机pwa等相关周边工具的引入,丰富了脚手架(见下图),但主要的搭建流程没有变化。主要是在bin目录下配置相关命令。命令行包主要是commander使用的。想独立开发个人脚手架的同学,可以参考这两篇文章教你从零开始搭建前端脚手架工具,走进Vue-cli源码,自己搭建前端脚手架工具vue-hot-reload-api源码分析letVue//latebindletversionconstmap=Object.create(null)if(typeofwindow!=='undefined'){window.__VUE_HOT_MAP__=map}letinstalled=falseletisBrowserify=falseletinitHookName='beforeCreate'exports.install=(vue,browserify)=>{if(installed)returninstalled=trueVue=vue.__esModule?vue.default:vueversion=Vue.version.split('.').map(Number)isBrowserify=browserify//与<2.0.0-alpha兼容。7if(Vue.config._lifecycleHooks.indexOf('init')>-1){initHookName='init'}exports.compatible=version[0]>=2if(!exports.compatible){console.warn('[HMR]您使用的vue-hot-reload-api版本是'+'仅与Vue.js核心^2.0.0兼容。')return}}/***为热模块创建记录,它跟踪它的构造函数*和实例**@param{String}id*@param{Object}options*/exports.createRecord=(id,options)=>{if(map[id])returnletCtor=nullif(typeofoptions==='function'){Ctor=optionsoptions=Ctor.options}makeOptionsHot(id,options)map[id]={Ctor,options,instances:[]}}/***检查模块被记录**@param{String}id*/exports.isRecorded=(id)=>{returntypeofmap[id]!=='undefined'}/***使组件选项对象成为热点。**@param{String}id*@param{Object}选项*/functionmakeOptionsHot(id,options){if(options.functional){constrender=options.renderoptions.render=(h,ctx)=>{constinstances=map[id].instancesif(ctx&&instances.indexOf(ctx.parent)<0){instances.push(ctx.parent)}returnrender(h,ctx)}}else{injectHook(options,initHookName,function(){constrecord=map[id]if(!record.Ctor){记录。Ctor=this.constructor}record.instances.push(this)})injectHook(options,'beforeDestroy',function(){constinstances=map[id].instancesinstances.splice(instances.indexOf(this),1)})}}/***向可热重载组件注入一个钩子,以便*我们可以跟踪它。**@param{Object}options*@param{String}name*@param{Function}hook*/functioninjectHook(options,name,hook){constexisting=options[name]options[name]=existing?数组.isArray(现有)?existing.concat(挂钩):[existing,hook]:[hook]}functiontryWrap(fn){return(id,arg)=>{try{fn(id,arg)}catch(e){console.error(e)console.warn('SomethingVue组件热重载期间出错。需要完全重载。')}}}functionupdateOptions(oldOptions,newOptions){for(constkeyinoldOptions){if(!(keyinnewOptions)){deleteoldOptions[key]}}for(constkeyinnewOptions){oldOptions[key]=newOptions[key]}}exports.rerender=tryWrap((id,options)=>{constrecord=map[id]if(!options){record.instances.slice().forEach(instance=>{instance.$forceUpdate()})return}if(typeofoptions==='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//重置静态树//2.5之前,所有静态树都缓存在实例上if(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)){实例。$options.cached=[]}//post2.5.4:v-once树缓存在instance._staticTrees上。//纯静态树缓存在staticRenderFns数组中//(两者都已在上面重置)//2.6:暂时将渲染范围内的插槽标记为不稳定,以便//可以强制子组件更新constrestore=patchScopedSlots(instance)instance。$forceUpdate()instance.$nextTick(restore)})}else{//功能正常或尚未创建实例record.options.render=options.renderrecord.options.staticRenderFns=options.staticRenderFns//处理功能组件重新渲染if(record.options.functional){//使用完整选项重新渲染if(Object.keys(options).length>2){updateOptions(record.options,options)}else{//仅模板重新渲染。//需要为CSS模块注入样式注入代码//才能正常工作。constinjectStyles=record.options._injectStylesif(injectStyles){constrender=options.renderrecord.options.render=(h,ctx)=>{injectStyles.call(ctx)returnrender(h,ctx)}}}记录.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)=>{constrecord=map[id]if(options){if(typeofoptions==='function'){options=options.options}makeOptionsHot(id,options)if(record.Ctor){if(version[1]<2){//为全局mixin处理保留pre2.2行为record.Ctor.extendOptions=options}constnewCtor=record.Ctor.super.extend(options)//防止record.options。_Ctor被意外覆盖newCtor.options._Ctor=record.options._Ctorrecord.Ctor.options=newCtor.optionsrecord.Ctor.cid=newCtor.cidrecord.Ctor.prototype=newCtor.prototypeif(newCtor.release){//在<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('Rootormanuallymountedinstancemodified.Fullreloadrequired.')}})})//2.6优化模板编译作用域槽并跳过子更新//仅使用作用域插槽。我们需要修补作用域插槽解析助手//以暂时将所有作用域插槽标记为不稳定,以强制子//updates.functionpatchScopedSlots(instance){if(!instance._u)return//https://github。com/vuejs/vue/blob/dev/src/core/instance/render-helpers/resolve-scoped-slots.jsconstoriginal=instance._uinstance._u=slots=>{尝试{//2.6.4~2.6.6returnoriginal(slots,true)}catch(e){//2.5/>=2.6.7returnoriginal(slots,null,true)}}return()=>{instance._u=original}}总的来说,vue-hot-reload-api的思路还是很清晰的,主要是维护一个map映射对象,通过比较组件名,这里主要维护一个Ctor对象,通过hook的方式在vue中监听watch监控循环,然后rerender和reloadvue-cli更新后热更新vswebpack热更新vue-cli热重载和webpack热更新的区别主要在于:1.依赖性:vue-cli热重载强依赖vue框架,使用vue自带的Watcher监控,通过vue的生命周期函数替换名称模块的更改;而webpack不依赖框架,使用sock.js实现浏览器与本地服务器的通信,本地watch监听为webpack和webpack-dev-server模块名监听,替换过程使用jsonp/阿贾克斯交付;2.粒度:vue-cli热重载主要是更新vue自身框架的组件粒度。vue-cli虽然也使用了webpack,但其他主要用于打包和本地服务;而webpack的热更新是模块粒度的,主要是更新模块名变化的位置。由于是工具类应用,具体是哪个框架的生命周期无法确定。因此,其监控内容的变化必须自行实现类似的周期性变化监控;3、定位:vue-cli定位是vue框架的命令行工具,所以不需要考虑双方的通信和定制的Scalability等;而webpack本身的定位是一个打包工具,或者说其实是一个基于node.js运行环境的应用,这就决定了它必须有更方便、更个性化的扩展和抽象。总结在简单的小项目过程中,可以直接使用vue-cli脚手架开发vue相关的应用,但是在开发过程中遇到相关的渲染问题不是很懂,需要了解它深层原理(ps:这次是基于一个生活周期渲染问题引起的探索,大致描述是f5刷新和vue-cli热重载下页面渲染会出现不同的数据形式,然后研究下vue-cli的源代码);而对于大型定制项目,或者需要为前端项目组提供一套完整的前端工程工具模板开发。Webpack仍然是首选。毕竟webpack在node.js运行的时候还是一个压倒性的工具应用。参考vue-cli官网Vue-hot-reload-api源码分析走进Vue-cli源码,自己搭建前端脚手架工具WebpackHMR原理分析
