學習筆記-開發(fā)腳手架及封裝自動化構(gòu)建工作流

腳手架工具

常用的腳手架工具:

  • React.js 項目 - create-react-app
  • Vue.js 項目 - vue-cli
  • Angular 項目 - angular-cli
  • Yeoman
  • Plop

Yeoman

Yeoman 是一款比較通用的腳手架,可以搭配不同的 generator 生成不同的腳手架。

基礎使用

  • 在全局范圍安裝 yo
    yarn global add yo
  • 安裝對應的 generator扁藕,這里安裝的是 node 對應的 generator
    yarn global add generator-node
  • 生成項目結(jié)構(gòu)
mkdir my-module
cd my-module
yo node
yarn

Sub Generator

有的時候我們并不需要創(chuàng)建完整的項目結(jié)構(gòu),只是需要在已有項目基礎上創(chuàng)建特定文件吮旅,比如 readme,eslint等味咳,這些文件都有特定的格式庇勃,如果自己手動寫很容易寫錯,可以通過生成器自動生成槽驶,這個時候可以使用 Yeoman 提供的 Sub Generator 來實現(xiàn)责嚷。

具體就是通過運行 Sub Generator 的命令來實現(xiàn)。我們這里可以用 generator-node 里邊所提供的的一個子集的生成器 cli 幫我們生成一個 cli 應用所需要的的文件掂铐。

運行 sub generator 的方式就是在原有的 generator 名字后面跟上 :罕拂,跟上 sub generator 的名字,在這里就是 node:cli

yo node:cli

將模塊作為全局的命令行模塊去使用

yarn link

運行my-module --help

這就是 Generator 的子集 Sub Generator 的特性全陨。

并不是每個 Generator 都提供子集的生成器爆班,可以通過 Generator 的官方文檔查看它有哪些自己生成器。

常規(guī)使用步驟

  1. 明確你的需求
  2. 找到合適的 Generator
  3. 全局范圍安裝找到的 Generator
  4. 通過 Yo 運行對應的 Generator
  5. 通過命令行交互填寫選項
  6. 生成你所需要的項目結(jié)構(gòu)

自定義 Generator

Generator 本質(zhì)上就是一個NPM模塊辱姨。

簡單實現(xiàn)

  1. 首先創(chuàng)建生成器文件夾柿菩,初始化 package.json 文件
mkdir generator-sample
cd generator-sample
yarn init

注意: yarn init 在 git bash 不生效,可以在 cmd 中執(zhí)行雨涛。

image.png
  1. 安裝依賴 yeoman-generator@4.0.1
yarn add yeoman-generator@4.0.1
  1. 在根目錄下創(chuàng)建文件 generators/app/index.js
image.png
  1. 在 index.js 文件中實現(xiàn)一個文件寫入的功能
// 此文件作為 Generator 的核心入口
// 需要導出一個繼承自 Yeoman Generator 的類型
// Yeoman Generator 在工作時會自動調(diào)用在此類型中定義的一些生命周期方法
// 我們在這些方法中可以通過調(diào)用父類提供的一些工具方法實現(xiàn)一些功能枢舶,比如文件寫入

const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  write() {
    // Yeoman 在生成文件時調(diào)用此方法
    // 這里給項目中寫入一個文件
    this.fs.write(
      this.destinationPath('tmp.txt'),
      Math.random().toString()
    )
  }
}
  1. 使用 yarn link 將這個模塊鏈接到全局范圍,使它成為一個全局模塊包镜悉。
  2. 創(chuàng)建一個新的項目 my-proj,在 my-proj 中運行 yo sample 就會在 my-proj 項目下生成一個 tmp.txt 文件。

根據(jù)模板創(chuàng)建文件

在 app 文件夾下創(chuàng)建 templates 目錄医瘫,將要生成的文件都放入 templates 目錄作為模板侣肄,模板中是完全遵循 EJS 語法。

image.png

這個時候我們寫入文件就不需要借助 fs 的 write 方法了醇份,而是借助 fs 的一個專門使用模板引擎的方法 copyTpl稼锅。

image.png
// 模板文件路徑
const tmpl = this.templatePath('foo.txt')
// 輸出目標路徑
const output = this.destinationPath('foo.txt')
// 模板數(shù)據(jù)上下文
const context = { title: 'Hello wl~', success: false }

this.fs.copyTpl(tmpl, output, context)

然后進入命令行吼具,在 my-proj 項目下運行 yo sample,會在項目下生成文件 foo.txt矩距。

