欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用vite搭建ssr活動(dòng)頁架構(gòu)的實(shí)現(xiàn)

 更新時(shí)間:2022年07月05日 09:04:02   作者:總有一天成為海賊王的  
本文主要介紹了使用vite搭建ssr活動(dòng)頁架構(gòu),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

最近接了個(gè)需求,重構(gòu)公司的活動(dòng)頁項(xiàng)目。要實(shí)現(xiàn):

  • SEO良好
  • MPA
  • 啟動(dòng)速度快,構(gòu)建速度快
  • 前端工程化
  • 瀏覽器兼容至少IE11

基于這些需求,我選擇了 vite + react + vite-plugin-ssr

文章前面是ssr入門,老手請(qǐng)隨意跳過,看最后即可

入門SSR

什么是SSR

術(shù)語

  • ssr,全名 server side render,服務(wù)端渲染
  • csr,全名 client side render,客戶端渲染
  • spa,全名 single page application,單頁面應(yīng)用
  • mpa,全名 multi page application,多頁面應(yīng)用

ssr的歷史

我的學(xué)習(xí)習(xí)慣是,不論學(xué)什么,先去了解它的歷史背景。存在即合理,了解到為什么產(chǎn)生一個(gè)技術(shù),能讓我更容易去理解這門技術(shù)

最初的網(wǎng)頁渲染,前端三劍客:html + css + js,放在服務(wù)器上,靜態(tài)部署就可以供用戶訪問了。

后來隨著網(wǎng)頁復(fù)雜度上升,出現(xiàn)了jsp/ejs等等一系列模板語法,在服務(wù)端獲取到數(shù)據(jù)后,把數(shù)據(jù)渲染到模板中,最后生成html返回給客戶端,這是最原始的ssr。

隨著前端框架的誕生(ng/react/vue),越來越多同學(xué)開始使用框架開發(fā)web,這些前端框架的出現(xiàn)使得前后端開發(fā)解耦(csr的情況下),前端同學(xué)可以更充分的利用前端工程化等等新技術(shù)來健壯前端項(xiàng)目。而這種完全解耦的方式也帶來了一些問題,比如非常不友好的SEO

csr的缺點(diǎn)

讓我們打開一個(gè)SPA網(wǎng)頁(使用腳手架默認(rèn)方式搭建),右鍵查看網(wǎng)頁源代碼

第一個(gè)問題:SEO極度不友好。 網(wǎng)頁里面根本沒有內(nèi)容。爬蟲最喜歡這種網(wǎng)頁了,看一眼就走。

SPA的工作方式就是使用js來動(dòng)態(tài)渲染html,壓力全部給到了客戶端(瀏覽器)這邊,正是因?yàn)檫@個(gè),第二個(gè)問題也出現(xiàn)了:首屏的加載速度較慢

為什么ssr的需求再次出現(xiàn)

為了更好的SEO,為了更快的加載速度(服務(wù)端生成了首頁靜態(tài)頁面,客戶端可以直接展示,隨后再用JS動(dòng)態(tài)渲染)

前端開發(fā)使用react/vue,可以熟練開發(fā)網(wǎng)頁。而cra/vue-cli腳手架創(chuàng)建出來的模板默認(rèn)是SPA。

那么應(yīng)該如何實(shí)現(xiàn) “既要,還要”呢(前端框架/seo我全都要)

如何實(shí)現(xiàn)基礎(chǔ)ssr

基于上面的問題,我們希望實(shí)現(xiàn):

  • 查看網(wǎng)頁源代碼時(shí),展示網(wǎng)頁的內(nèi)容

既然需要服務(wù)端渲染,服務(wù)端用來執(zhí)行vue/react這種js框架,那第一反應(yīng)就是用nodejs來做服務(wù)端渲染,因?yàn)閚odejs天然執(zhí)行js代碼

