自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊聊 Nuxt 開箱即用的特性

開發(fā) 前端
最近公司項(xiàng)目中使用了 Nuxt 框架,進(jìn)行首屏的服務(wù)端渲染,加快了內(nèi)容的到達(dá)時間 (time-to-content),于是筆者開始了對 Nuxt 的學(xué)習(xí)和使用。以下是從源碼角度對 Nuxt 的一些特性的介紹和分析。

 [[420713]]

引文

最近公司項(xiàng)目中使用了 Nuxt 框架,進(jìn)行首屏的服務(wù)端渲染,加快了內(nèi)容的到達(dá)時間 (time-to-content),于是筆者開始了對 Nuxt 的學(xué)習(xí)和使用。以下是從源碼角度對 Nuxt 的一些特性的介紹和分析。

FEATURES

服務(wù)端渲染(SSR)

Vue.js 是構(gòu)建客戶端應(yīng)用程序的框架。默認(rèn)情況下,可以在瀏覽器中輸出 Vue 組件,進(jìn)行生成 DOM 和操作 DOM。然而,也可以將同一個組件渲染為服務(wù)器端的 HTML 字符串,將它們直接發(fā)送到瀏覽器,最后將這些靜態(tài)標(biāo)記"激活"為客戶端上完全可交互的應(yīng)用程序。 ------Vue SSR 指南

官方Vue SSR指南的基本用法章節(jié),給出了 demo 級別的服務(wù)端渲染實(shí)現(xiàn),Nuxt 也是基于該章節(jié)實(shí)現(xiàn)的,大體流程幾乎一致。建議先食用官方指南,再看本文定大有裨益。

Nuxt 作為一個服務(wù)端渲染框架,了解其服務(wù)端渲染的實(shí)現(xiàn)原理必然是重中之重,就讓我們通過相關(guān)源碼,看看其具體實(shí)現(xiàn)吧!