image.png

相對于手動創(chuàng)建每一個文件拗盒,模板的方式大大提高了效率。

接受用戶輸入數(shù)據(jù)

對于項目的動態(tài)數(shù)據(jù)锥债,例如標題陡蝇,名稱,這樣的數(shù)據(jù)一般通過命令行交互的方式詢問我們的使用者從而得到哮肚。 在 Generator 中想要實現(xiàn)命令行交互可以用 Generator 中的 prompting 方法登夫。

image.png
image.png
// 此文件作為 Generator 的核心入口
// 需要導出一個繼承自 Yeoman Generator 的類型
// Yeoman Generator 在工作時會自動調(diào)用在此類型中定義的一些生命周期方法
// 我們在這些方法中可以通過調(diào)用父類提供的一些工具方法實現(xiàn)一些功能,比如文件寫入

const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  prompting() {
    // Yeoman 在詢問用戶環(huán)節(jié)會自動調(diào)用此方法
    // 在此方法中可以調(diào)用父類的 prompt() 方法發(fā)出對用戶的命令行詢問
    // this.prompt 方法接收一個數(shù)組作為參數(shù)允趟,數(shù)組的每一項都是一個問題對象
    // 這個問題對象中 type 表示用什么方式接受信息恼策, name 為得到結(jié)果的鍵
    // message 是給用戶的提示,也就是問題潮剪,default: this.appname 拿到的是當前生成項目的文件夾的名字
    // answers 就是拿到的結(jié)果涣楷,鍵就是數(shù)組中每一項設置的 name,值就是用戶的輸入抗碰。
    // 將結(jié)果掛載到 this.answers 上狮斗,以便后續(xù)使用。
    return this.prompt([
      {
        type: 'input',
        name: 'projectName',
        message: 'Your project name',
        default: this.appname // appname 為項目生成目錄名稱
      }
    ])
      .then(answers => {
        // answers => { projectName: 'user input value'}
        this.answers = answers
      })
  }
  write() {
    // Yeoman 自動在生成文件時調(diào)用此方法
    // 這里給項目中寫入一個文件
    // this.fs.write(
    //   this.destinationPath('tmp.txt'),
    //   Math.random().toString()
    // )


    // 通過模板方式寫入文件到目標目錄
    // 模板文件路徑
    const tmpl = this.templatePath('bar.html')
    // 輸出目標路徑
    const output = this.destinationPath('bar.html')
    // 模板數(shù)據(jù)上下文
    const context = this.answers
    this.fs.copyTpl(tmpl, output, context)
  }
}

然后在 my-proj 項目下執(zhí)行 yo sample改含,就創(chuàng)建了 bar.html 文件情龄,并且拿到了項目名稱。

image.png
image.png

Vue Generator 案例

mkdir generator-vue-wl
cd generator-vue-wl
yarn init
yarn add yeoman-generator@4.0.1

在項目中添加文件 generators/app/index.js

在項目中添加文件夾 templates捍壤,將準備好的 vue 的模板放入這個文件夾中骤视,將模板中名字的不用使用<%= name %>替代,eg:

image.png

在 index.js 文件中寫入代碼:

const Generator = require('yeoman-generator')

module.exports = class extends Generator {
  prompting() {
    return this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Your project name',
        default: this.appname
      }
    ])
      .then(answers => {
        this.answers = answers
      })
  }
  writing() {
    // 把每一個文件都通過模板轉(zhuǎn)換到目標路徑

    const templates = [
      '.browserslistrc',
      '.editorconfig',
      '.env.development',
      '.env.production',
      '.eslintrc.js',
      '.gitignore',
      'babel.config.js',
      'package.json',
      'postcss.config.js',
      'README.md',
      'public/favicon.ico',
      'public/index.html',
      'src/App.vue',
      'src/main.js',
      'src/router.js',
      'src/assets/logo.png',
      'src/components/HelloWorld.vue',
      'src/store/actions.js',
      'src/store/getters.js',
      'src/store/index.js',
      'src/store/mutations.js',
      'src/store/state.js',
      'src/utils/request.js',
      'src/views/About.vue',
      'src/views/Home.vue'
    ]

    // 通過遍歷所有的文件路徑鹃觉,為每個模板生成對應文件
    templates.forEach(item => {
      // item => 每個文件路徑
      this.fs.copyTpl(
        this.templatePath(item),
        this.destinationPath(item),
        this.answer
      )
    })
  }
}

