Micro?Frontends extending the microservice idea to frontend development(譯)

原文地址:Micro?Frontends extending the microservice idea to frontend development

由多個擁有獨立發(fā)布能力的團隊構(gòu)建現(xiàn)代Web應(yīng)用程序的技術(shù)谤辜,策略和方法蓄坏。

什么是微前端价捧?

"微前端"一詞最早于2016年底出現(xiàn)在ThoughtWorks Technology Radar上。它將微服務(wù)的概念擴展到了前端世界涡戳。當前的趨勢是構(gòu)建一個特性豐富且功能強大的瀏覽器應(yīng)用程序(又名單頁應(yīng)用程序)结蟋,該應(yīng)用程序未應(yīng)用微服務(wù)架構(gòu)。隨著時間的流逝妹蔽,通常由獨立團隊開發(fā)的前端層會不斷增長椎眯,并且變得越來越難以維護。這就是我們所謂的巨石應(yīng)用胳岂。

微前端背后的想法是將網(wǎng)站或Web應(yīng)用程序視為由每個獨立團隊擁有功能的合集编整。每個團隊都有自己關(guān)心和專長的不同業(yè)務(wù)或任務(wù)領(lǐng)域。單個團隊是跨職能的乳丰,從數(shù)據(jù)庫到用戶界面掌测,端到端地開發(fā)其功能。

但是产园,這個想法并不新奇汞斧。它與自包含系統(tǒng)概念有很多共同點。過去什燕,類似的方法被稱為垂直系統(tǒng)的前端集成粘勒。但是微前端顯然是一個更友好,更小巧的術(shù)語屎即。

巨石應(yīng)用

Monolithic Frontends

垂直組織

End-To-End Teams with Micro Frontends

什么是現(xiàn)代web應(yīng)用庙睡?

在介紹中,我使用了"構(gòu)建現(xiàn)代web應(yīng)用程序"一詞技俐。讓我們定義與此術(shù)語相關(guān)的假設(shè)乘陪。

將這個東西放到更大的視野中,Aral Balkan寫過一篇博客文章雕擂,介紹了他的Documents-to-Applications Continuum啡邑。他提出了滑動比例尺的概念,其中在左側(cè)是一個由靜態(tài)文檔構(gòu)建且通過鏈接連接的站點井赌,而在右側(cè)是一個純行為驅(qū)動的谤逼,無內(nèi)容的應(yīng)用程序,例如在線照片編輯器仇穗。

如果您將項目放置在此范圍的左側(cè)流部,則非常適合在Web服務(wù)器級別進行集成。使用此模型仪缸,服務(wù)器從構(gòu)成用戶請求的頁面的所有組件中收集并連接HTML字符串贵涵。通過從服務(wù)器重新加載頁面或通過ajax替換頁面的一部分來完成更新。Gustaf Nilsson Kotte撰寫了有關(guān)此主題的綜合文章

當您的用戶界面必須提供實時反饋(即使是在不可靠的連接上)時宾茂,僅由服務(wù)器渲染的網(wǎng)站已不再滿足要求瓷马。要實現(xiàn)Optimistic UISkeleton Screens,您還需要能夠在設(shè)備本身上更新用戶界面跨晴。Google的術(shù)語Progressive Web Apps恰如其分地描述了成為網(wǎng)絡(luò)良好公民(漸進式增強功能)的平衡行為欧聘,同時還提供了類似App的功能表現(xiàn)。這種應(yīng)用程序位于site-app-continuum中間的某個位置端盆。在這里怀骤,僅基于服務(wù)器的解決方案已經(jīng)不能滿足要求。我們必須將集成轉(zhuǎn)移到瀏覽器中焕妙,這是本文的重點蒋伦。

微前端背后的核心思想

  • 技術(shù)獨立
    每個團隊都應(yīng)該能夠選擇和升級其技術(shù)棧,而不必與其他團隊進行協(xié)調(diào)焚鹊。自定義元素是一種隱藏實現(xiàn)細節(jié)痕届,同時為其他人提供中立接口的好方法。

  • 團隊代碼隔離
    即使所有團隊都使用相同的框架末患,也不要共享運行時間研叫。構(gòu)建獨立的獨立應(yīng)用程序。不要依賴共享狀態(tài)或全局變量璧针。

  • 建立團隊前綴
    同意尚無法隔離的命名約定嚷炉。命名空間CSS,事件探橱,本地存儲和Cookies申屹,以避免沖突并闡明所有權(quán)。

  • 通過自定義API支持本機瀏覽器功能
    使用瀏覽器事件進行通信而不是構(gòu)建全局的PubSub系統(tǒng)走搁。如果確實需要構(gòu)建跨團隊API独柑,請盡量簡單迈窟。

  • 建立彈性站點
    即使JavaScript失敗或尚未執(zhí)行私植,您的功能也應(yīng)該很有用。使用通用渲染和漸進增強功能可改善感知性能车酣。

