這兩年,我主要從事部門周邊業(yè)務(wù),搭建了數(shù)個(gè)細(xì)碎的 web 應(yīng)用。最近由于一些人事變動(dòng),我又回到了最初的產(chǎn)品線上。時(shí)光飛逝,欣賞著自己的遺(la)產(chǎn)(ji)代碼,“青驄”歲月浮現(xiàn)眼前。今天就談?wù)勎业谝粋€(gè)web技術(shù)棧——nuxt.js,一款server/client
同構(gòu)渲染的 vue 框架。
安裝
當(dāng)年向領(lǐng)導(dǎo)層推薦 nuxt 的時(shí)候,我列了很多理由;雖然有些比較扯蛋,但當(dāng)時(shí)最核心的考量是,nuxt 可以幫助我們零基礎(chǔ)上手 web 應(yīng)用。我這里先說說如何安裝。nuxt 多年來一直使用的是 npm script 腳手架:操作就這么幾步:
- 在命令行里敲下:
yarn create nuxt-app <my-project>
- 按默認(rèn)選項(xiàng)安裝依賴
- 一句
yarn dev
,開工了!
在 vue-cli 滿天飛的今天,npm 腳手架自然算不上什么驚奇的事情了,但幾年前能做到零基礎(chǔ)啟動(dòng) vue 是很不容易的。我記得那時(shí)候市面上的資料還是基于 html 掛載 vue.js——把 vue 當(dāng)作一個(gè)類似于 Jquery 的 lib 使用。作為練習(xí)項(xiàng)目而言,以上足夠了;但是,放到一個(gè)企業(yè)級項(xiàng)目里,你要考慮其他問題,比如瀏覽器兼容、開發(fā)體驗(yàn)、代碼風(fēng)格……這就意味著你要配齊 vue 全家桶,各色 webpack、eslint、babel 配置,集成三方的 UI 庫,布置后端服務(wù)等等等等。在一個(gè)幾乎沒有任何現(xiàn)代開發(fā)經(jīng)驗(yàn)的項(xiàng)目組里,該怎么上手?從零開始學(xué)所有配置,或是在 github 的某個(gè)角落找到大體接近你需求的 demo,然后開始修改自己也不懂的各色文件?
也許,經(jīng)歷過那段時(shí)期的老員工依舊會對 nuxt 抱有微詞,畢竟坑是不可抗拒的。但在前端開發(fā)方面,nuxt(相比于單純的 vue 庫)還是給我們帶來了很多驚喜:它封裝了上述所有配置,限制了代碼風(fēng)格和目錄結(jié)構(gòu),提供了一個(gè)相對舒適的開發(fā)環(huán)境,我們因此得以在最短時(shí)間內(nèi)投入到了生產(chǎn)實(shí)踐中。
目錄結(jié)構(gòu)
作為一個(gè)通用框架,nuxt 給我們提供了什么?我最早是 1.4 版本開始的第一個(gè) nuxt 項(xiàng)目,到今天是 2.11 版本,nuxt 的目錄結(jié)構(gòu)幾乎沒有變化:
├── .nuxt
├── pages
├── components
├── layouts
├── assets
├── static
├── store
├── plugins
├── middleware
├── nuxt.config.js
-
.nuxt
nuxt 內(nèi)置了 webpack,當(dāng)運(yùn)行
nuxt build
后,它會將相關(guān)目錄的文件打包壓縮到.nuxt
目錄下,方便進(jìn)一步應(yīng)用部署。 -
pages
頁面文件目錄——vue 文件夾。該目錄下的目錄結(jié)構(gòu)和 vue 文件名會在 build 時(shí)映射為
vue-router
配置。我們來看看約定的路由規(guī)則:├── pages │ ├── index.vue │ ├── login.vue │ └── dashboard │ └── about.vue
上述目錄結(jié)構(gòu)最終會被描述為:
/
、/login
,以及/dashboard/about
三個(gè)路由。還可以設(shè)置動(dòng)態(tài)路由;如下所示,該路由將被映射為/job/:id
,并可以通過this.$route.params.id
獲取路由參數(shù)。├── pages │ └── job │ └── _id.vue
此外,各個(gè)頁面以及后續(xù)的引入模塊,會以懶加載的形式在路由跳轉(zhuǎn)后
import
;相比于傳統(tǒng)的 spa(單頁面應(yīng)用),ssr(服務(wù)端渲染)的 nuxt 單次加載的資源更少,這也成為了 nuxt 剛出來時(shí)的一個(gè)賣點(diǎn)——首頁渲染快。 -
components
組件目錄,顧名思義,用于放置 vue 的功能組件。注意,該目錄下的 vue 文件,不具有 nuxt 增強(qiáng)的生命周期鉤子,即它不能使用 asyncData、fetch 這類鉤子;這種限制我倒比較支持,組件和數(shù)據(jù)請求應(yīng)當(dāng)盡可能的正交化。
-
layouts
第三個(gè) vue 文件目錄,放置 page 布局,通俗來說就是不同頁面的通用模版。比如:我們自己寫了一個(gè)叫
/layouts/trophy.vue
的獎(jiǎng)杯布局,<!-- layouts/trophy.vue --> <template> <div id="app"> <header/> <main> <nuxt /> </main> </rooter> </div> </template>
pages 目錄下的頁面通過 layout 字段指定該類型模版,之后 page 內(nèi)容會填充到
<nuxt />
標(biāo)簽里。<!-- pages/index.vue --> <script> export default { layout: "trophy" }; </script>
-
asserts 和 static
asserts 和 static 就是所謂的資源目錄,存放 css、less、圖片等文件。Nuxt 集成了 css-loader、file-loader、url-loader 等 webpack 加載器,當(dāng)你把這些文件放在 asserts 目錄之下時(shí),nuxt 內(nèi)置的 webpack 會針對特定文件選擇特定加載器,打包、壓縮、拷貝到.nuxt 相應(yīng)路徑之下;而相對較大的資源文件,則可以放在 static 之下,webpack 將直接拷貝到.nuxt 之下
-
store
store 對應(yīng)的就是 vuex 狀態(tài)樹文件。nuxt 自帶 vue 全家桶,自然也包含了 vuex。store 里的 js 文件最終會被 nuxt 構(gòu)件為 vuex 相關(guān)功能配置。
-
plugins & middlewar
插件和中間件,用于定制化拓展。比如使用 vuetify,element 這類 ui 庫,我們就可以將相關(guān)配置放置在 plugins 里。又比如,你想對所有請求加權(quán)限控制,可以將配置放在 middlewar 里;所有資源的請求將先經(jīng)過這里的中間件,并做相應(yīng)的錯(cuò)誤處理。
-
nuxt.config.js
nuxt 框架本身的配置文件,可以定制框架功能。最常用的修改就是在這里調(diào)整默認(rèn)的 webpack 配置了,而且最近幾版新添的配置項(xiàng),主要方向也是在暴露內(nèi)置 webpack 的 api。
nuxt 目錄結(jié)構(gòu)的定型,稍早于 vue-cli 推薦結(jié)構(gòu)的普及;目錄結(jié)構(gòu)看似合理,但是與主流風(fēng)格稍有不同,還是略顯美中不足的。在項(xiàng)目開始前,最好先確定這類代碼規(guī)范;這樣,當(dāng)切換項(xiàng)目、新手入門,或是多團(tuán)隊(duì)合作時(shí),能盡最大可能減少“解碼成本”——理解項(xiàng)目邏輯所需的成本。我參與的項(xiàng)目由于歷史慣性,之后都沿用了 nuxt 風(fēng)格。
生命周期
上面提到過 nuxt 框架對 vue 生命周期的增強(qiáng)。我們刨去 vue 自帶的生命周期(簡化為 Render),看看增強(qiáng)部分。
-
nuxtServerInit
nuxtServerInit 只在首次請求到來時(shí)(第一次導(dǎo)航到站點(diǎn),或是刷新頁面時(shí)),在 server 端調(diào)用,與 client 端無關(guān)。設(shè)計(jì)的目的就是在 server 端異步請求數(shù)據(jù),并初始化全局 vuex 狀態(tài)樹。
-
middleware
第二步是請求通過各類中間件。上面提到過,我們會在 middlewar 目錄下放置各類中間件作為請求的過濾器。中間件包括 nuxt.config.js 配置的全局中間件,和 layout、page 里指定的組件級中間件兩種。
// middleware/authenticated.js export default function ({ store, redirect }) { if (!store.state.authenticated) { return redirect('/login') } }
<!-- /pages/index.vue --> <script> export default { middleware: 'authenticated' } </script>
-
validate
validate 是 page 里的鉤子方法,作用于動(dòng)態(tài)路由對映的頁面組件中。目的是配置一個(gè)校驗(yàn)方法,動(dòng)態(tài)檢驗(yàn)路由參數(shù)的有效性。
<!-- /pages/job/_id.vue --> <script> export default { validate({ params, query, store }) { return true // if the params are valid return false // will stop Nuxt.js to render the route and display the error page } } </script>
-
asyncData / fetch
再之后是兩種異步數(shù)據(jù)請求的鉤子。
asyncData
用于異步取數(shù),并初始化data
(增強(qiáng) vue 生命周期里的data
)。fetch
也是異步取數(shù),但不返回?cái)?shù)據(jù),一般用作初始化局部使用的 vuex 狀態(tài)數(shù)——可以與 nuxtServerInit 比較一下。<script> export default { asyncData() { return axios.get("/api/data"); }, async fetch ({ store, params }) { let { stars } = await axios.get('http://api/stars'); store.commit('setStars', stars); }, }; </script>
asyncData / fetch 觸發(fā)的時(shí)機(jī)大家需要注意一下,client 和 server 都有可能觸發(fā)。在頁面初次加載時(shí),由 server 觸發(fā)數(shù)據(jù)請求;后續(xù)若有路由變化,會是在 client 觸發(fā)。
nuxt 增強(qiáng)的生命鉤子主要就是上面幾種,分工很明確。不過由于 SSR 的特性,server 和 client 端都有可能觸發(fā)這些鉤子,所以 debug 的時(shí)候,還是很容易搞混的。我這里列一下最常見的鉤子觸發(fā)時(shí)機(jī),大家注意一下:
Hooks | Server(1st page) | Client(1st page) | Client(next pages) |
---|---|---|---|
nuxtServerInit | ? | ? | ? |
middleware | ? | ? | ? |
beforeCreate | ? | ? | ? |
asyncData/fetch | ? | ? | ? |
Created | ? | ? | ? |
mounted | ? | ? | ? |
小結(jié)
這期介紹了一款基于 vue 的 SSR 框架——nuxt。nuxt 框架對比 vue 庫的好處是:它提供了一套完整的解決方案,幫助團(tuán)隊(duì)統(tǒng)一命令、規(guī)范代碼、集成配套工具、封裝配置文件,以及提供生產(chǎn)和開發(fā)環(huán)境。這是對缺少文檔、缺乏經(jīng)驗(yàn),或是流動(dòng)性很大的團(tuán)隊(duì)來說,不可或缺的優(yōu)異功能。
不過,框架之于庫的劣勢也很鮮明:缺乏靈活性,缺少配套插件,封裝可能過深,項(xiàng)目侵入較大等等。我自己碰過最大的坑是,nuxt 對 serverless lambda 支持不友好;server 端的依賴不能打包部署,只能把所有 node_modules 一股腦扔到 lambda 上,結(jié)果超出了 lambda 上傳 size 限制。所幸,有個(gè)小姑娘花了兩禮拜時(shí)間,用 webpack 過濾掉了無用文件,從而大幅減少了 size。當(dāng)然,依賴問題可能是各種技術(shù)棧都會碰到的難題。我就見過有些部門專門組織多人團(tuán)隊(duì),花了一年時(shí)間才勉強(qiáng)解決依賴過大的問題,最后得到了領(lǐng)導(dǎo)層的高度贊揚(yáng)。
總之,框架和庫的選擇目前來說還是極度依個(gè)人經(jīng)驗(yàn),很難找出一套可以量化優(yōu)劣的評判機(jī)制;多寫代碼,積累經(jīng)驗(yàn),并在適當(dāng)時(shí)機(jī)轉(zhuǎn)型,可能是比較靠譜的應(yīng)對之道。(我也經(jīng)常在 nuxt 和 vue spa 之間搖擺,有時(shí)候還會被領(lǐng)導(dǎo)層噴“拍腦袋”,)
相關(guān)
文章同步發(fā)布于an-Onion 的 Github。碼字不易,歡迎點(diǎn)贊