在命令行執(zhí)行 yarn link专酗。然后定位到全新的目錄,創(chuàng)建新項目盗扇。

mkdir vue-demo
cd vue-demo
yo vue-wl

發(fā)布 Generator

發(fā)布 Generator 就是發(fā)布一個公開的 npm祷肯。

  1. 將自己的 Generator 項目提交到遠程倉庫
  2. 在項目下使用 npm publishyarn publish

Plop

除了 Yeoman 這樣大型的腳手架工具外,還有一些小型的腳手架工具也很好用疗隶,比如 Plop佑笋。它是用于在項目中創(chuàng)建特定類型文件的小工具,類似于 Yeoman 中的 Sub Generator斑鼻。不過它一般不會獨立去使用蒋纬,都是集成到項目中,用來自動化的創(chuàng)建同類型的項目文件。

步驟:

  • 將 plop 模塊作為項目開發(fā)依賴安裝
  • 在項目根目錄下創(chuàng)建一個 plopfile.js 文件
  • 在 plopfile.js 文件中定義腳手架任務
  • 編寫用于生成特定類型文件的模板
  • 通過 Plop 提供的 CLI 運行腳手架任務

demo:https://gitee.com/sun_wl/plop-demo.git

通過 nodejs 開發(fā)一個小型腳手架

腳手架工具就是一個 node cli 應用蜀备。

mkdir node-scaffold
cd node-scaffold
yarn init

在 package.json 中添加一個 bin 字段关摇,用于指定 cli 應用的入口文件,然后創(chuàng)建這個文件碾阁。

image.png

自動化構(gòu)建

常見自動化構(gòu)建工具介紹

  • Grunt 最早的前端構(gòu)建系統(tǒng)输虱,插件生態(tài)非常完善。但是工作過程基于臨時文件脂凶,所以構(gòu)建速度相對較慢宪睹。例如使用它區(qū)完成項目中 sass 文件的構(gòu)建,先對文件進行編譯艰猬,再去自動添加一些私有屬性的前綴横堡,最后壓縮代碼。每一步都會有磁盤操作冠桃,sass 文件編譯完成命贴,會將結(jié)果寫入一個臨時文件,然后下一個插件在讀取臨時文件進行下一步食听,處理的環(huán)節(jié)越多胸蛛,讀取磁盤次數(shù)越多,大型項目就會特別慢樱报。
  • Gulp 很好的解決了Grunt 構(gòu)建速度慢的問題葬项,它處理文件都是在內(nèi)存中完成的,相對于磁盤讀寫快了很多迹蛤,而且它默認支持同時執(zhí)行多個任務民珍,使用方式也簡單易懂,生態(tài)系統(tǒng)也很多盗飒,是目前市面上最流行的前端構(gòu)建系統(tǒng)嚷量。
  • FIS 百度開源的構(gòu)建系統(tǒng)。相對于前兩個微內(nèi)核的特點逆趣,F(xiàn)IS更像捆綁套餐蝶溶,把項目中典型的需求都集成在內(nèi)部了。

webpack 是一個模塊打包工具宣渗。

Grunt

首先在項目中安裝 grunt 依賴抖所,在根目錄創(chuàng)建 gruntfile.js 文件:

// Grunt 的入口文件
// 用于定義一些需要 Grunt 自動執(zhí)行的任務
// 需要導出一個函數(shù)
// 此函數(shù)接受一個 grunt 的形參,內(nèi)部提供一些創(chuàng)建任務時可以用到的 API