DOM是API

自定義元素曲稼,是Web組件規(guī)范中的互操作性,是集成到瀏覽器中的一個很好的原始方法湖员。每個團隊都使用自己選擇的網(wǎng)絡(luò)技術(shù)來構(gòu)建其組件贫悄,并將其包裝在一個自定義元素中(例如<order-minicart> </order-minicart\>)。該特定元素(標簽娘摔,屬性和事件)的DOM規(guī)范充當其他團隊的合約或公共API窄坦。優(yōu)點是他們可以使用組件及其功能,而無需了解實現(xiàn)。他們只需要能夠與DOM交互即可鸭津。

但是彤侍,僅自定義元素并不能滿足我們所有需求。為了解決漸進增強逆趋,通用渲染或路由問題盏阶,我們需要其他軟件。

該頁面分為兩個主要區(qū)域闻书。首先名斟,我們將討論頁面組成-如何根據(jù)不同團隊擁有的組件來組裝頁面。之后,我們將展示實現(xiàn)客戶端頁面轉(zhuǎn)換的示例打月。

頁面組成

除了使用不同框架本身編寫的客戶端和服務(wù)器端代碼集成之外谈撒,還應(yīng)該討論很多附帶主題:隔離js的機制,避免css沖突楞卡,按需加載資源,在團隊之間共享公共資源脾歇,處理數(shù)據(jù)獲取 并考慮為用戶提供良好的加載狀態(tài)蒋腮。 我們將一步一步地涉及這些主題。

基本原型

拖拉機商店的不同型號產(chǎn)品頁面將作為以下示例的基礎(chǔ)藕各。

它具有一個變體選擇器池摧,可以在三種不同的拖拉機型號之間切換。 更改產(chǎn)品圖片時激况,名稱作彤,價格和建議會更新。 還有一個購買按鈕乌逐,將選擇的變體添加到購物籃竭讳,并在頂部增加一個迷你購物籃,并相應(yīng)地進行更新浙踢。

Example 0 - Product Page - Plain JS

try in browser & inspect the code

所有HTML都是使用純JavaScript和沒有依賴性的ES6模板字符串在客戶端生成的绢慢。該代碼使用簡單的狀態(tài)/標記分離,并在每次更改時重新呈現(xiàn)整個HTML客戶端-無需花哨的DOM區(qū)別洛波,也沒有通用渲染胰舆。也沒有團隊區(qū)分-代碼寫在一個js/css文件中。

客戶端集成

在此示例中蹬挤,頁面分為三個團隊擁有的單獨的組件/片段缚窿。Team Checkout(藍色)現(xiàn)在負責(zé)與購買過程有關(guān)的所有事情,即“購買”按鈕和迷你購物籃焰扳。Team Inspire(綠色)在此頁面上管理產(chǎn)品推薦倦零。該頁面本身歸團隊產(chǎn)品(紅色)所有误续。

Example 1 - Product Page - Composition

try in browser & inspect the code

團隊產(chǎn)品決定要包括的功能以及在布局中的位置。該頁面包含團隊產(chǎn)品本身可以提供的信息扫茅,例如產(chǎn)品名稱女嘲,圖像和可用的變體。但是它也包括其他團隊的片段(自定義元素)诞帐。

如何創(chuàng)建自定義元素欣尼?

讓我們以“購買”按鈕為例。Team Product包含按鈕停蕉,只需將<blue-buy sku =“t_porsche”> </blue-buy>添加到標記中的所需位置即可愕鼓。為此,Team Checkout必須在頁面上注冊藍色購買元素慧起。

class BlueBuy extends HTMLElement {
  connectedCallback() {
    this.innerHTML = `<button type="button">buy for 66,00 €</button>`;
  }

  disconnectedCallback() { ... }
}

window.customElements.define('blue-buy'菇晃,BlueBuy);