客戶端的話,用vue來做(react也行,只不過最近在熟悉vue3),vue3的話,體積比react更小,toC網(wǎng)站更好一些。react18針對(duì)ssr出了新api,開發(fā)者可以使用 React.lazysuspense 實(shí)現(xiàn)懶加載,也提供了很好的用戶體驗(yàn):https://github.com/reactwg/react-18/discussions/37

下面是基礎(chǔ)的ssr例子

以下例子 請(qǐng)注意:客戶端使用的是esm規(guī)范,服務(wù)端使用的是cjs

如果希望統(tǒng)一使用esm,可以使用 tsx 執(zhí)行node腳本 或修改package.json => type: "module"

創(chuàng)建服務(wù)端

const express = require('express')

const app = express()

app.get('*', (req, res) => {
  res.send('Hello World')
})

app.listen(4000, () => {
  console.log('Server running at http://localhost:4000');
})

啟動(dòng)服務(wù)后,打開瀏覽器 http:localhost:4000,即可看到內(nèi)容

渲染vue

服務(wù)端有了,但是是返回的string,我們想用vue來開發(fā),嘗試返回一個(gè)vue組件

vue3提供了服務(wù)端渲染組件的方法,在 vue/server-renderer

const express = require('express')
const { renderToString } = require('vue/server-renderer')
const { createSSRApp } = require('vue')

const app = express()

app.get('*', (req, res) => {
  const vue = createSSRApp({
    data: () => ({ count: 1 }),
    template: `<button @click="count++">{{ count }}</button>`,
  })

  renderToString(vue).then((html) => {
    res.send(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <div id="app">${html}</div>
    </body>
    </html>
    `)
  })
})

app.listen(4000, () => {
  console.log('Server running at http://localhost:4000')
})

此時(shí)打開頁面,可以看到button了,但是此時(shí)頁面是靜態(tài)的,因?yàn)檫@個(gè)頁面在服務(wù)端已經(jīng)渲染好了,但在客戶端沒有注入vue

右鍵查看網(wǎng)頁源代碼,可以看到button元素

客戶端渲染

我們希望button的交互可以動(dòng)起來,此時(shí)需要客戶端來做渲染了

const { createSSRApp } = require('vue')

const vue = createSSRApp({
  data: () => ({ count: 1 }),
  template: `<button @click="count++">{{ count }}</button>`,
})

vue.mount('#app')

這段代碼是否很眼熟,其實(shí)基本上跟服務(wù)端渲染返回的內(nèi)容是一樣的。所以ssr的本質(zhì)是服務(wù)端渲染靜態(tài)html+客戶端渲染js

此外,為了在瀏覽器中加載客戶端文件,我們還需要:

  • server.js中添加 server.use(express.static('.')) 來托管客戶端文件。這里要注意js執(zhí)行順序
  • <script type="module" src="/client.js"></script>添加到 HTML 外殼以加載客戶端入口文件
  • 通過在 HTML 外殼中添加 Import Map 以支持在瀏覽器中使用 import * from 'vue'
const express = require('express')
const { renderToString } = require('vue/server-renderer')
const { createSSRApp } = require('vue')

const app = express()

app.get('/', (req, res) => {
  const vue = createSSRApp({
    data: () => ({ count: 1 }),
    template: `<button @click="count++">{{ count }}</button>`,
  })

  renderToString(vue).then((html) => {
    res.send(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <script type="importmap">
      {
        "imports": {
          "vue": "https://unpkg.com/vue@3/dist/vue.esm-browser.js"
        }
      }
      </script>
      <script src="/client.js" type="module"></script>
    </head>
    <body>
      <div id="app">${html}</div>
    </body>
    </html>
    `)
  })
})

app.use(express.static('.'))

app.listen(4000, () => {
  console.log('Server running at http://localhost:4000')
})

此時(shí)打開本地地址,可以看到點(diǎn)擊button數(shù)字變化了

以上是最簡單的ssr,在vue官網(wǎng)上可以找到這個(gè)例子。

我們甚至沒有去考慮前端的路由,狀態(tài)管理 等等。一個(gè)完整的ssr還需要一系列構(gòu)建。

網(wǎng)頁路由

