[See How]簡(jiǎn)單聊聊前端渲染模式以及Nuxt3.js

原文鏈接:因卓誒-簡(jiǎn)單聊聊前端渲染模式以及Nuxt3.js

前言

最近的工作有涉及到ssr琉朽,所以這篇文章算是一個(gè)總結(jié)博其,并且對(duì)還在beta階段的nuxt3做一個(gè)淺析枫耳。前段時(shí)間有一個(gè)蠻火的視頻原朝,關(guān)于rollup作者rich的一段演講桥胞,在演講里面rich梳理了ssr和csr瞻坝,并且講述了痛點(diǎn)句各,和提出新的概念“transition app”航厚,如果你有興趣可以看看這個(gè)視頻

在文章開始前,我來(lái)簡(jiǎn)單介紹一下"spa", "mpa", "ssr", "csr"......這些個(gè)名詞的意義气忠。如果你是做web前端開發(fā)的,這幾個(gè)詞可能伴隨著你的工作生涯很久很久了赋咽,相關(guān)文章互聯(lián)網(wǎng)上多如牛毛旧噪,如果你對(duì)這些概念比較模糊甚至壓根不知道,那么別關(guān)閉網(wǎng)頁(yè)脓匿,我希望這篇文章能夠拯救你淘钟。

SPA與MPA

MPA稱之為“多頁(yè)應(yīng)用”, 那么什么是多頁(yè)應(yīng)用呢?字面意思其實(shí)就是有多個(gè)頁(yè)面的應(yīng)用就是多頁(yè)應(yīng)用陪毡。從技術(shù)手段上來(lái)講米母,你可以這么粗略地理解。SPA,MPA不同點(diǎn)太多了毡琉,而且各有利弊铁瞒。

MPA應(yīng)用你需要單獨(dú)維護(hù)多個(gè)html頁(yè)面,而且我們每加載/切換一次頁(yè)面桅滋,都需要加載一整個(gè)頁(yè)面慧耍。但是它對(duì)于seo特別友好,因?yàn)槲覀兛梢越o每一個(gè)html頁(yè)面設(shè)置不同的meta等信息丐谋,從而達(dá)到更好的收錄效果芍碧;所以MPA多出現(xiàn)在大型的電商/新聞網(wǎng)站等。

不同于MPA号俐,SPA可以使得我們通過(guò)ajax或者其他技術(shù)動(dòng)態(tài)的更改某一個(gè)區(qū)域的內(nèi)容而不需要重新加載頁(yè)面泌豆,包括切換頁(yè)面也不會(huì)重新加載整個(gè)html,它對(duì)狀態(tài)的留存做的很好吏饿,而且在移動(dòng)端表現(xiàn)特別優(yōu)異(因?yàn)樵谝郧傲髁渴呛苷滟F的踪危,可以以最小的損失切換頁(yè)面蔬浙,無(wú)論是用戶體驗(yàn)還是成本相較于MPA都是極大的改善)

SSR

在我們web較早的時(shí)候,開發(fā)者喜歡使用jsp或者其他模板渲染引擎來(lái)構(gòu)造一個(gè)應(yīng)用陨倡。我們一般稱之為SSR(服務(wù)端渲染) 它的大致架構(gòu)是如下這個(gè)樣子

用戶發(fā)起一個(gè)請(qǐng)求抵達(dá)后端服務(wù)器后:

  1. 后端會(huì)將用戶所需要的內(nèi)容通過(guò)數(shù)據(jù)層進(jìn)行查詢
  2. 處理業(yè)務(wù)
  3. 通過(guò)模板來(lái)拼接頁(yè)面
  4. 返回一個(gè)html字符串給客戶端
  5. 前端渲染然后加載js腳本完成剩余交互

你可能也發(fā)現(xiàn)了敛滋,在SSR服務(wù)端渲染中,前端負(fù)責(zé)的東西太過(guò)單薄兴革,說(shuō)得好聽叫交互绎晃,難聽點(diǎn)就是“點(diǎn)擊事件工程師”。所以老一輩的后端基本人人都會(huì)前端杂曲,js的水平高的一抓一大把庶艾。隨著使用SSR渲染頁(yè)面的應(yīng)用越來(lái)越多,弊端也出現(xiàn)了:

  1. 后端做了太多事情了擎勘,再牛逼的人也吃不消
  2. 前后端耦合咱揍,維護(hù)難度升級(jí)
  3. 內(nèi)容更新/跳轉(zhuǎn),都需要重新加載一次頁(yè)面
  4. 服務(wù)端渲染成本很高
  5. ...