現(xiàn)在,每次瀏覽器遇到一個新的blue-buy標簽時蚓挤,都會調(diào)用connectedCallback磺送。這是對自定義元素的根DOM節(jié)點的引用〔右猓可以使用標準DOM元素(例如innerHTML或getAttribute())的所有屬性和方法估灿。

Custom Element in Action

命名元素時,規(guī)范定義的唯一要求是名稱必須包含短劃線(-)缤剧,以保持與即將到來的新HTML標簽的兼容性馅袁。在接下來的示例中,使用了命名約定[team_color]-[feature]荒辕。團隊命名空間可防止名稱沖突汗销,因此只需查看DOM,就可以清楚地了解功能的所有權(quán)抵窒。

父子通信 / DOM修改

當用戶在變量選擇器中選擇另一臺拖拉機時弛针,必須相應(yīng)地更新購買按鈕。要實現(xiàn)此團隊產(chǎn)品李皇,只需從DOM中刪除現(xiàn)有元素削茁,然后插入一個新元素即可。

container.innerHTML;
// => <blue-buy sku="t_porsche">...</blue-buy>
container.innerHTML = '<blue-buy sku="t_fendt"></blue-buy>';

同步調(diào)用舊元素的connectedCallback疙赠,以使該元素有機會清理諸如事件偵聽器之類的東西付材。之后朦拖,將調(diào)用新創(chuàng)建的t_fendt元素的connectedCallback圃阳。

另一個性能更高的選項是僅更新現(xiàn)有元素上的sku屬性。

document.querySelector('blue-buy').setAttribute('sku', 't_fendt');

如果Team Product使用具有DOM差異功能的模板引擎(如React)璧帝,則將由算法自動完成捍岳。

Custom Element Attribute Change

為了支持這一點,Custom元素可以實現(xiàn)attributeChangedCallback并指定觀察到的屬性列表,應(yīng)為其觸發(fā)此回調(diào)

const prices = {
  t_porsche: '66,00 €',
  t_fendt: '54,00 €',
  t_eicher: '58,00 €',
};

class BlueBuy extends HTMLElement {
  static get observedAttributes() {
    return ['sku'];
  }
  connectedCallback() {
    this.render();
  }
  render() {
    const sku = this.getAttribute('sku');
    const price = prices[sku];
    this.innerHTML = `<button type="button">buy for ${price}</button>`;
  }
  attributeChangedCallback(attr, oldValue, newValue) {
    this.render();
  }
  disconnectedCallback() {...}
}

window.customElements.define('blue-buy', BlueBuy);

為了避免重復(fù)锣夹,引入了render()方法页徐,該方法在connectedCallback和attributeChangedCallback中被調(diào)用。這種方法收集所需的數(shù)據(jù)银萍,而innerHTML就是新的標記变勇。當決定在Custom Element中使用更復(fù)雜的模板引擎或框架時,這里就是初始化代碼的地方贴唇。

瀏覽器支持

上面的示例使用了當前支持Chrome搀绣,Safari和Opera自定義元素V1規(guī)范。但是戳气,使用document-register-element可以使用輕量級且經(jīng)歷過戰(zhàn)斗考驗的polyfill來使其在所有瀏覽器中都能正常工作链患。在幕后,它使用廣泛支持的Mutation Observer API瓶您,因此在后臺不會出現(xiàn)駭人的DOM樹麻捻。

框架兼容性

由于自定義元素是一種網(wǎng)絡(luò)標準,因此所有主流的JavaScript框架(如Angular呀袱,React贸毕,Preact,Vue或Hyperapp)都支持它們夜赵。 但是當您進入細節(jié)時崖咨,某些框架中仍然存在一些實現(xiàn)問題。在無處不在的自定義元素Rob Dodson匯集了一個兼容性測試套件油吭,著重強調(diào)了尚未解決的問題击蹲。

父子或兄弟組件通信 / DOM事件

但是,屬性傳遞不足以進行所有交互婉宰。在我們的示例中歌豺,當用戶單擊“購買”按鈕時,迷你購物籃應(yīng)刷新心包。

這兩個片段均由Team Checkout擁有(藍色)类咧,因此它們可以構(gòu)建某種內(nèi)部JavaScript API,該API可使迷你購物籃知道何時按下按鈕蟹腾。但這將要求組件實例相互通信痕惋,并且沖突隔離。

