改造你的網(wǎng)站看锉,變身 PWA

pwa
pwa

最近有很多關(guān)于 Progressive Web Apps(PWAs)的消息诸迟,很多人都在問這是不是(移動)web 的未來。我不想陷入native app 和 PWA 的紛爭食拜,但是有一件事是確定的 --- PWA極大的提升了移動端表現(xiàn)鸵熟,改善了用戶體驗。

好消息是開發(fā)一個 PWA 并不難负甸。事實上流强,我們可以將現(xiàn)存的網(wǎng)站進行改進痹届,使之成為PWA。這也是我這篇文章要講的 -- 當(dāng)你讀完這篇文章打月,你可以將你的網(wǎng)站改進队腐,讓他看起來就像是一個 native web app。他可以離線工作并且擁有自己的主屏圖標(biāo)奏篙。

Progressive Web Apps 是什么柴淘?

Progressive Web Apps (下文以“PWAs”代指) 是一個令人興奮的前端技術(shù)的革新。PWAs綜合了一系列技術(shù)使你的 web app表現(xiàn)得就像是 native mobile app秘通。相比于純 web 解決方案和純 native 解決方案为严,PWAs對于開發(fā)者和用戶有以下優(yōu)點:

  1. 你只需要基于開放的 W3C 標(biāo)準(zhǔn)的 web 開發(fā)技術(shù)來開發(fā)一個app。不需要多客戶端開發(fā)肺稀。

  2. 用戶可以在安裝前就體驗?zāi)愕?app第股。

  3. 不需要通過 AppStore 下載 app。app 會自動升級不需要用戶升級话原。

  4. 用戶會受到‘安裝’的提示夕吻,點擊安裝會增加一個圖標(biāo)到用戶首屏。

  5. 被打開時繁仁,PWA 會展示一個有吸引力的閃屏涉馅。

  6. chrome 提供了可選選項,可以使 PWA 得到全屏體驗黄虱。

  7. 必要的文件會被本地緩存稚矿,因此會比標(biāo)準(zhǔn)的web app 響應(yīng)更快(也許也會比native app響應(yīng)快)

  8. 安裝及其輕量 -- 或許會有幾百 kb 的緩存數(shù)據(jù)。

  9. 網(wǎng)站的數(shù)據(jù)傳輸必須是 https 連接悬钳。

  10. PWAs 可以離線工作盐捷,并且在網(wǎng)絡(luò)恢復(fù)時可以同步最新數(shù)據(jù)偶翅。

現(xiàn)在還處在 PWA 的早期默勾,但已經(jīng)有 很多成功案例

PWA 技術(shù)目前被 Firefox聚谁,Chrome 和其他基于Blink內(nèi)核的瀏覽器支持母剥。微軟正在努力在Edge瀏覽器上實現(xiàn)。Apple沒有動作 although there are promising comments in the WebKit five-year plan形导。幸運的是环疼,瀏覽器支持對于 PWA 似乎不太重要...

PWAs 是漸進增強的

你的app仍然可以運行在不支持 PWA 技術(shù)的瀏覽器里。用戶不能離線訪問朵耕,不過其他功能都像原來一樣沒有影響炫隶。綜合利弊得失,沒有理由不把你的 app 改進為 PWA阎曹。

不只是 Apps

Google 引領(lǐng)了 PWA 的一系列動作伪阶,所以大多數(shù)教程都在說如何從零開始構(gòu)建一個基于 Chrome煞檩,native-looking mobile app。然而并不是只有特殊的單頁應(yīng)用可以PWA化栅贴,也不需要一定遵循 material interface design guidelines斟湃。大多數(shù)網(wǎng)站都可以在數(shù)小時內(nèi)實現(xiàn) PWA 化。這包括你的 WordPress站點或者靜態(tài)站點檐薯。

示例代碼

示例代碼可以在https://github.com/sitepoint-editors/pwa-retrofit找到凝赛。