ssr的網(wǎng)頁路由有兩種方式

  • 服務(wù)端路由
  • 客戶端路由

服務(wù)端路由

服務(wù)端路由,就是利用 web框架的路由能力,匹配到某個(gè)路由時(shí),返回對(duì)應(yīng)的html代碼,并且加載相應(yīng)的客戶端代碼,比如:

import express from 'express'

const router = express.Router()

router.get('/some-page', (req, res) => {
  // 返回 some-page 的html
  res.send(`<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <script src="/client.js" type="module"></script>
    </head>
    <body>
      <div id="app">要渲染的html字符串</div>
    </body>
    </html>`)
})

服務(wù)端路由跳轉(zhuǎn)直接使用 a標(biāo)簽即可

客戶端路由

客戶端路由的話,就要用到前端框架對(duì)應(yīng)的路由庫,vue-router / react-router 等

可以參照官方例子

比較兩種方式

服務(wù)端路由適合做頁面零碎的項(xiàng)目,如活動(dòng)頁,每次跳轉(zhuǎn)路由會(huì)刷新整個(gè)頁面

客戶端路由適合做頁面之間交互強(qiáng)的項(xiàng)目,如產(chǎn)品頁,跳轉(zhuǎn)路由不會(huì)刷新頁面

使用vite做ssr

vue官方推薦了幾個(gè)做ssr的例子,包括 Nuxt/ Quasar這種重框架,也有 vite的輕框架。為了細(xì)粒度把控項(xiàng)目,我使用了 vite+ vite-plugin-ssr的方案來做

vite-plugin-ssr

Like Next.js / Nuxt but as do-one-thing-do-it-well Vite plugin.

類似 Next/Nuxt 但是只做一件事并把它做好 的vite插件

這個(gè)插件的文檔寫得非常詳細(xì),而且github上有許多例子。

插件的具體功能我不贅述,各位可看官方文檔,我在這里講一下這個(gè)插件(v0.3x)的約定式路由的工作原理。以下 vite-plugin-ssr 簡稱為 vps

vps的約定式路由

vps推薦使用文件夾名稱作為路由,這種方式也是最方便的?;顒?dòng)頁不存在頁面之間的交互,所以我選擇的默認(rèn)方式。

vps規(guī)定了一系列文件命名,作為開發(fā)/構(gòu)建遍歷的條件。以下4種命名會(huì)被vps收集,每種文件有其獨(dú)特的作用。我們不要隨意以 page.*** 來命名文件

// Vite resolves globs with micromatch: https://github.com/micromatch/micromatch
// Pattern `*([a-zA-Z0-9])` is an Extglob: https://github.com/micromatch/micromatch#extglobs
export const pageFiles = {
  //@ts-ignore
  '.page': import.meta.glob('/**/*.page.*([a-zA-Z0-9])'),
  //@ts-ignore
  '.page.client': import.meta.glob('/**/*.page.client.*([a-zA-Z0-9])'),
  //@ts-ignore
  '.page.server': import.meta.glob('/**/*.page.server.*([a-zA-Z0-9])'),
  //@ts-ignore
  '.page.route': import.meta.glob('/**/*.page.route.*([a-zA-Z0-9])'),
}

dev階段

  • node啟動(dòng)服務(wù)端server,調(diào)用 vps 的createPageRenderer,返回了 renderPage方法,我們調(diào)用 renderPage 即可獲取到服務(wù)端渲染后的內(nèi)容。源碼地址
  • vps在vite的dev階段,設(shè)置了 optimizeDeps做依賴預(yù)構(gòu)建的優(yōu)化。(咱們也可以參考這塊源碼對(duì)vite項(xiàng)目進(jìn)行一些優(yōu)化)。 源碼地址