module.exports = grunt => {
  // registerTask: 用于注冊任務
  // 注冊一個 foo 任務
  // 使用 yarn grunt foo 執(zhí)行
  grunt.registerTask('foo', () => {
    console.log('hello grunt~')
  })

  // 第二個參數(shù)如果是字符串痕囱,就是這個任務的描述
  // 可以通過 yarn grunt --help 看到描述信息
  grunt.registerTask('bar', '任務描述', () => {
    console.log('other task~')
  })

  // 如果在構(gòu)建任務的邏輯代碼中發(fā)生錯誤田轧,例如文件找不見,可以將任務標記為失敗的任務
  // 實現(xiàn)方式是在函數(shù)中 return false
  grunt.registerTask('bad', () => {
    console.log('bad working')
    return false
  })

  // 執(zhí)行 yarn grunt 自動調(diào)用 default
  // grunt.registerTask('default', () => {
  //   console.log('default task')
  // })

  // default 任務的第二個參數(shù)可以傳入一個數(shù)組作為映射
  // 自動執(zhí)行數(shù)組中的任務
  // grunt.registerTask('default', ['foo', 'bar'])

  // 如果失敗的任務在任務列表中鞍恢,會導致后續(xù)的任務不會再執(zhí)行
  // 如果想要失敗任務后的任務仍然能夠執(zhí)行傻粘,可以執(zhí)行 yarn grunt --force
  grunt.registerTask('default', ['foo', 'bad', 'bar'])

  // grunt 默認支持同步模式巷查,所以這里的 log 不會被打印
  // grunt.registerTask('async-task', () => {
  //   setTimeout(() => {
  //     console.log('async task working')
  //   }, 1000)
  // })

  // 想要執(zhí)行異步任務,需要使用 this.async() 得到一個回調(diào)函數(shù)
  // 在異步操作完成后調(diào)用這個回調(diào)函數(shù)抹腿,標識一下這個任務已經(jīng)完成
  // 要使用 this 就不能使用箭頭函數(shù)了
  grunt.registerTask('async-task', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('async task working')
      done()
    }, 1000)
  })

  // 異步任務無法通過 return false 標記失敗,可以通過給回調(diào)函數(shù)傳遞一個 false
  grunt.registerTask('bad-async', function () {
    const done = this.async()
    setTimeout(() => {
      console.log('bad-async')
      done(false)
    }, 1000)
  })





  // initConfig:用于添加配置選項的 API
  // 例如grunt需要對文件進行壓縮時旭寿,可以通過這個配置需要壓縮的文件路徑
  // 接受一個參數(shù)為對象警绩,對象的鍵和任務的名稱保持一致,值可以是任意類型的數(shù)據(jù)
  // grunt.config() 可以獲取配置的值
  // 值也可以是個對象盅称,在使用 grunt.config() 獲取時使用 . 的形式
  grunt.initConfig({
    wl: 'bar'
    // wl:{
    //   bar:123
    // }
  })
  grunt.registerTask('wl', () => {
    console.log(grunt.config('wl'))  // bar
    // console.log(grunt.config('wl.bar')) // 123
  })



  // 多目標任務
  // 使用 grunt.registerMultiTask 注冊多目標任務
  // 多目標任務必須通過 initConfig 來配置這個任務和它的多目標
  // 執(zhí)行 yarn grunt build 會執(zhí)行 build 中配置的多個任務
  // 要運行指定目標可以通過 yarn grunt build:js 來執(zhí)行
  // 在任務的函數(shù)中可以通過 this.target 拿到任務的目標肩祥,通過 this.data 拿到數(shù)據(jù)
  // 在 build 中指定的屬性的每一個鍵都會成為一個目標,除了 options
  // options 是任務的配置選項缩膝,可以在任務執(zhí)行的函數(shù)中通過 this.options() 拿到
  // 在子目標中也可以配置 options 選項混狠,會覆蓋對象中的 options
  grunt.initConfig({
    build: {
      options: {
        foo: 'bar'
      },
      css: {
        options: {
          foo: 'baz'
        }
      },
      // css: 1,
      js: 2
    }
  })

  // registerMultiTask: 多目標任務
  grunt.registerMultiTask('build', function () {
    console.log('options', this.options())
    console.log(`target:${this.target}, data:${this.data}`)
  })







  // 插件的使用
  // eg: grunt-contrib-clean   用來清除臨時文件
  // 通過 grunt.loadNPmTasks() 方法加載插件中提供的任務
  // grunt 的插件的命名方式都是 `grunt-contrib-${taskName}`
  // 直接運行 yarn grunt clean 發(fā)現(xiàn)報錯提示 No "clean" targets found.
  // 說明 clean 任務是一個多目標任務,需要配置 initConfig
  // grunt.loadNpmTasks('grunt-contrib-clean')

  // 給 clean 任務配置一個 temp 任務疾层,值為需要清除的文件目錄
  // 可以先創(chuàng)建一個這樣的文件用來測試
  // 執(zhí)行 yarn grunt clean 后将饺,會發(fā)現(xiàn)這個文件被清除了
  // temp 文件路徑可以使用通配符
  grunt.initConfig({
    clean: {
      // temp: 'temp/app.js'
      // temp: 'temp/*.txt'
      temp: 'temp/**'   // temp/** 表示 temp 下的所有文件
    }
  })
  grunt.loadNpmTasks('grunt-contrib-clean')





  // 常用插件
  // 隨著引入模塊越多,loadNpmTasks 的使用也會越多
  // 有一個依賴痛黎,可以減少 loadNpmTasks 的使用:load-grunt-tasks
  const loadGruntTasks = require('load-grunt-tasks')
  // yarn add grunt-sass sass --dev
  // yarn add grunt-babel @babel/core @babel/preset-env --dev
  // yarn add grunt-contrib-watch --dev
  const sass = require('sass')
  grunt.initConfig({
    sass: {
      options: {
        sourceMap: true,
        // 用來指定使用哪個模塊來進行 sass 的編譯
        implementation: sass
      },
      main: {
        files: {
          // 輸出文件路徑: 輸入文件源路徑
          'dist/css/main.css': 'src/sass/main.scss'
        }
      }
    },
    babel: {
      options: {
        sourceMap: true,
        presets: ['@babel/preset-env']  // 將最新的 ECMAScript 加載進來
      },
      main: {
        files: {
          'dist/js/app.js': 'src/js/app.js'
        }
      }
    },
    watch: {
      js: {
        files: ['src/js/*.js'],
        tasks: ['babel']
      },
      css: {
        files: ['src/scss/*.scss'],
        tasks: ['sass']
      }
    }
  })
  // grunt.loadNpmTasks('grunt-sass')
  // 自動加載所有的 grunt 插件中的任務
  loadGruntTasks(grunt)

  // watch 是監(jiān)聽文件變化才會執(zhí)行 babel 和 sass予弧,第一次變異并不會執(zhí)行
  // 所以需要給任務做個映射,在啟動的時候先執(zhí)行一次
  grunt.registerTask('default', ['babel', 'sass'])




}