代碼提供了一個簡單的四個頁面的網(wǎng)站。其中包含一些圖片坛缕,一個樣式表和一個main javascript 文件墓猎。這個網(wǎng)站可以運行在所有現(xiàn)代瀏覽器上(IE10+)。如果瀏覽器支持 PWA 技術(shù)赚楚,當(dāng)離線時用戶可以瀏覽他們之前看過的頁面陶衅。

運行代碼前,確保 Node.js 已經(jīng)安裝直晨,然后再命令行里啟動服務(wù):

node ./server.js [port]

[port]是可配置的搀军,默認(rèn)為 8888。打開 Chrome 或者其他基于Blink內(nèi)核的瀏覽器勇皇,比如 Opera 或者 Vivaldi罩句,然后輸入鏈接 http://localhost:8888/(或者你指定的某個端口)。你也可以打開開發(fā)者工具看一下各個console信息敛摘。

瀏覽主頁门烂,或者其他頁面,然后用以下任一方法使頁面離線:

  1. 按下 Cmd/Ctrl + C 兄淫,停止 node 服務(wù)器屯远,或者

  2. 在開發(fā)者工具的 Network 或者 Application - Service Workers 欄里點擊 offline 選項。

重新瀏覽任意之前瀏覽過的頁面捕虽,它們?nèi)匀豢梢詾g覽到慨丐。瀏覽一個之前沒有看過的頁面,你會看到一個專門的離線頁面泄私,標(biāo)識“you’re offline”房揭,還有一個你可以瀏覽的頁面列表:

連接手機

你也可以通過 USB 連接你的安卓手機來預(yù)覽示例網(wǎng)頁。在開發(fā)者工具中打開 Remote devices 菜單晌端。

在左邊選擇 Settings 捅暴,點擊 Add Rule 輸入 8888 端口。你可以在你的手機上打開Chrome咧纠,打開 http://localhost:8888/蓬痒。

你可以點擊瀏覽器菜單里的 “Add to Home screen”。瀏覽幾個頁面漆羔,瀏覽器會提醒你去安裝梧奢。這兩種方式都可以創(chuàng)建一個新的圖標(biāo)在你的主屏上瞪讼。瀏覽幾個頁面后關(guān)掉Chrome,斷開設(shè)備連接粹断。你依然可以打開 PWA Website app -- 你會看到一個啟動頁符欠,并且可以離線訪問之前你訪問過的頁面。

將你的網(wǎng)站改進為一個 Progressive Web App 總共有三個必要步驟:

第一步:開啟 HTTPS

由于一些顯而易見的原因瓶埋,PWAs 需要 HTTPS 連接希柿。

HTTPS 在示例代碼中并不是必須的,因為 Chrome 允許使用 localhost 或者任何 127.x.x.x 的地址來測試养筒。你也可以在 HTTP 連接下測試你的 PWA曾撤,你需要使用 Chrome ,并且輸入以下命令行參數(shù):

  • --user-data-dir
  • --unsafety-treat-insecure-origin-as-secure

第二步:創(chuàng)建一個 Web App Manifest

manifest 文件提供了一些我們網(wǎng)站的信息晕粪,例如 name挤悉,description 和需要在主屏使用的圖標(biāo)的圖片,啟動屏的圖片等巫湘。

manifest文件是一個 JSON 格式的文件装悲,位于你項目的根目錄。它必須用Content-Type: application/manifest+json 或者 Content-Type: application/json這樣的 HTTP 頭來請求尚氛。這個文件可以被命名為任何名字诀诊,在示例代碼中他被命名為 /manifest.json:

{
  "name"              : "PWA Website",
  "short_name"        : "PWA",
  "description"       : "An example PWA website",
  "start_url"         : "/",
  "display"           : "standalone",
  "orientation"       : "any",
  "background_color"  : "#ACE",
  "theme_color"       : "#ACE",
  "icons": [
    {
      "src"           : "/images/logo/logo072.png",
      "sizes"         : "72x72",
      "type"          : "image/png"
    },
    {
      "src"           : "/images/logo/logo152.png",
      "sizes"         : "152x152",
      "type"          : "image/png"
    },
    {
      "src"           : "/images/logo/logo192.png",
      "sizes"         : "192x192",
      "type"          : "image/png"
    },
    {
      "src"           : "/images/logo/logo256.png",
      "sizes"         : "256x256",
      "type"          : "image/png"
    },
    {
      "src"           : "/images/logo/logo512.png",
      "sizes"         : "512x512",
      "type"          : "image/png"
    }
  ]
}