CSR

CSR(客戶端渲染)大致是以下的架構(gòu):

CSR架構(gòu)更貼近我們的現(xiàn)代前端開發(fā)棚饵,我們一般使用VUE, REACT這一類的前端視圖框架時(shí)煤裙,都是默認(rèn)CSR體系的。大致的流程是下面這樣子的:

  1. 瀏覽器向前端服務(wù)器請(qǐng)求html和js噪漾,html頁(yè)面是空html硼砰,并且同時(shí)執(zhí)行js
  2. js渲染頁(yè)面
  3. 通過(guò)后端暴露的api進(jìn)行交互

SSR和CSR的區(qū)別

可以發(fā)現(xiàn),使用CSR進(jìn)行開發(fā)欣硼,會(huì)有幾個(gè)明顯的缺點(diǎn)

SEO

因?yàn)閺那岸朔?wù)器獲取的html最開始是空html题翰,這非常不利于seo,很多搜索引擎的老版本蜘蛛會(huì)直接爬頁(yè)面诈胜,不會(huì)等待js加載完豹障,所以會(huì)直接爬出來(lái)一個(gè)空頁(yè)面。盡管現(xiàn)在的百度焦匈,谷歌等搜索引擎的爬蟲能力很強(qiáng)血公,能夠部分支持CSR SPA頁(yè)面,SEO效果雖然可以其他方式彌補(bǔ) (比如加入meta標(biāo)簽等等); 但是我們使用SSR完全不用擔(dān)心缓熟,因?yàn)楂@得的html頁(yè)面是一個(gè)完整的坞笙,可以直接渲染的。

用戶體驗(yàn)(白屏)

關(guān)于白屏荚虚,由于CSR從HTML構(gòu)建完成到JS渲染頁(yè)面完成(但還沒(méi)呈現(xiàn)頁(yè)面)這一段過(guò)程中薛夜,是處于一個(gè)白屏的時(shí)間,用戶體驗(yàn)很不好版述,反之使用SSR獲得HTML之后只需要直接構(gòu)建DOM就可以了梯澜。

同樣的,我們使用SSR還有不一樣的缺點(diǎn):

  1. 成本問(wèn)題(相比CSR多了構(gòu)建HTML以及獲取數(shù)據(jù),需要更多的服務(wù)器負(fù)載均衡)
  2. 部署問(wèn)題(與CSR部署環(huán)境不同晚伙,不是僅僅需要一個(gè)靜態(tài)文件托管服務(wù)器那么簡(jiǎn)單了)
  3. 代碼難度問(wèn)題
  4. ...

使用Vite快速構(gòu)建一個(gè)SSR(實(shí)踐SSR)

Vite SSR雖然現(xiàn)在是一個(gè)實(shí)驗(yàn)性質(zhì)吮龄,不能用于生產(chǎn)環(huán)境。但是我們可以使用Vite做一個(gè)ssr的demo咆疗,幫助我們理解SSR的構(gòu)建漓帚,理解之后我們?cè)賮?lái)引入"Nuxt", "同構(gòu)"等概念。Vite里面為SSR提供了很多支持午磁,所以我們要開發(fā)一個(gè)demo尝抖,會(huì)非常非常簡(jiǎn)單,你也可以參考這篇官網(wǎng)文檔

我們首先需要更改index.html的內(nèi)容

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <div id="app"></div>
    <script type="module" src="/src/entry-client"></script>
  </body>
</html>

可以看到我們?cè)赼pp的div里寫了一段注釋迅皇,到時(shí)候我們渲染完之后的html將會(huì)replace這個(gè)注釋昧辽。

然后需要在根目錄新建一個(gè)server.mjs,作為我們的服務(wù)入口登颓,用express作為一個(gè)例子:


import { readFileSync } from 'fs'
import { resolve } from 'path'
import express from 'express'
import { createServer as createViteServer } from 'vite'

