摘要:源碼解析起因最近在搞框架的熱加載方案,自然是少不了向成熟的框架學習偷窺。這將銷毀并重建整個組件包括子組件。通過使用說明可以看出,暴露的接口還是很清晰的,下面來看下具體源碼實現。
Vue-hot-reload-api 源碼解析 起因
最近在搞san框架的熱加載方案,自然是少不了向成熟的框架學習(偷窺ing)。熱加載方案基本也只是主流框架在做,且做的比較成熟,大部分應用開發者并不會接觸到這部分東西,所以相應的資料比較少。google了一下這個庫,發現木有人做相應的解析,順手記錄下好了。
什么是Vue-hot-reload-api?眾所周知,*.vue文件為廣大開發者提供了良好的開發體驗,vue-loader的原理不多贅述,在vue的腳手架中,webpack通過vue-loader來解析*.vue文件,把template、js和style文件分離并讓相應的loader去處理。
在這個過程中,vue-loader還會做些其他事情,比如向client端注入hot-reload相應的代碼,構建時編譯等等。
webpack的hmr原理也不多說了,vue的熱加載就是通過注入的代碼來實現組件的熱更新,下面來看下使用時的文檔和源碼。
用法先來看下官方文檔。
你僅會在開發一個基于 Vue components 構建工具的時候用到這個。對于普通的應用,使用 vue-loader 或者 vueify 就可以了。
文檔中明確說明了,一般使用不需要用到這個,只有在開發相應的構建工具時才會用到。
// 定義一個組件作為選項對象 // 在vue-loader中,這個對象是Component.options const myComponentOptions = { data () { ... }, created () { ... }, render () { ... } } // 檢測 Webpack 的 HMR API // https://doc.webpack-china.org/guides/hot-module-replacement/ if (module.hot) { const api = require("vue-hot-reload-api") const Vue = require("vue") // 將 API 安裝到 Vue,并且檢查版本的兼容性 api.install(Vue) // 在安裝之后使用 api.compatible 來檢查兼容性 if (!api.compatible) { throw new Error("vue-hot-reload-api與當前Vue的版本不兼容") } // 此模塊接受熱重載 // 在這兒多說一句,webpack關于hmr的文檔實在是太。。。 // 各大框架的loader中關于hmr的實現都是基于自身模塊接受更新來實現 module.hot.accept() if (!module.hot.data) { // 為了將每一個組件中的選項變得可以熱加載, // 你需要用一個不重復的id創建一次記錄, // 只需要在啟動的時候做一次。 api.createRecord("very-unique-id", myComponentOptions) } else { // 如果一個組件只是修改了模板或是 render 函數, // 只要把所有相關的實例重新渲染一遍就可以了,而不需要銷毀重建他們。 // 這樣就可以完整的保持應用的當前狀態。 api.rerender("very-unique-id", myComponentOptions) // --- 或者 --- // 如果一個組件更改了除 template 或 render 之外的選項, // 就需要整個重新加載。 // 這將銷毀并重建整個組件(包括子組件)。 api.reload("very-unique-id", myComponentOptions) } }
通過使用說明可以看出,vue-hot-reload-api暴露的接口還是很清晰的,下面來看下具體源碼實現。
var Vue // late bind var version // 全局對象__VUE_HOT_MAP__來保存所有的構造器和實例 var map = window.__VUE_HOT_MAP__ = Object.create(null) var installed = false // 這個參數來判斷是vue-loader還是vueify在調用 var isBrowserify = false // 2.0.0-alpha.7版本前的初始化鉤子名是init,這個參數來作區分 var initHookName = "beforeCreate" exports.install = function (vue, browserify) { if (installed) return installed = true // 判斷打包的是esodule還是普通的js函數 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 // 兼容性,1.x和2.x的框架實現和loader實現都有很大差異 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 // 判斷傳入的options是對象還是函數 if (typeof options === "function") { Ctor = options options = Ctor.options } // 燥起來,這個函數會在組件初始化和結束時的生命周期注入hook函數 // 當實例化以后,hook函數調用會把實例記錄到map中 // destroy后會從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) { // 注入hook函數,到達相應聲明周期后執行 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) { // 判斷未注入時,生命周期init/beforeDestroy是否已經有了函數 // 不存在的話,直接把生命周期函數置為[hook] // 存在的話,判斷是否為Array,從而把已存在的函數和hook連接起來 var existing = options[name] options[name] = existing ? Array.isArray(existing) ? existing.concat(hook) : [existing, hook] : [hook] } // 不得不說,這個一開始確實沒搞懂是為啥要包一層 // 自己實現的時候才知道,當有error彈出時 // 如果不手動這樣接住error,webpack會接到然后立即location.reload() // 根本來不及看reload之前給出的提示 // 所以要手動處理下error 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] // 邊界處理 // 如果沒有傳options或者已經為空 // 會把這個構造函數生成的所有實例強制刷新并返回 if (!options) { record.instances.slice().forEach(function (instance) { instance.$forceUpdate() }) return } // 判斷是否是構造函數還是proto if (typeof options === "function") { options = options.options } // 修改map對象中的Ctor以便記錄 record.Ctor.options.render = options.render record.Ctor.options.staticRenderFns = options.staticRenderFns // .slice方法保證了instances的length是有效的 record.instances.slice().forEach(function (instance) { // 把更新過的模塊render函數和靜態方法指到舊的實例上 // reset static trees // 然后重刷新 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 } // 其實最開始的commit中,并未繼承Ctor的父類,是直接Vue.extend(options) // 對vue了解不深,不知道為啥改成這樣 // 有興趣的同學可以思考下 var newCtor = record.Ctor.super.extend(options) record.Ctor.options = newCtor.options record.Ctor.cid = newCtor.cid record.Ctor.prototype = newCtor.prototype // 2.0早期版本兼容 if (newCtor.release) { // temporary global mixin strategy used in < 2.0.0-alpha.6 newCtor.release() } } record.instances.slice().forEach(function (instance) { // 判斷vNode和上下文是否存在 // 不存在的需要手動刷新 if (instance.$vnode && instance.$vnode.context) { instance.$vnode.context.$forceUpdate() } else { console.warn("Root or manually mounted instance modified. Full reload required.") } }) })
短短的100多行代碼,從這個庫支持2.x的第一個commit讀起,慢慢由簡單實現到覆蓋大部分邊界及兼容性考慮,再到vue-loader的調用,webpack的hmr各種坑和debug,這個過程很受啟發。
更多的前端、健身內容,請點擊 將就的博客查看
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/87350.html
摘要:筆者系貢獻者之一官方說明簡單來說就是將文件變成,然后放入瀏覽器運行。部分首先分析部分從做右到左,也就是被先后被和處理過了。源碼解析之二源碼解析之三寫作中源碼解析之四寫作中作者博客作者微博 筆者系 vue-loader 貢獻者(#16)之一 官方說明 vue-loader is a loader for Webpack that can transform Vue components ...
摘要:包中導出的默認是運行時構建。當然,我們期待的是只修改代碼,不用重新運行命令,甚至不需要刷新瀏覽器即看到代碼的改動效果,這時候需要新的插件來配置實現的熱重載。 首先已經全局安裝了node/vue/webpack; 新建文件夾demo4并初始化 cd demo4 npm init -y 這是頁面會生成一個package.json文件。 安裝webpack及相關插件 npm install ...
摘要:參考令人困惑的地方項目名稱項目名稱版本描述作者開源協議主文件指定了運行腳本命令的命令行縮寫,比如這是的指定了運行時,所要執行的命令。要解析并且完成相應的功能,這些基本都是必須的。 參考 Webpack——令人困惑的地方 package.json { name: 項目名稱, //項目名稱 version: 1.0.0, //版本 description: vue+...
摘要:但我們今天學的是,原因我還不會而且新手還是學習為主吧。原因中文文檔全,學習曲線簡單,很容易上手。后續總結在學習打包工具過程中由于出現的問題各種蛋疼,讓很多人都半途而廢。大家互相學習共同進步本節講的都是很基礎的東西,自己可以延展一下。 寫這篇文章的時候先說一下原因:webpack:現在很流行的打包工具;推薦原因:學習成本比gulp,fis3等簡單,就是這么直接!vue:國人開發的MVVM...
摘要:線上另加入了排行榜功能,如需查看源碼的,請切換到分支整個項目結構清晰,尤其單文件組件的表現力尤為突出,使得每個組件的邏輯都沒有過于復雜,而且在的統籌下,的單向數據流模式使得所有的變化都在可控制可預期的范圍內。 2016注定不是個平凡年,無論是中秋節問世的angular2,還是全面走向穩定的React,都免不了面對另一個競爭對手vue2。喜歡vue在設計思路上的先進性(原諒我用了這么一個...
閱讀 3076·2021-09-28 09:43
閱讀 908·2021-09-08 09:35
閱讀 1449·2019-08-30 15:56
閱讀 1192·2019-08-30 13:00
閱讀 2739·2019-08-29 18:35
閱讀 1836·2019-08-29 14:07
閱讀 3443·2019-08-29 13:13
閱讀 1337·2019-08-29 12:40