VUE問題:You are using the runtime-only build of Vue where the template compiler is not available.

轉載至 https://segmentfault.com/a/1190000006435886

Troubleshooting of upgrading Vue from 1.0 to 2.0

系列文章:

  1. Vue 2.0 升(cai)級(keng)之旅 (本文)

  2. Vuex — The core of Vue application

  3. 從單頁應用(SPA)到服務器渲染(SSR)

本文不包含 Vue 2.0 所有新特性,如 SSR 等,本文並沒有涉及,本文只包含 個人博客項目 升級中所遇到的經驗分享,如有興趣,可以查看 Vue 2.0 changes log

前言

這節淨是些嘮叨,只想看升(tian)級(keng)的可直接跳過。

從去年年底開始寫博客,那時對怎麼搞個博客網站一竅不通,看別人用 Github Pages 寫博客挺讚的,就也想搞個玩玩。技術選型時,在 jekyll 和 hexo 中選擇了前者,或許你會問爲什麼?估計當時大腦的供氧量不足了吧...

於是,我的博客就這麼誕生了。(jekyll 版的博客已經廢棄了,如果你有興趣,可以查看之前的提交

可是,用久了就發現並不怎麼好用,雖然支持 markdown,可代碼塊要轉換成 highlighter 標籤;其次,主題模板是挺好看,可換成中文字雜就那麼彆扭哪;還有,對 jekyll 的模板又不熟,自定義也不方便。

年初有一天,突然想到自己也是搞技術的,爲啥不自己搭一個博客網站哪?對,順帶還能學學新技術,何樂而不爲。又到了技術選型的時候了,這次擺在我面前又有 2 個選擇,React 和 Vue,這次我選擇了後者。

Why?因爲,後者更輕量級,也更貼近我熟悉的 Angular 的語法,還有,那時網上就有說今年 4 月 Vue 會升級到 2.0 和 Vue 兼具 React 和 Angular 的優點等等。(好吧,老實說,不選 React 只是因爲不喜歡 JSX 而已。-_-||)

So,我就用 Vue 1.10+ 搭建了自己的新博客——Disciple.Ding Blog(點這裏看源碼),並漸漸地往裏添加一些新學到的東西,ES6webpackdocker 等,並在 DAOcloud 上發佈了。(免費用了人家那麼久的服務,在這裏做個硬廣也是應該的,DAOcloud 的確很好用,特別和 Github 綁定之後能自動構建,應用更新也及其簡單,只是有個缺點就是有帶寬限制。)

在不久之前,Vue 如約發佈了 2.0 版本。正如計劃之初,博客 Vue 的版本也將升級到 2.0。

說了那麼多,再不進入正題就要變成標題黨了。好,那就開始我們的升(cai)級(keng)之旅。

升(tian)級(keng)之旅

首先,升級依賴。

npm install vue@next vue-router@next --save

import vue

順利安裝完成並按 changelog 做了修改之後,啓動項目也正常,當我興致勃勃地打開 Browser,駕輕就熟地輸入 localhost,並自然而然地按下 Enter,一切水到渠成。

然而,迎接我的竟是一片白板,控制檯裏赫然映着一串紅字。

[Vue warn] : You are using the runtime-only build of Vue where the template option is not available. Either pre-compile the templates into render functions, or use the compiler-included build. (found in root instance)

What? template 選項不能用了,changelog 沒提到啊?但 vue-router 的例子中都在用啊,什麼鬼?甚至我將代碼全部替換成例子中的代碼依舊無法運行,但在 vue-router 項目裏就能跑,什麼鬼啊!

但是,我並不妥協,分別打斷點運行,發現兩者竟然跑的不是同一段代碼,納尼!

import vue from 'vue'

同樣的 import 語句,卻有不一樣的結果,vue-router 中引的是 vue.js,而在我的項目中引的竟然是 vue.common.js...common...mon...n...

爲什麼會引 vue.common.js,from 'vue' 不該引的是 vue.js 麼?這就要引入另一個知識點:package.json。

package.json 中的 main 屬性決定了,當項目被引入時,輸出的是哪個文件,而 vue 的 package.json 中的 main 指向的是 dist/vue.common.js

福利時間:推薦一個網站 json.is,它對 package.json 裏的每條屬性都有詳細的解釋。

找到了問題產生的原因,那麼解決也就輕而易舉了。

import vue from 'vue/dist/vue.js'

每次引用 vue 的時候都要寫那麼長,一點都不優雅,而且爲什麼 vue-router 的例子可以用啊?

我要一探究竟。確認了 vue-router 中依賴的 vue 的 package.json 文件中的 main 字段指向的也是 dist/vue.common.js。那就只有一個可能了,webpack 對引入做了處理,查看 webpack.config.js

module.exports = {
    // 省略...
    resolve: {
        alias: {
            'vue': 'vue/dist/vue.js'
        }
    },
    ...

果然啊~他用 webpack 的別名功能把 vue/dist/vue.js 命名成了 vue,防不勝防。

在自己項目的 wepack.config.js 裏同樣給 vue 起別名,這樣就又能愉快地使用 import vue from 'vue' 了。

你是不是以爲這樣就結束了?不,對待一個問題要刨根問底,不能不求甚解。

爲什麼 vue 默認導出的是 vue.common.js,它和 vue.js 的區別在哪裏,又有什麼關係?

這個問題在囧克斯的博客中有提到。

Vue 最早會打包生成三個文件,一個是 runtime only 的文件 vue.common.js,一個是 compiler only 的文件 compiler.js,一個是 runtime + compiler 的文件 vue.js。

也就是說,vue.js = vue.common.js + compiler.js,而如果要使用 template 這個屬性的話就一定要用 compiler.js,那麼,引入 vue.js 是最恰當的。

路由升級

vue-router 的升級並不困難,參照 Releases Note 上的註釋修改應該沒有什麼大問題,主要的變化有兩點:

  1. 路由配置從一系列的方法調用,變成了傳遞一個配置對象

  2. 原先的 v-link 指令,變成了 router-link Component,路徑指向用 to 屬性

正當你以爲會一路順風順水,輕鬆升級路由完成的時候,現實總會給你當頭一棒。

之前博客的 vue-router 中使用了 beforeEach 和 afterEach 方法,根據 Release Note

  • router.beforeEach (replaced by the beforeEach option)

  • router.afterEach (replaced by the afterEach option)

行,那我把它改到配置裏

const ROUTER_SETTING = {
    routes: [
        // 省略...
    ],
    beforeEach: () => { /* some function */ },
    afterEach: () => { /* some function */ }
}

But, not work. What's wrong?

難道我哪裏寫錯了?又經過我一番谷哥和查閱文檔之後,發現在下一個版本的 Release Note 中有這麼一段

beforeEach and afterEach are reverted as router instance methods (options removed). This makes it more convenient for plugins/modules to add hooks after the router instance has been created.

好吧,它又被恢復迴路由實例的方法了。那麼,改回去

const router = new VueRouter(ROUTER_SETTING);

router
    .beforeEach(() => { /* some function */ })
    .afterEach(() => { /* some function */ });

OK,這樣總好了吧。然而,並沒有...console 中報出無法從 undefined 中讀取 afterEach,好吧,我猜這應該是 beforeEach 中沒有像之前一樣返回路由對象,所以不能鏈式調用。

class VueRouter {
    // 省略...
    beforeEach (fn: Function) {
        this.beforeHooks.push(fn)
    }
    
    afterEach (fn: Function) {
        this.afterHooks.push(fn)
    }
    // 省略...
}

看一眼源碼,果然如此。

那再將之前的代碼稍作修改就可以了。

const router = new VueRouter(ROUTER_SETTING);

router.beforeEach(() => { /* some function */ });
router.afterEach(() => { /* some function */ });

不過,不能鏈式調用似乎沒之前的優雅了哪~

最後,提一下 vue-router 2.0 裏所有的 hook(就像之前的 beforeEachafterEach,以及每個路由狀態中的 beforeEnterbeforeRouteLeave等)都具有相同的參數簽名,這在 Release Note 中也有提到。

fn (toRoute, redirect, next) {
    // toRoute: {Object} 當前路由對象
    // redirect: {Function} 調用跳轉至另一路由
    // next: {Function} 調用繼續當前路由跳轉
    // 什麼都不做,則取消當前跳轉
}

路由升級完成後,如果控制檯沒有什麼報錯,那麼,路由可以相互切換了,那些不依賴數據讀取的組件已經可以正常顯示了。

那些依賴數據讀取的組件哪?

這就要提到組件的生命週期鉤子(即 lifecycle hooks)

Lifecycle hooks

生命週期鉤子應該算 vue 這次升級中 broken changes 最多的一部分了,對照 1.0 的文檔和 release note,作了下面這張表

vue 1.0+ vue 2.0 Description
init beforeCreate 組件實例剛被創建,組件屬性計算之前,如 data 屬性等
created created 組件實例創建完成,屬性已綁定,但 DOM 還未生成,$el 屬性還不存在
beforeCompile beforeMount 模板編譯/掛載之前
compiled mounted 模板編譯/掛載之後
ready mounted 模板編譯/掛載之後(不保證組件已在 document 中)
- beforeUpdate 組件更新之前
- updated 組件更新之後
- activated for keep-alive,組件被激活時調用
- deactivated for keep-alive,組件被移除時調用
attached - 不用了還說啥哪...
detached - 那就不說了吧...
beforeDestory beforeDestory 組件銷燬前調用
destoryed destoryed 組件銷燬後調用

知道了 hooks 升級前後的對應關係,那麼升級起來就輕而易舉了,改改組件的屬性名就可以了。

那麼,改完屬性名是不是就完成了?然而並沒有。

因爲,在 vue 1.0+ 中,如果一個組件和路由相關,那麼,它就可能不單單有自己組件的 lifecycle hooks,它還會有基於 vue-router 的 lifecycle hooks。

而在 vue 2.0 中,router lifecycle hooks 全部被移除了,因爲,這些 hooks 可以通過其他的方式來代替,這樣不但簡化了配置,還不用在組件中去處理路由相關的業務,降低了耦合。那這些 hooks 該如何替換,我們接下來就來看一下。

  • activate & deactivate:使用組件自身的 lifecycle hook 替代

  • data:通過組件 watch 屬性來監聽當前路由 $route 的變化

  • canActivate:由路由屬性 beforeEnter 來代替

  • canDeactivate:由路由屬性 beforeRouteLeave 來代替

  • canReuse:去除

那個這個是不是也直接改改屬性名就好了哪?

恩,差不多。不過需要注意的是,如果原先 hooks 中使用了有關路由信息的 transition 參數是肯定不能用了。比如,根據路由參數來進行查詢,原先通過 transition.to.params 獲取路由參數,現在就要通過剛剛提到的當前路由對象this.$route.params 來獲取。

在升級這裏的過程中,還遇到一個問題:當用戶輸入的 URL 滿足路由匹配,但根據路由參數無法獲得正確的文章時,我想讓路由直接跳轉到首頁。

在 1.0 版本中,我通過 transition.redirect('/'); 就輕鬆的回到了首頁,由於 2.0 中沒有 transition 參數,而 $route 只包含當前路由的信息,並不包換路由切換的操作。那該怎麼做哪?再一次谷哥和查閱文檔,然而一無所獲。

i choose death

最後在 vue-router 的例子中找到了解決問題的鑰匙——$router

$router 返回的是整個項目路由的實例,它是隻讀的。於是,剛剛那個問題就可以通過 this.$router.replace('/'); 來解決。

這裏還有一點,在 1.0 版本中組件配置 route 屬性時還可以設置一個叫 waitForData 的屬性。這個在 2.0 中,我還沒有找到直接的替換方式,不過,我在整個組件上添加 v-if 來處理。從理論和效果的角度上講,v-if 是可以替代原先的 waitForData 屬性,就似乎不那麼優雅。

剩餘其他小點,看控制檯報錯信息,然後查查 Release Note 都能輕鬆處理啦~

至此,我的整個 Blog 也升級完成了,歡迎來訪。(查看源碼戳這裏

寫在最後

如果現在再讓我選一個技術來搭博客的話,我會選 React。爲啥?

因爲 vue 我已經玩過啦,哈哈哈~

最後,借用外國網友的一句話:

I'm constantly rewriting / refactoring this silly little blog using the latest and buzziest tech, so that I can stay up to date on these libraries and frameworks.

這也是我自己搭博客,而不是直接使用博客系統的主要原因。

最後的最後,安利下自己的 Blog,以及 Source Code

歡迎交流,噴子繞道。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章