const createServer = async () => {
  const app = express()
  const vite = await createViteServer({
    server: { middlewareMode: 'ssr' }
  })
  
  app.use(vite.middlewares)
  app.use('*', async (req, res) => {
    try {
      
      const url = req.originalUrl
      
      let template = readFileSync(resolve('index.html'), 'utf-8')
      
      template = await vite.transformIndexHtml(url, template)
      
      const { render } = await vite.ssrLoadModule('./src/entry-server.js')
      
      const appHtml = await render(url)
      
      const html = template.replace(`<!--ssr-outlet-->`, appHtml)
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (error) {
      vite.ssrFixStacktrace(e)
      console.error(e)
      res.status(500).end(e.message)
    }
  })

  app.listen(3000)
}

createServer()

我們的main.js也需要更改



import App from './App.vue'
import Router from './router'
import { createSSRApp } from 'vue'

export function createApp() {
  const app = createSSRApp(App)
  app.use(Router)
  return { app, router: Router }
}

我們?cè)趍ain.js中却妨,從vue導(dǎo)出createSSRApp函數(shù)栋荸,并且使用router躺彬,并且返回一個(gè)對(duì)象簿姨,這個(gè)對(duì)象之后將會(huì)被entry-server引用。

那么router也和我們傳統(tǒng)的csr應(yīng)用不太一樣喇嘱,我們根據(jù)env判斷茉贡,傳入了不同的路由類型:



import { createRouter, createWebHistory, createMemoryHistory } from 'vue-router'

const Router = createRouter({
  history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
  routes: [
    {
      name: 'index',
      path: '/index',
      component: () => import('../pages/index.vue')
    }
  ]
})

export default Router

然后我們需要在src中新建 entry-client.js(會(huì)被index.html引入) 以及 entry-server.js



import { createApp } from './main'

const { app, router } = createApp()

router.isReady().then(() => {
  app.mount('#app')
})


import { createApp } from './main'
import { renderToString } from 'vue/server-renderer'

export const render = async (url) => {
  try {
    const { app, router } = createApp()
    
    router.push(url)
    
    await router.isReady()
    const ctx = {}
    
    const html = await renderToString(app, ctx)
    return html
  } catch (error) {
    
  }
}

到此為止我們可以在本地啟動(dòng)一個(gè)服務(wù)器,并且可以將我們的頁(yè)面以ssr的形式渲染到瀏覽器中了婉称,由于我們的demo代碼都是esm块仆,所以我們使用node執(zhí)行构蹬,必須要寫成mjs的后綴王暗。

啟動(dòng)服務(wù)器之后,訪問(wèn)/index這個(gè)路由庄敛,你就能看到我們的頁(yè)面了

如果你的node版本不支持mjs俗壹,請(qǐng)先升級(jí)...

ssr示例項(xiàng)目:

  1. 本篇文章的demo
  2. 官方的demo

喝水,脫水藻烤,注水(SSR)

讀到這里绷雏,你或許已經(jīng)對(duì)ssr的流程有一個(gè)粗略的了解了;那么這一part的三個(gè)例子會(huì)加深你對(duì)ssr的理解怖亭,就是ssr常常說(shuō)的喝水涎显,脫水,注水兴猩。

我們ssr在服務(wù)端構(gòu)造頁(yè)面時(shí)期吓,數(shù)據(jù)是從數(shù)據(jù)源流下,使得我們頁(yè)面數(shù)據(jù)得到填充倾芝,這個(gè)過(guò)程就叫做喝水(render & beforeRender)喝水的過(guò)程就是在服務(wù)端渲染頁(yè)面做的事情讨勤,就好比下面這個(gè)圖:

飽滿的水氣球代表了一個(gè)健壯的網(wǎng)頁(yè)

我們實(shí)現(xiàn)ssr需要直出html箭跳,所以需要把結(jié)構(gòu)以及數(shù)據(jù)進(jìn)行脫水 (如圖)

然后到了客戶端,我們需要ssr應(yīng)用重新煥活潭千,就要讓原本脫水了的state,prop等等數(shù)據(jù)恢復(fù)到原來(lái)的生機(jī)谱姓,并且重新render組件,這個(gè)過(guò)程就叫做注水

SSG

SSG這種渲染模式采取了CSR和SSR的共同優(yōu)點(diǎn)刨晴,它不需要開發(fā)者介入服務(wù)器操作屉来,開發(fā)者只需要準(zhǔn)備cdn或者其他靜態(tài)網(wǎng)頁(yè)托管服務(wù)器,prerender出靜態(tài)資源這一步將在構(gòu)建時(shí)就已經(jīng)做了割捅,呈現(xiàn)在用戶眼前的雖然不是實(shí)時(shí)變更的奶躯,但是也保留了CSR和SSR的精髓,一定程度上有了平衡亿驾。但是因?yàn)閜rerender的緣故嘹黔,它和SSR的大致工作方式會(huì)相似一點(diǎn)。