我們通過 nuxt 啟動 Nuxt 項(xiàng)目,其首先會執(zhí)行 startDev 方法,然后調(diào)用_listenDev 方法,獲取 Nuxt 配置,調(diào)用getNuxt方法實(shí)例化 Nuxt。然后執(zhí)行 nuxt.ready() 方法,生成渲染器。

  1. // @nuxt/server/src/server.js 
  2. async ready () { 
  3.  // Initialize vue-renderer 
  4.  this.serverContext = new ServerContext(this) 
  5.  this.renderer = new VueRenderer(this.serverContext) 
  6.  await this.renderer.ready() 
  7.  
  8.  // Setup nuxt middleware 
  9.  await this.setupMiddleware() 
  10.  
  11.  return this 

在 ready 中會執(zhí)行 this.setupMiddleware() ,其中會調(diào)用nuxtMiddleware 中間件(這里是響應(yīng)的關(guān)鍵)。

  1. // @nuxt/server/src/middleware/nuxt.js 
  2. export default ({ options, nuxt, renderRoute, resources }) => async function nuxtMiddleware (req, res, next) { 
  3.  const context = getContext(req, res) 
  4.  try { 
  5.    const url = normalizeURL(req.url) 
  6.    res.statusCode = 200 
  7.    const result = await renderRoute(url, context) // 渲染相應(yīng)路由,后文會展開 
  8.     
  9.    const { 
  10.      html, 
  11.      redirected, 
  12.      preloadFiles 
  13.   } = result // 得到html 
  14.  
  15.    // 設(shè)置頭部字段 
  16.    res.setHeader('Content-Type''text/html; charset=utf-8'
  17.    res.setHeader('Accept-Ranges''none'
  18.    res.setHeader('Content-Length', Buffer.byteLength(html)) 
  19.    res.end(html, 'utf8') // 做出響應(yīng) 
  20.    return html 
  21. } catch (err) { 
  22.    if (context && context.redirected) { 
  23.      consola.error(err) 
  24.      return err 
  25.   } 
  26.    next(err) 

nuxtMiddleware 中間件中首先標(biāo)準(zhǔn)化請求的url,設(shè)置請求狀態(tài)碼,通過url匹配到相應(yīng)的路由,渲染出對應(yīng)的路由組件,設(shè)置頭部信息,最后做出響應(yīng)。

  1. renderSSR (renderContext) { 
  2.  // Call renderToString from the bundleRenderer and generate the HTML (will update the renderContext as well) 
  3.  // renderSSR 只是 universal app的渲染方法,Nuxt 也可以進(jìn)行開發(fā)普通的 SPA 項(xiàng)目 
  4.  const renderer = renderContext.modern ? this.renderer.modern : this.renderer.SSR 
  5.  return renderer.render(renderContext) 

其中 renderRoute 方法會調(diào)用 @nuxt/vue-render 的renderSSR 進(jìn)行服務(wù)端渲染操作。

  1. // @nuxt/vue-renderer/src/renderers/SSR.js 
  2. async render (renderContext) { 
  3.  // Call Vue renderer renderToString 
  4.  let APP = await this.vueRenderer.renderToString(renderContext) 
  5.  
  6.  let HEAD = '' 
  7.  // ... 此處省略n行HEAD拼接代碼,后續(xù) HEAD 管理部分會提及 
  8.     
  9.  // Render with SSR template 
  10.  const html = this.renderTemplate(this.serverContext.resources.SSRTemplate, templateParams) 
  11.  
  12.  return { 
  13.    html, 
  14.    preloadFiles 

而 renderSSR 又會調(diào)用 renderer.render 方法,將 url 匹配的路由渲染成字符串,將字符串與模版相結(jié)合,得到最終返回給瀏覽器的html,至此 Nuxt 服務(wù)端渲染完成。

最后貼一張盜來的 Nuxt 執(zhí)行流程圖,圖畫的很棒,流程也很清晰,感謝。

數(shù)據(jù)拉取(Data Fetching)

在客戶端程序(CSR)可以通過在 mounted 鉤子中獲取數(shù)據(jù),但在通用程序(Universal)中則需要使用特定的鉤子才能在服務(wù)端獲取數(shù)據(jù)。

Nuxt 中主要提供了兩種鉤子獲取數(shù)據(jù)

  • asyncData
    • 只可以在頁面級組件中獲取,不可以訪問 this
    • 通過返回對象保存數(shù)據(jù)狀態(tài)或與Vuex配合進(jìn)行狀態(tài)保存
  • fetch
    • 所有組件中都可以獲取,可以訪問 this
    • 無需傳入 context,傳入 context 會 fallback 到老版的 fetch,功能類似于 asyncData
  1. // .nuxt/server.js 
  2. // Components are already resolved by setContext -> getRouteData (app/utils.js) 
  3. const Components = getMatchedComponents(app.context.route) 
  4.   
  5. // 在匹配的路由中,調(diào)用 asyncData 和 legacy 版本的 fetch方法 
  6. const asyncDatas = await Promise.all(Components.map((Component) => { 
  7.  const promises = [] 
  8.  
  9.  // 調(diào)用 asyncData(context) 
  10.  if (Component.options.asyncData && typeof Component.options.asyncData === 'function') { 
  11.    const promise = promisify(Component.options.asyncData, app.context) 
  12.    promise.then((asyncDataResult) => { 
  13.      SSRContext.asyncData[Component.cid] = asyncDataResult 
  14.      applyAsyncData(Component) 
  15.      return asyncDataResult 
  16.   }) 
  17.    promises.push(promise) 
  18.   } else { 
  19.      promises.push(null
  20.   } 
  21.  
  22.    // 調(diào)用 legacy 版本的fetch(context) 兼容老版本的 fetch 
  23.    if (Component.options.fetch && Component.options.fetch.length) { 
  24.      promises.push(Component.options.fetch(app.context)) 
  25.   } else { 
  26.    promises.push(null
  27.  
  28.  return Promise.all(promises) 
  29. })) 

在生成的 .nuxt/server.js 中,會遍歷匹配的組件,查看組件中是否定義了 asyncData 選項(xiàng)以及 legacy 版 fetch ,存在就依次調(diào)用,獲得 asyncDatas。

  1. // .nuxt/mixins/fetch.server.js  
  2. // nuxt v2.14及之后 
  3. async function serverPrefetch() { 
  4.  // Call and await on $fetch 
  5.  // v2.14 之后推薦的 fetch 
  6.  try { 
  7.    await this.$options.fetch.call(this) 
  8. } catch (err) { 
  9.    if (process.dev) { 
  10.      console.error('Error in fetch():', err) 
  11.   }  
  12.  this.$fetchState.pending = false // 設(shè)置fetchState 為 false 

在服務(wù)端實(shí)例化 vue 實(shí)例之后,執(zhí)行 serverPrefetch,觸發(fā) fetch 選項(xiàng)方法,獲取數(shù)據(jù),數(shù)據(jù)會作用于生成 html的過程。

HEAD 管理(Meta Tags and SEO)

截至目前,Google 和 Bing 可以很好對同步 JavaScript 應(yīng)用程序進(jìn)行索引。但是對于異步獲取數(shù)據(jù)的網(wǎng)站來說,主流的搜索引擎暫時還無法支持,于是造成網(wǎng)站搜索排名靠后,于是希望獲得更好的SEO成為眾多網(wǎng)站考慮使用SSR框架的原因。

為了獲得良好的SEO,那么就需要對HEAD進(jìn)行精細(xì)化的配置和管理。讓我們看看其是如何實(shí)現(xiàn)的吧~

Nuxt框架借助 vue-meta 庫實(shí)現(xiàn)全局、單個頁面的 meta 標(biāo)簽的自定義。Nuxt 內(nèi)部的實(shí)現(xiàn)也幾乎遵循 vue-meta 官方的 SSR meta 管理的流程。具體詳情請查看。

  1. // @nuxt/vue-app/template/index.js 
  2. // step1 
  3. Vue.use(Meta, JSON.stringify(vueMetaOptions)) 
  4.  
  5. // @nuxt/vue-app/template/template.js 
  6. // step2 
  7. export default async (SSRContext) => { 
  8.  const _app = new Vue(app) 
  9.  // Add meta infos (used in renderer.js) 
  10.  SSRContext.meta = _app.$meta() 
  11.  return _app 

首先通過Vue插件的形式,注冊vue-meta,內(nèi)部會在Vue的原型上掛載$meta屬性。然后將meta添加到服務(wù)端渲染上下文中。

  1. async render (renderContext) { 
  2.    // Call Vue renderer renderToString 
  3.    let APP = await this.vueRenderer.renderToString(renderContext) 
  4.    // step3 
  5.    let HEAD = '' 
  6.  
  7.    // Inject head meta 
  8.    // (this is unset when features.meta is false in server template) 
  9.    // 以下就是上文省略的 n 行 HEAD 拼接代碼,可以適當(dāng)忽略 
  10.    // 了解主要過程即可,具體細(xì)節(jié)按需查看 
  11.    const meta = renderContext.meta && renderContext.meta.inject({ 
  12.      isSSR: renderContext.nuxt.serverRendered, 
  13.      ln: this.options.dev 
  14.   }) 
  15.  
  16.    if (meta) { 
  17.      HEAD += meta.title.text() + meta.meta.text() 
  18.   } 
  19.  
  20.    if (meta) { 
  21.      HEAD += meta.link.text() + 
  22.        meta.style.text() + 
  23.        meta.script.text() + 
  24.        meta.noscript.text() 
  25.   } 
  26.  
  27.    // Check if we need to inject scripts and state 
  28.    const shouldInjectScripts = this.options.render.injectScripts !== false 
  29.  
  30.    // Inject resource hints 
  31.    if (this.options.render.resourceHints && shouldInjectScripts) { 
  32.      HEAD += this.renderResourceHints(renderContext) 
  33.   } 
  34.  
  35.    // Inject styles 
  36.    HEAD += this.renderStyles(renderContext) 
  37.  
  38.  
  39.    // Prepend scripts 
  40.    if (shouldInjectScripts) { 
  41.      APP += this.renderScripts(renderContext) 
  42.   } 
  43.  
  44.    if (meta) { 
  45.      const appendInjectorOptions = { body: true } 
  46.      // Append body scripts 
  47.      APP += meta.meta.text(appendInjectorOptions) 
  48.      APP += meta.link.text(appendInjectorOptions) 
  49.      APP += meta.style.text(appendInjectorOptions) 
  50.      APP += meta.script.text(appendInjectorOptions) 
  51.      APP += meta.noscript.text(appendInjectorOptions) 
  52.   } 
  53.  
  54.    // Template params 
  55.    const templateParams = { 
  56.      HTML_ATTRS: meta ? meta.htmlAttrs.text(renderContext.nuxt.serverRendered /* addSrrAttribute */) : ''
  57.      HEAD_ATTRS: meta ? meta.headAttrs.text() : ''
  58.      BODY_ATTRS: meta ? meta.bodyAttrs.text() : ''
  59.      HEAD, 
  60.      APP, 
  61.      ENV: this.options.env 
  62.   } 
  63.  
  64.    // Render with SSR template 
  65.    // 通過模版和參數(shù) 生成html 
  66.    const html = this.renderTemplate(this.serverContext.resources.SSRTemplate, templateParams) 
  67.  
  68.    let preloadFiles 
  69.    if (this.options.render.http2.push) { 
  70.      // 獲取需要預(yù)加載的文件 
  71.      preloadFiles = this.getPreloadFiles(renderContext) 
  72.   } 
  73.  
  74.    return { 
  75.      html, 
  76.      preloadFiles, 
  77.   } 

最后在響應(yīng)的 html 中注入 metadata 即可。

文件系統(tǒng)路由(File System Routing)

想必使用過 Nuxt 的同學(xué)應(yīng)該都對其基于文件生成路由的特性,印象深刻。讓我從源碼角度看看 Nuxt 是如何實(shí)現(xiàn)基于 pages 目錄(可配置),自動生成路由的。

首先在啟動 Nuxt 項(xiàng)目或者修改文件時,會自動調(diào) generateRoutesAndFiles 方法,生成路由 以及 .nuxt 目錄下的文件。

  1. // @nuxt/builder/src/builder.js 
  2. async generateRoutesAndFiles() { 
  3.   ... 
  4.   await Promise.all([ 
  5.     this.resolveLayouts(templateContext), 
  6.     this.resolveRoutes(templateContext), //解析生成路由,需要關(guān)注的重點(diǎn) 
  7.     this.resolveStore(templateContext), 
  8.     this.resolveMiddleware(templateContext) 
  9.   ]) 
  10.   ... 

解析路由會存在三種情況:一是修改了默認(rèn)的 pages 目錄名稱,且未在 nuxt.config.js 中配置相關(guān)目錄,二是使用 nuxt 默認(rèn)的 pages 目錄,三是使用調(diào)用用戶自定義的路由生成方法生成路由。

  1. // @nuxt/builder/src/builder.js 
  2. async resolveRoutes({ templateVars }) { 
  3.   consola.debug('Generating routes...'
  4.   if (this._defaultPage) { 
  5.     // 在srcDir下未找到pages目錄 
  6.   } else if (this._nuxtPage) { 
  7.     // 使用nuxt動態(tài)生成路由 
  8.   } else { 
  9.     // 用戶提供了自定義方法去生成路由,提供用戶自定義路由的能力 
  10.   } 
  11.   // router.extendRoutes method 
  12.   if (typeof this.options.router.extendRoutes === 'function') { 
  13.     const extendedRoutes = await this.options.router.extendRoutes( 
  14.       templateVars.router.routes, 
  15.       resolve 
  16.     ) 
  17.     if (extendedRoutes !== undefined) { 
  18.       templateVars.router.routes = extendedRoutes 
  19.     } 
  20.   } 

除此之外,還可以提供相應(yīng)的 extendRoutes 方法,在 nuxt 生成路由的基礎(chǔ)上添加自定義路由。

  1. export default { 
  2.   router: { 
  3.     extendRoutes(routes, resolve) { 
  4.       // 例如添加 404 頁面 
  5.       routes.push({ 
  6.         name'custom'
  7.         path: '*'
  8.         component: resolve(__dirname, 'pages/404.vue'
  9.       }) 
  10.     } 
  11.   } 

其中當(dāng)修改了默認(rèn)的 pages 目錄,導(dǎo)致找不到相關(guān)的目錄,會使用 @nuxt/vue-app/template/pages/index.vue 文件生成路由。

  1. async resolveRoutes({ templateVars }) { 
  2.   if (this._defaultPage) { 
  3.     // createRoutes 方法根據(jù)傳參,生成路由。具體算法,不再展開 
  4.     templateVars.router.routes = createRoutes({ 
  5.       files: ['index.vue'], 
  6.       srcDir: this.template.dir + '/pages', // 指向@nuxt/vue-app/template/pages/index.vue 
  7.       routeNameSplitter, // 路由名稱分隔符,默認(rèn)`-` 
  8.       trailingSlash // 尾斜杠 / 
  9.     }) 
  10.   } else if (this._nuxtPage) { 
  11.     const files = {} 
  12.     const ext = new RegExp(`\\.(${this.supportedExtensions.join('|')})$`) 
  13.     for (const page of await this.resolveFiles(this.options.dir.pages)) { 
  14.       const key = page.replace(ext, ''
  15.       // .vue file takes precedence over other extensions 
  16.       if (/\.vue$/.test(page) || !files[key]) { 
  17.         files[key] = page.replace(/(['"])/g, '\\$1') 
  18.       } 
  19.     } 
  20.     templateVars.router.routes = createRoutes({ 
  21.       files: Object.values(files), 
  22.       srcDir: this.options.srcDir, 
  23.       pagesDir: this.options.dir.pages, 
  24.       routeNameSplitter, 
  25.       supportedExtensions: this.supportedExtensions, 
  26.       trailingSlash 
  27.     }) 
  28.     } else { 
  29.       templateVars.router.routes = await this.options.build.createRoutes(this.options.srcDir) 
  30.     } 
  31.     // router.extendRoutes method 
  32.     if (typeof this.options.router.extendRoutes === 'function') { 
  33.       const extendedRoutes = await this.options.router.extendRoutes( 
  34.         templateVars.router.routes, 
  35.         resolve 
  36.       ) 
  37.       if (extendedRoutes !== undefined) { 
  38.         templateVars.router.routes = extendedRoutes 
  39.       } 
  40.   } 

然后就是調(diào)用 createRoutes 方法,生成路由。生成的路由大致長這樣,和手動書寫的路由文件幾乎一致(后續(xù)還會進(jìn)行打包??,懶加載引入路由組件)。

  1.   { 
  2.     name'index'
  3.     path: '/'
  4.     chunkName: 'pages/index'
  5.     component: 'Users/username/projectName/pages/index.vue' 
  6.   }, 
  7.   { 
  8.     name'about'
  9.     path: '/about'
  10.     chunkName: 'pages/about/index'
  11.     component: 'Users/username/projectName/pages/about/index.vue' 
  12.   } 

智能預(yù)取(Smart Prefetching)

從 Nuxt v2.4.0 開始,當(dāng) 出現(xiàn)在可視區(qū)域后,Nuxt將會預(yù)取經(jīng)過code-splitted的 page 頁面的腳本,使得在用戶點(diǎn)擊之前,該路由指向的地址,就處于 ready 狀態(tài),這將極大的提升用戶的體驗(yàn)。

相關(guān)實(shí)現(xiàn)邏輯集中于 .nuxt/components/nuxt-link.client.js 中。

首先 Smart Prefetching 特性的實(shí)現(xiàn)依賴于window.IntersectionObserver 這個實(shí)驗(yàn)性的 API,如果瀏覽器不支持該 API,就不會進(jìn)行組件預(yù)取操作。

  1. mounted () { 
  2.   if (this.prefetch && !this.noPrefetch) { 
  3.     this.handleId = requestIdleCallback(this.observe, { timeout: 2e3 }) 
  4.   } 

然后在需要預(yù)取的 組件掛載階段,會調(diào)用 requestIdleCallback 方法在瀏覽器的空閑時段內(nèi)調(diào)用 observe 方法。

  1. const observer = window.IntersectionObserver && new window.IntersectionObserver((entries) => { 
  2.   entries.forEach(({ intersectionRatio, target: link }) => { 
  3.     // 如果intersectionRatio 小于等于0,表示目標(biāo)不在viewport內(nèi) 
  4.     if (intersectionRatio <= 0 || !link.__prefetch) { 
  5.       return 
  6.     } 
  7.     // 進(jìn)行預(yù)取數(shù)據(jù)(其實(shí)就是加載組件) 
  8.     link.__prefetch() 
  9.   }) 
  10. }) 

當(dāng)被監(jiān)聽的元素的可視情況發(fā)生改變的時候(且出現(xiàn)在視圖內(nèi)時),會觸發(fā) new window.IntersectionObserver(callback) 的回調(diào),執(zhí)行真正的預(yù)取操作prefetchLink。

  1. prefetchLink () { 
  2.   // 判斷網(wǎng)絡(luò)環(huán)境,離線或者2G環(huán)境下,不進(jìn)行預(yù)取操作 
  3.   if (!this.canPrefetch()) { 
  4.     return 
  5.   } 
  6.   // 停止監(jiān)聽該元素,提高性能 
  7.   observer.unobserve(this.$el) 
  8.   const Components = this.getPrefetchComponents() 
  9.  
  10.   for (const Component of Components) { 
  11.     // 及時加載組件,使得用戶點(diǎn)擊時,該組件是一個就緒的狀態(tài) 
  12.     const componentOrPromise = Component() 
  13.     if (componentOrPromise instanceof Promise) { 
  14.       componentOrPromise.catch(() => {}) 
  15.     Component.__prefetched = true // 已經(jīng)預(yù)取的標(biāo)志位 
  16.   } 

總結(jié)

上文從源碼角度介紹了 Nuxt 服務(wù)端渲染的實(shí)現(xiàn)、服務(wù)端數(shù)據(jù)的獲取以及 Nuxt 開箱即用的幾個特性:HEAD 管理、基于文件系統(tǒng)的路由和智能預(yù)取 code-splitted 的路由。如果希望對 SSR 進(jìn)行更深入研究,還可以橫向?qū)W習(xí) React 的 SSR 實(shí)現(xiàn) Next 框架。

希望對您有所幫助,如有紕漏,望請輔正。

參考

為什么使用服務(wù)器端渲染 (SSR)?

Nuxt源碼精讀

Vue Meta

Introducing Smart prefetching

服務(wù)端渲染

 

責(zé)任編輯:武曉燕 來源: 大轉(zhuǎn)轉(zhuǎn)FE
相關(guān)推薦

2024-11-25 06:20:00

Netty封裝框架

2023-11-04 12:43:44

前端圖片參數(shù)

2022-08-08 08:29:55

圖片壓縮前端互聯(lián)網(wǎng)

2023-03-08 21:25:58

開源工具庫開箱

2023-01-15 20:28:32

前端圖片壓縮

2021-09-26 05:41:47

基礎(chǔ)設(shè)施連接無線技術(shù)網(wǎng)絡(luò)

2021-09-01 17:43:32

StreamNativ開源

2021-09-28 09:30:18

uni-appVue 3.0uniCloud

2022-08-02 09:01:55

后臺管理模版

2023-01-29 07:49:57

2013-11-01 09:37:19

Android系統(tǒng)架構(gòu)工具

2022-01-03 18:15:35

FlaskTepHttpRunner

2025-04-14 11:00:00

2015-06-30 09:49:19

管理平臺開源KVM

2019-11-25 00:00:00

開源技術(shù) 數(shù)據(jù)

2022-01-11 09:32:20

鴻蒙HarmonyOS應(yīng)用

2025-01-23 20:42:44

2023-10-31 08:03:33

開源電子簽名組件

2021-04-22 10:28:52

開發(fā)技能代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號