build階段

  • 針對(duì) client / server 分別打包。如果使用約定式路由,會(huì)根據(jù)上文講到的遍歷條件,遍歷所有文件后,把所有的 .page文件設(shè)置為 input 的每一項(xiàng)(MPA)。源碼地址
  • 生成vps的manifest文件,其命名為 vite-plugin-ssr.json,里面會(huì)存放一些vps的基本信息。源碼地址
  • 生成單個(gè)的server bundled代碼,供部署使用,名為 importBuild.js源碼地址
  • 生成 package.json。 如果我們指定打包為es,則package.json中的type = module,否則為 commonjs。源碼地址
  • page.server的代碼轉(zhuǎn)為固定的一個(gè)導(dǎo)出語句,用來判斷 page.server是否有導(dǎo)出。源碼地址
  • 移除vite的內(nèi)置鉤子 vite:ssr-require-hook(我們?nèi)绻肽Ц牟寮^子,可以參考這種方法)源碼地址

項(xiàng)目大了之后,打包速度慢該怎么辦?

做活動(dòng)頁,每個(gè)頁面之間是沒有關(guān)聯(lián)的,其實(shí)我希望打包是增量式的打包,但是如果公共文件改變了,也無法避免全量打包。所以如果能做到緩存打包文件,就可以提升打包速度。

理想美好,現(xiàn)實(shí)往往相反。rollup2并不支持content hash,但是好消息是rollup3支持了并且會(huì)在最近發(fā)布

目前我們只能用hack的方式去實(shí)現(xiàn)content hash,比如使用node的 crypto模塊來做md5hash

import { createHash } from 'crypto'
import type { PreRenderedChunk } from 'rollup'

export function getContentHash(chunk: string | Uint8Array) {
  return createHash('md5').update(chunk).digest('hex').substring(0, 6)
}

export function getHash(chunkInfo: PreRenderedChunk) {
  return getContentHash(
    Object.values(chunkInfo.modules)
      .map((m) => m.code)
      .join(),
  )
}

然后在rollup的output中設(shè)置文件的命名

rollupOptions: {
  treeshake: 'smallest',
  output: {
    format: 'es',
    assetFileNames: (assetInfo) => {
      let extType = path.extname(assetInfo.name || '').split('.')[1]
      if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType!)) {
          extType = 'img'
          }
      const hash = getContentHash(assetInfo.source)
      return `assets/${extType}/[name].${hash}.[ext]`
    },
    
    chunkFileNames: (chunkInfo) => {
      const server = chunkInfo.name.endsWith('server') ? 'server-' : ''
      const name = chunkInfo.facadeModuleId?.match(/src/pages/(.*?)//)?.[1] || chunkInfo.name
      
      if (chunkInfo.isDynamicEntry || chunkInfo.name === 'vendor') {
        const hash = getHash(chunkInfo)
        return `assets/js/${name}-${server}${hash}.chunk.js`
      } else {
        return `assets/js/${name}-${server}[hash].chunk.js`
      }
    },
    entryFileNames: (chunkInfo) => {
      if (chunkInfo.name === 'pageFiles') {
        return '[name].js'
      }
      const hash = getHash(chunkInfo)
      return `assets/js/entry-${hash}.js`
    },
  },
},

做了content-hash后,打包速度會(huì)有非常大的提升,因?yàn)閞ollup其實(shí)有個(gè)cache機(jī)制,針對(duì)cache的文件不會(huì)transform,而正好transform是非常耗時(shí)的一步。

我嘗試了打包1000個(gè)文件,耗時(shí)40+s,在我的接受范圍內(nèi)

快速創(chuàng)建頁面模板

活動(dòng)頁面會(huì)有比較多相似的地方,所以直接根據(jù)模板來創(chuàng)建頁面代碼,開發(fā)效率又高一點(diǎn)(又可以摸魚了)。代碼地址

做得不好的地方