在頁面的<head>中引入:

<link rel="manifest" href="/manifest.json">

manifest 中主要屬性有:

  • name —— 網(wǎng)頁顯示給用戶的完整名稱
  • short_name —— 當(dāng)空間不足以顯示全名時的網(wǎng)站縮寫名稱
  • description —— 關(guān)于網(wǎng)站的詳細(xì)描述
  • start_url —— 網(wǎng)頁的初始 相對 URL(比如 /
  • scope —— 導(dǎo)航范圍。比如阅嘶,/app/的scope就限制 app 在這個文件夾里属瓣。
  • background-color —— 啟動屏和瀏覽器的背景顏色
  • theme_color —— 網(wǎng)站的主題顏色,一般都與背景顏色相同讯柔,它可以影響網(wǎng)站的顯示
  • orientation —— 首選的顯示方向:any, natural, landscape, landscape-primary, landscape-secondary, portrait, portrait-primary, 和 portrait-secondary抡蛙。
  • display —— 首選的顯示方式:fullscreen, standalone(看起來像是native app),minimal-ui(有簡化的瀏覽器控制選項) 和 browser(常規(guī)的瀏覽器 tab)
  • icons —— 定義了 src URL, sizestype的圖片對象數(shù)組魂迄。

MDN提供了完整的manifest屬性列表:Web App Manifest properties

在開發(fā)者工具中的 Application tab 左邊有 Manifest 選項粗截,你可以驗證你的 manifest JSON 文件,并提供了 “Add to homescreen”极祸。

第三步:創(chuàng)建一個 Service Worker

Service Worker 是攔截和響應(yīng)你的網(wǎng)絡(luò)請求的編程接口慈格。這是一個位于你根目錄的一個單獨的 javascript 文件。

你的 js 文件(在示例代碼中是 /js/main.js)可以檢查是否支持 Service Worker遥金,并且注冊:

if ('serviceWorker' in navigator) {

  // register service worker
  navigator.serviceWorker.register('/service-worker.js');

}

如果你不需要離線功能,可以簡單的創(chuàng)建一個空的 /service-worker.js文件 —— 用戶會被提示安裝你的 app蒜田。

Service Worker 很復(fù)雜稿械,你可以修改示例代碼來達到自己的目的。這是一個標(biāo)準(zhǔn)的 web worker冲粤,瀏覽器用一個單獨的線程來下載和執(zhí)行它美莫。它沒有調(diào)用 DOM 和其他頁面 api 的能力页眯,但他可以攔截網(wǎng)絡(luò)請求,包括頁面切換厢呵,靜態(tài)資源下載窝撵,ajax請求所引起的網(wǎng)絡(luò)請求。

這就是需要 HTTPS 的最主要的原因襟铭。想象一下第三方代碼可以攔截來自其他網(wǎng)站的 service worker碌奉, 將是一個災(zāi)難。

service worker 主要有三個事件: install寒砖,activatefetch赐劣。

Install 事件

這個事件在app被安裝時觸發(fā)。它經(jīng)常用來緩存必要的文件哩都。緩存通過 Cache API來實現(xiàn)魁兼。

首先,我們來構(gòu)造幾個變量:

  1. 緩存名稱(CACHE)和版本號(version)漠嵌。你的應(yīng)用可以有多個緩存但是只能引用一個咐汞。我們設(shè)置了版本號,這樣當(dāng)我們有重大更新時儒鹿,我們可以更新緩存碉考,而忽略舊的緩存。

  2. 一個離線頁面的URL(offlineURL)挺身。當(dāng)離線時用戶試圖訪問之前未緩存的頁面時侯谁,這個頁面會呈現(xiàn)給用戶。

  3. 一個擁有離線功能的頁面必要文件的數(shù)組(installFilesEssential)章钾。這個數(shù)組應(yīng)該包含靜態(tài)資源墙贱,比如 CSS 和 JavaScript 文件,但我也把主頁面(/)和圖標(biāo)文件寫進去了贱傀。如果主頁面可以多個URL訪問惨撇,你應(yīng)該把他們都寫進去,比如//index.html府寒。注意魁衙,offlineURL也要被寫入這個數(shù)組。

  4. 可選的株搔,描述文件數(shù)組(installFilesDesirable)剖淀。這些文件都很會被下載,但如果下載失敗不會中止安裝纤房。

// configuration
const
  version = '1.0.0',
  CACHE = version + '::PWAsite',
  offlineURL = '/offline/',
  installFilesEssential = [
    '/',
    '/manifest.json',
    '/css/styles.css',
    '/js/main.js',
    '/js/offlinepage.js',
    '/images/logo/logo152.png'
  ].concat(offlineURL),
  installFilesDesirable = [
    '/favicon.ico',
    '/images/logo/logo016.png',
    '/images/hero/power-pv.jpg',
    '/images/hero/power-lo.jpg',
    '/images/hero/power-hi.jpg'
  ];

installStaticFiles()方法添加文件到緩存纵隔,這個方法用到了基于 promise的 Cache API。當(dāng)必要的文件都被緩存后才會生成返回值。

// install static assets
function installStaticFiles() {

  return caches.open(CACHE)
    .then(cache => {

      // cache desirable files
      cache.addAll(installFilesDesirable);

      // cache essential files
      return cache.addAll(installFilesEssential);

    });

}

最后捌刮,我們添加install的事件監(jiān)聽函數(shù)碰煌。 waitUntil方法確保所有代碼執(zhí)行完畢后,service worker 才會執(zhí)行 install绅作。執(zhí)行 installStaticFiles()方法芦圾,然后執(zhí)行 self.skipWaiting()方法使service worker進入 active狀態(tài)。

// application installation
self.addEventListener('install', event => {

  console.log('service worker: install');

  // cache core files
  event.waitUntil(
    installStaticFiles()
    .then(() => self.skipWaiting())
  );

});

Activate 事件

當(dāng) install完成后俄认, service worker 進入active狀態(tài)个少,這個事件立刻執(zhí)行。你可能不需要實現(xiàn)這個事件監(jiān)聽梭依,但是示例代碼在這里刪除老舊的無用緩存文件:

// clear old caches
function clearOldCaches() {

  return caches.keys()
    .then(keylist => {

      return Promise.all(
        keylist
          .filter(key => key !== CACHE)
          .map(key => caches.delete(key))
      );

    });

}

// application activated
self.addEventListener('activate', event => {

  console.log('service worker: activate');

    // delete old caches
  event.waitUntil(
    clearOldCaches()
    .then(() => self.clients.claim())
    );

});

注意稍算,最后的self.clients.claim()方法設(shè)置本身為active的service worker。

Fetch 事件

當(dāng)有網(wǎng)絡(luò)請求時這個事件被觸發(fā)役拴。它調(diào)用respondWith()方法來劫持 GET 請求并返回:

  1. 緩存中的一個靜態(tài)資源糊探。

  2. 如果 #1 失敗了,就用 Fetch API(這與 service worker 的fetch 事件沒關(guān)系)去網(wǎng)絡(luò)請求這個資源河闰。然后將這個資源加入緩存科平。

  3. 如果 #1 和 #2 都失敗了,那就返回一個適當(dāng)?shù)闹怠?/p>

// application fetch network data
self.addEventListener('fetch', event => {

  // abandon non-GET requests
  if (event.request.method !== 'GET') return;

  let url = event.request.url;

  event.respondWith(

    caches.open(CACHE)
      .then(cache => {

        return cache.match(event.request)
          .then(response => {

            if (response) {
              // return cached file
              console.log('cache fetch: ' + url);
              return response;
            }

            // make network request
            return fetch(event.request)
              .then(newreq => {

                console.log('network fetch: ' + url);
                if (newreq.ok) cache.put(event.request, newreq.clone());
                return newreq;

              })
              // app is offline
              .catch(() => offlineAsset(url));

          });

      })

  );

});

最后這個offlineAsset(url)方法通過幾個輔助函數(shù)返回一個適當(dāng)?shù)闹担?/p>