較干凈的方法是使用PubSub機制娃殖,在該機制中值戳,一個組件可以發(fā)布消息,而其他組件可以訂閱特定主題炉爆。幸運的是堕虹,瀏覽器內(nèi)置了此功能卧晓。這正是單擊,選擇或鼠標懸停之類的瀏覽器事件的工作方式赴捞。除了本機事件逼裆,還可以使用新的CustomEvent(...)創(chuàng)建更高級別的事件。事件始終與創(chuàng)建/調(diào)度事件的DOM節(jié)點相關(guān)赦政。大多數(shù)本機事件還具有冒泡功能胜宇。這樣就可以偵聽DOM特定子樹上的所有事件。如果要偵聽頁面上的所有事件恢着,請將事件偵聽器附加到window元素掸屡。在示例中,blue:basket:changed-event的創(chuàng)建如下所示:

class BlueBuy extends HTMLElement {
  [...]
  connectedCallback() {
    [...]
    this.render();
    this.firstChild.addEventListener('click', this.addToCart);
  }
  addToCart() {
    // maybe talk to an api
    this.dispatchEvent(new CustomEvent('blue:basket:changed', {
      bubbles: true,
    }));
  }
  render() {
    this.innerHTML = `<button type="button">buy</button>`;
  }
  disconnectedCallback() {
    this.firstChild.removeEventListener('click', this.addToCart);
  }
}

現(xiàn)在然评,迷你購物籃可以在窗口上訂閱此事件仅财,并在刷新數(shù)據(jù)時得到通知。

class BlueBasket extends HTMLElement {
  connectedCallback() {
    [...]
    window.addEventListener('blue:basket:changed', this.refresh);
  }
  refresh() {
    // fetch new data and render it
  }
  disconnectedCallback() {
    window.removeEventListener('blue:basket:changed', this.refresh);
  }
}

通過這種方法碗淌,迷你籃片段向其范圍(窗口)之外的DOM元素添加了一個偵聽器盏求。對于大量的應(yīng)用程序這都沒問題,但是如果您對此不滿意亿眠,則還可以實現(xiàn)一種方法碎罚,其中頁面本身(團隊產(chǎn)品)偵聽事件并通過在DOM元素上調(diào)用refresh()通知迷你購物籃。

// page.js
const $ = document.getElementsByTagName;

$('blue-buy')[0].addEventListener('blue:basket:changed', function() {
  $('blue-basket')[0].refresh();
});

強制性地調(diào)用DOM方法并不常見纳像,但是可以在video element api中找到荆烈。 如果可以,應(yīng)首選使用聲明式方法(更改屬性)竟趾。

服務(wù)器端渲染/通用渲染