Gulp

基本使用

安裝依賴: yarn add gulp --dev

在根目錄創(chuàng)建 gulpfile.js 文件:

// gulp 的入口文件

// 基本使用
// 因為文件運行在 nodejs 環(huán)境中湖饱,可以使用 commonJS 的規(guī)范
// 定義構(gòu)建任務的方式就是通過導出函數(shù)成員的方式
// 在最新的 gulp 中取消了同步代碼模式掖蛤,每個任務都必須是異步任務
// 任務執(zhí)行完成需要通過調(diào)用回調(diào)函數(shù)來標記完成
// 函數(shù)的形參就是一個函數(shù),調(diào)用這個形參的函數(shù)就是標識任務結(jié)束
// 執(zhí)行 yarn gulp foo
exports.foo = done => {
  console.log('foo')
  done() // 標識任務完成
}

// 默認任務  yarn gulp
exports.default = done => {
  console.log('default')
  done()
}


// 以下是在 gulp@4.0 以前注冊 gulp 任務的方法
// 執(zhí)行 yarn gulp bar 可以看到結(jié)果
// gulp@4.0 以后的版本保留了之前的使用方式井厌,但是不推薦使用了
const gulp = require('gulp')
gulp.task('bar', done => {
  console.log('bar')
  done()
})





// 組合任務:并行任務和串行任務
const { series, parallel } = require('gulp')

const task1 = done => {
  setTimeout(() => {
    console.log('task1')
    done()
  }, 1000)
}
const task2 = done => {
  setTimeout(() => {
    console.log('task2')
    done()
  }, 1000)
}
const task3 = done => {
  setTimeout(() => {
    console.log('task3')
    done()
  }, 1000)
}
// series: 串行任務
// 執(zhí)行 yarn gulp foo 會發(fā)現(xiàn)三個任務按照順序依次執(zhí)行
exports.foo = series(task1, task2, task3)

// parallel: 并行任務
// 執(zhí)行 yarn gulp bar 會發(fā)現(xiàn)三個任務同時啟動
exports.bar = parallel(task1, task2, task3)







// 異步任務  通知外部任務完成的方式:
// 通過回調(diào)的方式解決
// 錯誤優(yōu)先蚓庭,如果有多個任務,其中一個錯誤仅仆,后邊的就不會執(zhí)行了
exports.callback = done => {
  console.log('callback task')
  done()
}

exports.callback_error = done => {
  console.log('callback task')
  done(new Error('task failed'))
}

// Promise 的方式器赞,避免了回調(diào)地獄
exports.promise = () => {
  console.log('promise task')
  return Promise.resolve()
}

exports.promise_error = () => {
  console.log('promise')
  return Promise.reject(new Error('task failed'))
}