也是有缺點(diǎn)的

  1. 隨著業(yè)務(wù)的復(fù)雜莫瞬,需要生成的頁(yè)面可能不單單只有1儡蔓,2個(gè),所以這對(duì)于構(gòu)建的要求很高
  2. 時(shí)效性問(wèn)題疼邀,用戶可能看到的頁(yè)面是上一次生成的喂江,所以這一部分仍需要其他模式來(lái)補(bǔ)充...

同構(gòu)SSR和CSR(共享data)

同構(gòu)說(shuō)白了,就是將我們的前端代碼旁振,既能在客戶端運(yùn)行获询,也能在服務(wù)端運(yùn)行,而且還能保持上下文的狀態(tài)拐袜,我們?cè)谏厦娴母脑炖右呀?jīng)實(shí)現(xiàn)了同一份代碼在2個(gè)端的運(yùn)行吉嚣,但是并沒(méi)有實(shí)現(xiàn)狀態(tài)的同步,比如我們?cè)趎uxt中蹬铺,使用asyncData這類鉤子一樣尝哆,能在服務(wù)端運(yùn)行而且返回的data可以和客戶端共享。


async asyncData({ store, $axios, $oss }) {
    return {
        hello: "world"
    }
}

我們現(xiàn)在需要改造我們的demo:



asyncData() {
   return {
     hello: 'message'
   }
 }

其次在server端將asyncData返回的對(duì)象和其他頁(yè)面html一起進(jìn)行脫水:


import { createApp } from './main'
import { renderToString } from 'vue/server-renderer'

export const render = async (url) => {
  try {
    const { app, router } = createApp()
    router.push(url)
    await router.isReady()
    let data = {}
    
    if (router.currentRoute.value.matched[0].components.default.asyncData) {
      const asyncFunc = router.currentRoute.value.matched[0].components.default.asyncData
      data = asyncFunc.call()
    }
    const html = await renderToString(app)
    return { html, data }
  } catch (error) {
    
  }
}

// 我們的server.mjs也需要變更一下



app.use('*', async (req, res) => {
    try {
      
      const url = req.originalUrl
      let template = readFileSync(resolve('index.html'), 'utf-8')
      template = await vite.transformIndexHtml(url, template)
      const { render } = await vite.ssrLoadModule('./src/entry-server.js')
      const { html: appHtml, data } = await render(url)
      
      const html = template.replace(`<!--ssr-outlet-->`, `${appHtml}<script>window.__data__=${JSON.stringify(data)}</script>`)
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
    } catch (error) {
      vite.ssrFixStacktrace(e)
      console.error(e)
      res.status(500).end(e.message)
    }
  })

可以看到我們將data序列化到了window對(duì)象中了甜攀,接下來(lái)我們需要在client端注水的時(shí)候秋泄,把新data進(jìn)行替換



router.isReady().then(() => {
  const component = router.currentRoute.value.matched[0].components.default
  let _data = {}
  
  if (typeof component.data === 'function') {
    _data = component.data.call()
  }
  
  if (window.__data__) {
    _data = {
      ..._data,
      ...window.__data__
    }
  }
  component.data = () => _data
  app.mount('#app')
})

這個(gè)時(shí)候我們已經(jīng)成功的看到index.vue中能夠正確的在template中打印hello這個(gè)字段了

到這里,你就可以舉一反三规阀,使用vuex也可以進(jìn)行同步數(shù)據(jù)恒序,都是把data序列化到window中保存,然后在client掛載前重新commit到store里面就可以了谁撼。

Nuxt3

是時(shí)候引入nuxt了歧胁,我們?nèi)绻褂胣uxt將會(huì)更容易的完成ssr需求,這一部分不會(huì)教大家怎么寫nuxt,畢竟都是框架与帆,都很簡(jiǎn)單了赌。我會(huì)和大家梳理一下nuxt2和nuxt3的變化,如果你用過(guò)nuxt2玄糟,那么這一部分內(nèi)容你可能會(huì)非常感興趣勿她。寫這篇文章的時(shí)候,nuxt3并沒(méi)有release阵翎,所以到時(shí)候release后會(huì)考慮再出一篇總結(jié)逢并。

值得關(guān)注的更新內(nèi)容

  1. 更好的性能
  2. esm的支持
  3. vue3更好的集成,說(shuō)明我們可以使用composition api了
  4. vite開發(fā)服務(wù)器加持
  5. webpack5 支持(盡管我不用)