// is image URL?
let iExt = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp'].map(f => '.' + f);
function isImage(url) {

  return iExt.reduce((ret, ext) => ret || url.endsWith(ext), false);

}


// return offline asset
function offlineAsset(url) {

  if (isImage(url)) {

    // return image
    return new Response(
      '<svg role="img" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg"><title>offline</title><path d="M0 0h400v300H0z" fill="#eee" /><text x="200" y="150" text-anchor="middle" dominant-baseline="middle" font-family="sans-serif" font-size="50" fill="#ccc">offline</text></svg>',
      { headers: {
        'Content-Type': 'image/svg+xml',
        'Cache-Control': 'no-store'
      }}
    );

  }
  else {

    // return page
    return caches.match(offlineURL);

  }

}

offlineAsset()方法檢查是否是一個圖片請求姜性,如果是瞪慧,那么返回一個帶有 “offline” 字樣的 SVG。如果不是部念,返回 offlineURL 頁面弃酌。

開發(fā)者工具提供了查看 Service Worker 相關(guān)信息的選項:

在開發(fā)者工具的 Cache Storage 選項列出了所有當(dāng)前域內(nèi)的緩存和所包含的靜態(tài)文件。當(dāng)緩存更新的時候儡炼,你可以點擊左下角的刷新按鈕來更新緩存:

不出意料妓湘, Clear storage 選項可以刪除你的 service worker 和緩存:

再來一步 - 第四步:創(chuàng)建一個可用的離線頁面

離線頁面可以是一個靜態(tài)頁面,來說明當(dāng)前用戶請求不可用乌询。然而榜贴,我們也可以在這個頁面上列出可以訪問的頁面鏈接。

main.js中我們可以使用 Cache API 妹田。然而API 使用promises唬党,在不支持的瀏覽器中會引起所有javascript運行阻塞。為了避免這種情況鬼佣,我們在加載另一個 /js/offlinepage.js 文件之前必須檢查離線文件列表和是否支持 Cache API 驶拱。

// load script to populate offline page list
if (document.getElementById('cachedpagelist') && 'caches' in window) {
  var scr = document.createElement('script');
  scr.src = '/js/offlinepage.js';
  scr.async = 1;
  document.head.appendChild(scr);
}

/js/offlinepage.js locates the most recent cache by version name, 取到所有 URL的key的列表,移除所有無用 URL沮趣,排序所有的列表并且把他們加到 ID 為cachedpagelist的 DOM 節(jié)點中:

// cache name
const
  CACHE = '::PWAsite',
  offlineURL = '/offline/',
  list = document.getElementById('cachedpagelist');

// fetch all caches
window.caches.keys()
  .then(cacheList => {

    // find caches by and order by most recent
    cacheList = cacheList
      .filter(cName => cName.includes(CACHE))
      .sort((a, b) => a - b);

    // open first cache
    caches.open(cacheList[0])
      .then(cache => {

        // fetch cached pages
        cache.keys()
          .then(reqList => {

            let frag = document.createDocumentFragment();

            reqList
              .map(req => req.url)
              .filter(req => (req.endsWith('/') || req.endsWith('.html')) && !req.endsWith(offlineURL))
              .sort()
              .forEach(req => {
                let
                  li = document.createElement('li'),
                  a = li.appendChild(document.createElement('a'));
                  a.setAttribute('href', req);
                  a.textContent = a.pathname;
                  frag.appendChild(li);
              });

            if (list) list.appendChild(frag);

          });

      })

  });

開發(fā)工具

如果你覺得 javascript 調(diào)試?yán)щy屯烦,那么 service worker 也不會很好。Chrome的開發(fā)者工具的 Application 提供了一系列調(diào)試工具房铭。

你應(yīng)該打開 隱身窗口 來測試你的 app驻龟,這樣在你關(guān)閉這個窗口之后緩存文件就不會保存下來柏蘑。