自定義元素非常適合在瀏覽器內(nèi)部集成組件憔购。但是,當構(gòu)建一個可在Web上訪問的站點時岔帽,初始負載性能很可能會變得很重要玫鸟,并且在下載并執(zhí)行所有js框架之前,用戶將看到白屏犀勒。另外屎飘,最好考慮一下如果JavaScript失敗或被阻塞,網(wǎng)站會發(fā)生什么情況贾费。Jeremy Keith在其電子書/播客Resilient Web Design中解釋了其重要性钦购。因此,在服務(wù)器上呈現(xiàn)核心內(nèi)容的能力是關(guān)鍵褂萧。不幸的是押桃,Web組件規(guī)范根本沒有涉及服務(wù)器渲染。沒有JavaScript箱玷,沒有自定義元素:(

自定義元素+服務(wù)器端包含=??

為了使服務(wù)器渲染正常工作怨规,重構(gòu)了前面的示例陌宿。每個團隊都有自己的express服務(wù)器锡足,還可以通過url訪問Custom Element的render()方法波丰。

$ curl http://127.0.0.1:3000/blue-buy?sku=t_porsche
<button type="button">buy for 66,00 €</button>

“自定義元素”標記名稱用作路徑名-屬性成為查詢參數(shù)。現(xiàn)在舶得,有一種方法可以通過服務(wù)器渲染每個組件的內(nèi)容掰烟。結(jié)合<blue-buy >-Custom Elements,可以實現(xiàn)與通用Web組件非常接近的功能:

<blue-buy sku="t_porsche">
  <!--#include virtual="/blue-buy?sku=t_porsche" -->
</blue-buy>

include注釋是服務(wù)器端包含的一部分沐批,該功能在大多數(shù)Web服務(wù)器中都可用纫骑。是的,將當前日期嵌入到我們網(wǎng)站上九孩,這是過去經(jīng)常使用的的一種技術(shù)先馆。還有一些替代技術(shù),例如ESI躺彬,nodesi煤墙,compoxuretailor,但是對于我們的項目宪拥,SSI已被證明是一種簡單且難以置信的穩(wěn)定解決方案仿野。

在Web服務(wù)器將完整頁面發(fā)送到瀏覽器之前,#include注釋將替換為/ blue-buy?sku=t_porsche的響應(yīng)她君。nginx中的配置如下所示:

upstream team_blue {
  server team_blue:3001;
}
upstream team_green {
  server team_green:3002;
}
upstream team_red {
  server team_red:3003;
}

server {
  listen 3000;
  ssi on;

  location /blue {
    proxy_pass  http://team_blue;
  }
  location /green {
    proxy_pass  http://team_green;
  }
  location /red {
    proxy_pass  http://team_red;
  }
  location / {
    proxy_pass  http://team_red;
  }
}

指令ssi:on; 啟用SSI功能脚作,并為每個團隊添加上游和位置塊,以確保將所有以/blue開頭的url路由到正確的應(yīng)用程序(team_blue:3001)缔刹。另外球涛,/路由映射到紅色隊,該紅色隊控制著主頁/產(chǎn)品頁面校镐。

此動畫在禁用了JavaScript的瀏覽器中顯示了拖拉機商店宾符。

Serverside Rendering - Disabled JavaScript

inspect the code

現(xiàn)在,變體選擇按鈕是實際的鏈接灭翔,每次單擊都會導(dǎo)致頁面重新加載魏烫。右側(cè)的終端說明了如何將頁面請求路由到紅色團隊,該團隊控制產(chǎn)品頁面肝箱,然后用藍色和綠色團隊的片段補充標記哄褒。

重新打開JavaScript時,僅第一個請求的服務(wù)器日志消息可見煌张。像第一個示例一樣呐赡,所有后續(xù)的拖拉機更改都由客戶端處理。在后面的示例中骏融,產(chǎn)品數(shù)據(jù)將從JavaScript中提取链嘀,并根據(jù)需要通過REST API加載萌狂。

您可以在本地計算機上使用此示例代碼。 僅需要安裝Docker Compose怀泊。

git clone https://github.com/neuland/micro-frontends.git
cd micro-frontends/2-composition-universal
docker-compose up --build

然后茫藏,Docker在端口3000上啟動Nginx,并為每個團隊構(gòu)建node.js映像霹琼。在瀏覽器中打開http://127.0.0.1:3000/時务傲,應(yīng)該會看到一個紅色的拖拉機。docker-compose的組合日志可輕松查看網(wǎng)絡(luò)中正在發(fā)生的事情枣申。遺憾的是售葡,無法控制輸出顏色,因此您必須忍受以下事實:藍色團隊可能會以綠色突出顯示:)

src文件映射到各個容器中忠藤,并且在更改代碼后挟伙,節(jié)點應(yīng)用程序?qū)⒅匦聠印8膎ginx.conf要求重新啟動docker-compose才能生效模孩。因此尖阔,隨時隨意擺弄并提供反饋。

數(shù)據(jù)獲取和加載狀態(tài)

SSI/ESI方法的缺點是瓜贾,最慢的片段確定整個頁面的響應(yīng)時間诺祸。因此,最好是可以緩存片段的響應(yīng)祭芦。對于制作成本高且難以緩存的片段筷笨,通常最好將其從初始渲染中排除。 可以將它們異步加載到瀏覽器中龟劲。在我們的示例中胃夏,綠色個性化片段顯示了個性化推薦,這是一個備選方案昌跌。

一種可能的解決方案是仰禀,紅色團隊僅跳過SSI Include。

之前

<green-recos sku="t_porsche">
  <!--#include virtual="/green-recos?sku=t_porsche" -->
</green-recos>

之后

<green-recos sku="t_porsche"></green-recos>

重要說明:自定義元素無法自動關(guān)閉蚕愤,因此編寫<green-recos sku="t_porsche" />無法正常工作答恶。

Reflow

渲染僅在瀏覽器中進行。但是萍诱,從動畫中可以看出悬嗓,此更改現(xiàn)在導(dǎo)致頁面的大量重排。推薦區(qū)域最初是空白的裕坊。團隊果嶺JavaScript已加載并執(zhí)行包竹。進行了用于獲取個性化推薦的API調(diào)用。呈現(xiàn)推薦標記,并請求關(guān)聯(lián)的圖像≈芟梗現(xiàn)在苗缩,該片段需要更多空間并推動頁面布局。