Nitro Engine

簡(jiǎn)單翻閱了一下文檔郭卫,和大家分享一下砍聊,在nuxt3中的新服務(wù)端引擎 Nitro Engine, nuxt2中服務(wù)端核心使用的是connect.js,而nuxt3使用的是nuxt團(tuán)隊(duì)自研的h3框架贰军,特點(diǎn)就是具有很強(qiáng)的可移植性玻蝌,而且非常輕量級(jí),并且還支持connect編寫的中間件词疼。也就是說(shuō)nuxt3基于h3編寫的server端俯树,可以無(wú)縫地移植到支持js運(yùn)行環(huán)境的地方,比如說(shuō)woker贰盗,serverless...

我們先試試许饿,開發(fā)一個(gè)在nuxt3中使用的api


export default (req, res) => {
  return 'Hello World'
}

同樣,支持異步舵盈,也支持nodejs風(fēng)格的調(diào)用

export default async (req, res) => {
  res.statusCode = 200
  res.end('hello world')
}

nuxt3也支持在同一個(gè)server文件夾中編寫middleware陋率,而且是自動(dòng)導(dǎo)入的。nuxt3這次的更新秽晚,屬于是把文件系統(tǒng)玩出花了瓦糟,不光plugins不需要重復(fù)聲明了(nuxt2要在config重復(fù)聲明),而且components爆惧,composables(nuxt3新增的文件夾狸页,可以存放公共hook)... 都可以支持自動(dòng)導(dǎo)入锨能。

試想一下扯再,如今寫nuxt3應(yīng)用,搭配vue3 composition api址遇,將會(huì)使開發(fā)體驗(yàn)上升好幾個(gè)臺(tái)階熄阻。

文末,我們可以試試打包一個(gè)nuxt應(yīng)用到cloudflare 作為woker運(yùn)行是什么效果倔约?我們?cè)赽uild之后會(huì)發(fā)現(xiàn)output文件夾很簡(jiǎn)潔(不像nuxt2遷移部署都很令人頭疼)

我們不僅可以在最后的demo中看到頁(yè)面秃殉,也可以訪問(wèn) api/hello 這個(gè)路由查看剛剛我們?cè)趎uxt中定義的api

點(diǎn)擊訪問(wèn)
部署到cloudflare-文檔
demo地址

結(jié)語(yǔ)

又是水文一篇,希望以后可以出一些高質(zhì)量的總結(jié)文章,希望這篇文章所講述的前端常見的渲染模式钾军,你能夠知道鳄袍,并且知道原理,這也就是本文最終的目標(biāo)吏恭∞中。框架會(huì)不會(huì)都沒(méi)關(guān)系,我們要洞悉一切技術(shù)背后的真相樱哼,再去研究框架不是手到擒來(lái)么哀九?

本文使用 文章同步助手 同步

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市搅幅,隨后出現(xiàn)的幾起案子阅束,更是在濱河造成了極大的恐慌,老刑警劉巖茄唐,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件息裸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡沪编,警方通過(guò)查閱死者的電腦和手機(jī)界牡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)漾抬,“玉大人宿亡,你說(shuō)我怎么就攤上這事∧闪睿” “怎么了挽荠?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)平绩。 經(jīng)常有香客問(wèn)我圈匆,道長(zhǎng),這世上最難降的妖魔是什么捏雌? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任跃赚,我火速辦了婚禮,結(jié)果婚禮上性湿,老公的妹妹穿的比我還像新娘纬傲。我一直安慰自己,他們只是感情好肤频,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布叹括。 她就那樣靜靜地躺著,像睡著了一般宵荒。 火紅的嫁衣襯著肌膚如雪汁雷。 梳的紋絲不亂的頭發(fā)上净嘀,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音侠讯,去河邊找鬼挖藏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛厢漩,可吹牛的內(nèi)容都是我干的熬苍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼袁翁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼柴底!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起粱胜,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤柄驻,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后焙压,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸿脓,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年涯曲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了野哭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡幻件,死狀恐怖拨黔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绰沥,我是刑警寧澤篱蝇,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站徽曲,受9級(jí)特大地震影響零截,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜秃臣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一涧衙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧奥此,春花似錦弧哎、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)章贞。三九已至祥绞,卻和暖如春非洲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜕径。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工两踏, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兜喻。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓梦染,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親朴皆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子帕识,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容