記錄兩個(gè)ssr探索過程中,我想實(shí)現(xiàn),但最后沒有實(shí)現(xiàn)的

  • 按需打包。因?yàn)樽龌顒?dòng)頁,按理說架構(gòu)應(yīng)該是按需打包,做完一個(gè)頁面打包一個(gè)頁面。嘗試了用monorepo,這樣打包的話,那么就要啟動(dòng)多個(gè)服務(wù)來監(jiān)聽。不用monorepo的話,就需要在rollup打包的過程中,設(shè)置outdir,然后打包在指定目錄中。同理,也需要啟動(dòng)多個(gè)服務(wù)。要做到只啟動(dòng)一個(gè)服務(wù),就得每次打包服務(wù)端都全量打包,客戶端按需打包,那么服務(wù)端和客戶端之間相互引用的文件路徑就很難去控制了。之所以想做按需打包,其實(shí)就是擔(dān)心以后項(xiàng)目大了打包慢。如果rollup的打包性能可以跟上的話,在接受范圍內(nèi)的話,其實(shí)是不需要做按需打包的
  • 按需啟動(dòng)。啟動(dòng)指定路由文件,而不去遍歷整個(gè)項(xiàng)目。這個(gè)得等vps0.4了

部署

部署的話,打算使用docker來做,下篇文章再講

源碼地址

react + ssr

vue3 + ssr

到此這篇關(guān)于使用vite搭建ssr活動(dòng)頁架構(gòu)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)vite搭建ssr活動(dòng)頁內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解Vuex下Store的模塊化拆分實(shí)踐

    詳解Vuex下Store的模塊化拆分實(shí)踐

    這篇文章主要介紹了詳解Vuex下Store的模塊化拆分實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-07-07
  • vue與TypeScript集成配置最簡教程(推薦)

    vue與TypeScript集成配置最簡教程(推薦)

    本篇文章主要介紹了vue與TypeScript集成配置最簡教程(推薦),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10
  • Vue3 directive自定義指令內(nèi)部實(shí)現(xiàn)示例

    Vue3 directive自定義指令內(nèi)部實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了Vue3 directive自定義指令內(nèi)部實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • 在vue項(xiàng)目創(chuàng)建的后初始化首次使用stylus安裝方法分享

    在vue項(xiàng)目創(chuàng)建的后初始化首次使用stylus安裝方法分享

    下面小編就為大家分享一篇在vue項(xiàng)目創(chuàng)建的后初始化首次使用stylus安裝方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-01-01
  • 使用watch監(jiān)聽路由變化和watch監(jiān)聽對(duì)象的實(shí)例

    使用watch監(jiān)聽路由變化和watch監(jiān)聽對(duì)象的實(shí)例

    下面小編就為大家分享一篇使用watch監(jiān)聽路由變化和watch監(jiān)聽對(duì)象的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-02-02
  • 淺談一下Vue技術(shù)棧之生命周期

    淺談一下Vue技術(shù)棧之生命周期

    這篇文章主要介紹了淺談一下Vue技術(shù)棧之生命周期,每一個(gè)vue實(shí)例從創(chuàng)建到銷毀的過程,就是這個(gè)vue實(shí)例的生命周期,這些過程中會(huì)伴隨著一些函數(shù)的自調(diào)用,需要的朋友可以參考下
    2023-05-05
  • Vue中的change事件無效問題及解決

    Vue中的change事件無效問題及解決

    這篇文章主要介紹了Vue中的change事件無效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • vue3中的reactive函數(shù)聲明數(shù)組方式

    vue3中的reactive函數(shù)聲明數(shù)組方式

    這篇文章主要介紹了vue3中的reactive函數(shù)聲明數(shù)組方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • Ant?Design?of?Vue?select框獲取key和name的問題

    Ant?Design?of?Vue?select框獲取key和name的問題

    這篇文章主要介紹了Ant?Design?of?Vue?select框獲取key和name的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • 一篇文章告訴你Vue3指令是如何實(shí)現(xiàn)的

    一篇文章告訴你Vue3指令是如何實(shí)現(xiàn)的

    在計(jì)算機(jī)技術(shù)中,指令是由指令集架構(gòu)定義的單個(gè)的CPU操作,在更廣泛的意義上,“指令”可以是任何可執(zhí)行程序的元素的表述,例如字節(jié)碼,下面這篇文章主要給大家介紹了關(guān)于Vue3指令是如何實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2022-01-01

最新評(píng)論