const timeout = time => {
  return new Promise(resolve => {
    setTimeout(resolve, time)
  })
}
// async await 的方式
exports.async = async () => {
  await timeout(1000)
  console.log('async task')
}

// stream 的方式最為常見,任務函數(shù)中返回一個 stream 對象
const fs = require('fs')
exports.stream = () => {
  const readStream = fs.createReadStream('package.json') // 讀取文件的文件流
  const writeStream = fs.createWriteStream('temp.txt')  // 寫入文件的文件流
  readStream.pipe(writeStream)  // 將 readStream 導到 writeStream 中
  return readStream // 返回 readStream 的文件流蝇恶,相當于在 readStream 的 end 事件中執(zhí)行結(jié)束任務
}

// exports.stream = done => {
//   const readStream = fs.createReadStream('package.json')
//   const writeStream = fs.createWriteStream('temp.txt')
//   readStream.pipe(writeStream)
//   readStream.on('end', () => {
//     done()
//   })
// }

通過 node 底層API 實現(xiàn)構(gòu)建過程工作原理

const fs = require('fs')
const { Transform } = require('stream')

exports.default = () => {
  // 文件讀取流
  const read = fs.createReadStream('normalize.css')
  // 文件寫入流
  const write = fs.createWriteStream('normalize.min.css')
  // 文件轉(zhuǎn)換流
  const transform = new Transform({
    transform: (chunk, encoding, callback) => {
      // 轉(zhuǎn)換流的核心轉(zhuǎn)換過程
      // chunk => 讀取流中讀取到的內(nèi)容
      const input = chunk.toString()
      const output = input.replace(/\s+/g, '').replace('/\/\*.+?\*\//g', '')
      callback(null, output)  // 將 output 通過回調(diào)函數(shù)返回出去拳魁,第一個參數(shù)為錯誤參數(shù),沒有錯誤傳 null
    }
  })
  // 把讀取出來的文件流導入寫入文件流
  read
    .pipe(transform)  // 轉(zhuǎn)換
    .pipe(write)      // 寫入

  return read

}

Gulp 文件操作 API

Gulp 也提供了文件讀取和寫入的 API撮弧,相比于 node 底層的 API 更容易理解和使用潘懊。文件轉(zhuǎn)換是通過插件來完成的。

// yarn add gulp-clean-css --dev
// yarn add gulp-rename --dev
const { src, dest } = require('gulp')
const cleanCss = require('gulp-clean-css')
const rename = require('gulp-rename')

// 通過 src 創(chuàng)建文件讀取流
// 通過 pipe 導出到 dest 創(chuàng)建的寫入流贿衍,寫入流只需要指定目錄
exports.default = () => {
  return src('src/*.css')
    .pipe(cleanCss())    // 先經(jīng)過轉(zhuǎn)換
    .pipe(rename({ extname: '.min.css' }))  // 重命名
    .pipe(dest('dist'))
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末授舟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贸辈,更是在濱河造成了極大的恐慌释树,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奢啥,居然都是意外死亡秸仙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門桩盲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寂纪,“玉大人,你說我怎么就攤上這事赌结±痰埃” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵柬姚,是天一觀的道長拟杉。 經(jīng)常有香客問我,道長量承,這世上最難降的妖魔是什么搬设? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮撕捍,結(jié)果婚禮上焕梅,老公的妹妹穿的比我還像新娘。我一直安慰自己卦洽,他們只是感情好贞言,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阀蒂,像睡著了一般该窗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蚤霞,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天酗失,我揣著相機與錄音,去河邊找鬼昧绣。 笑死规肴,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的夜畴。 我是一名探鬼主播拖刃,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼贪绘!你這毒婦竟也來了兑牡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤税灌,失蹤者是張志新(化名)和其女友劉穎均函,沒想到半個月后亿虽,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡苞也,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年洛勉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片如迟。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡坯认,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氓涣,到底是詐尸還是另有隱情,我是刑警寧澤陋气,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布劳吠,位于F島的核電站,受9級特大地震影響巩趁,放射性物質(zhì)發(fā)生泄漏痒玩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一议慰、第九天 我趴在偏房一處隱蔽的房頂上張望蠢古。 院中可真熱鬧,春花似錦别凹、人聲如沸草讶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽堕战。三九已至,卻和暖如春拍霜,著一層夾襖步出監(jiān)牢的瞬間嘱丢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工祠饺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留越驻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓道偷,卻偏偏與公主長得像缀旁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子勺鸦,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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