最后罚舱,Lighthouse extension for Chrome 提供了很多改進 PWA 的有用信息妓肢。

PWA 陷阱

有幾點需要注意:

URL 隱藏

我們的示例代碼隱藏了 URL 欄镐牺,我不推薦這種做法洼冻,除非你有一個單 url 應(yīng)用尖阔,比如一個游戲寡具。對于多數(shù)網(wǎng)站读宙,manifest 選項 display: minimal-ui 或者 display: browser是最好的選擇砂心。

緩存太多

你可以緩存你網(wǎng)站的所有頁面和所有靜態(tài)文件懈词。這對于一個小網(wǎng)站是可行的,但這對于上千個頁面的大型網(wǎng)站實際嗎辩诞?沒有人會對你網(wǎng)站的所有內(nèi)容都感興趣坎弯,而設(shè)備的內(nèi)存容量將是一個限制。即使你像示例代碼一樣只緩存訪問過的頁面和文件译暂,緩存大小也會增長的很快抠忘。

也許你需要注意:

  • 只緩存重要的頁面,類似主頁外永,和最近的文章崎脉。
  • 不要緩存圖片,視頻和其他大型文件
  • 經(jīng)常刪除舊的緩存文件
  • 提供一個緩存按鈕給用戶伯顶,讓用戶決定是否緩存

緩存刷新

在示例代碼中囚灼,用戶在請求網(wǎng)絡(luò)前先檢查該文件是否緩存。如果緩存祭衩,就使用緩存文件灶体。這在離線情況下很棒,但也意味著在聯(lián)網(wǎng)情況下汪厨,用戶得到的可能不是最新數(shù)據(jù)赃春。

靜態(tài)文件,類似于圖片和視頻等劫乱,不會經(jīng)常改變的資源织中,做長時間緩存沒有很大的問題。你可以在HTTP 頭里設(shè)置 Cache-Control 來緩存文件使其緩存時間為一年(31,536,000 seconds):

Cache-Control: max-age=31536000

頁面衷戈,CSS和 script 文件會經(jīng)常變化狭吼,所以你應(yīng)該改設(shè)置一個很短的緩存時間比如 24 小時,并在聯(lián)網(wǎng)時與服務(wù)端文件進行驗證:

Cache-Control: must-revalidate, max-age=86400

譯自 Retrofit Your Website as a Progressive Web App

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末殖妇,一起剝皮案震驚了整個濱河市刁笙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖疲吸,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件座每,死亡現(xiàn)場離奇詭異,居然都是意外死亡摘悴,警方通過查閱死者的電腦和手機峭梳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蹂喻,“玉大人葱椭,你說我怎么就攤上這事】谒模” “怎么了孵运?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蔓彩。 經(jīng)常有香客問我治笨,道長,這世上最難降的妖魔是什么粪小? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任大磺,我火速辦了婚禮,結(jié)果婚禮上探膊,老公的妹妹穿的比我還像新娘杠愧。我一直安慰自己,他們只是感情好逞壁,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布流济。 她就那樣靜靜地躺著,像睡著了一般腌闯。 火紅的嫁衣襯著肌膚如雪绳瘟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天姿骏,我揣著相機與錄音糖声,去河邊找鬼。 笑死分瘦,一個胖子當(dāng)著我的面吹牛蘸泻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嘲玫,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悦施,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了去团?” 一聲冷哼從身側(cè)響起抡诞,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤穷蛹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后昼汗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肴熏,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年乔遮,在試婚紗的時候發(fā)現(xiàn)自己被綠了扮超。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片取刃。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡蹋肮,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出璧疗,到底是詐尸還是另有隱情坯辩,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布崩侠,位于F島的核電站漆魔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏却音。R本人自食惡果不足惜改抡,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望系瓢。 院中可真熱鬧阿纤,春花似錦、人聲如沸夷陋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽骗绕。三九已至藐窄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酬土,已是汗流浹背荆忍。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留撤缴,地道東北人刹枉。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像腹泌,于是被迫代替她去往敵國和親嘶卧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

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