有不同的選擇來避免像這樣令人討厭的重排声诸。建議控制頁面的紅色小組可以固定容器的高度酱讶。在響應(yīng)式網(wǎng)站上,確定高度通常很棘手双絮,因為不同的屏幕尺寸可能會有所不同浴麻。但是更重要的問題是得问,這種團隊間協(xié)議在紅色和綠色團隊之間建立了緊密的聯(lián)系囤攀。如果綠色團隊希望在reco元素中添加其他子標題,則必須與紅色團隊在新高度上進行協(xié)調(diào)宫纬。兩個團隊都必須同時推出他們的更改焚挠,以避免布局混亂。

更好的方法是使用一種稱為骨架屏漓骚。紅色小組將綠色recos SSI包括在標記中蝌衔。此外,團隊綠色更改了其片段的服務(wù)器端渲染方法蝌蹂,以便生成內(nèi)容的示意圖噩斟。骨架標記可以重用實際內(nèi)容的部分布局樣式。這樣孤个,它可以保留所需的空間剃允,并且實際內(nèi)容的填充不會導(dǎo)致跳轉(zhuǎn)。

Skeleton Screen

骨架屏幕對于客戶端渲染也非常有用齐鲤。當您的自定義元素由于用戶操作而插入DOM時斥废,它可以立即渲染框架,直到從服務(wù)器需要的數(shù)據(jù)到達為止给郊。

即使在諸如變量選擇之類的屬性更改上牡肉,您也可以決定切換到框架視圖,直到新數(shù)據(jù)到達淆九。這樣统锤,用戶可以得到片段中正在發(fā)生某些事情的指示。但是炭庙,當端點快速響應(yīng)時饲窿,新舊數(shù)據(jù)之間的短暫骨架閃爍也可能很煩人。保留舊數(shù)據(jù)或使用智能超時可以有所幫助煤搜。因此免绿,請明智地使用此技術(shù),并嘗試獲取用戶反饋擦盾。

在頁面之間導(dǎo)航

即將繼續(xù)……(我保證)

關(guān)注Github Repo以獲得通知

其他資源

即將發(fā)生的事情……(很快)

  • 用例
    • 在頁面之間導(dǎo)航
      • 軟導(dǎo)航與硬導(dǎo)航
      • 通用路由器
  • 主題
    • 隔離的CSS /一致的用戶界面/樣式指南和樣式庫
    • 初始負載下的性能
    • 使用網(wǎng)站時的表現(xiàn)
    • 加載CSS
    • 加載JS
    • 集成測試

貢獻者

該站點由Github Pages生成帖池。 它的來源可以在neuland/micro-frontends上找到奈惑。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市睡汹,隨后出現(xiàn)的幾起案子肴甸,更是在濱河造成了極大的恐慌,老刑警劉巖囚巴,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件原在,死亡現(xiàn)場離奇詭異,居然都是意外死亡文兢,警方通過查閱死者的電腦和手機晤斩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姆坚,“玉大人澳泵,你說我怎么就攤上這事〖婧牵” “怎么了兔辅?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長击喂。 經(jīng)常有香客問我维苔,道長,這世上最難降的妖魔是什么懂昂? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任介时,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沸柔。我一直安慰自己循衰,他們只是感情好,可當我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布褐澎。 她就那樣靜靜地躺著会钝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪工三。 梳的紋絲不亂的頭發(fā)上迁酸,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機與錄音俭正,去河邊找鬼奸鬓。 笑死,一個胖子當著我的面吹牛段审,可吹牛的內(nèi)容都是我干的全蝶。 我是一名探鬼主播闹蒜,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼寺枉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绷落?” 一聲冷哼從身側(cè)響起姥闪,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎砌烁,沒想到半個月后筐喳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡函喉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年避归,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片管呵。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡梳毙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捐下,到底是詐尸還是另有隱情账锹,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布坷襟,位于F島的核電站奸柬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏婴程。R本人自食惡果不足惜廓奕,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧桌粉,春花似錦授段、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至缘薛,卻和暖如春窍育,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宴胧。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工漱抓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人恕齐。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓乞娄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親显歧。 傳聞我的和親對象是個殘疾皇子仪或,可洞房花燭夜當晚...
    茶點故事閱讀 45,585評論 2 359