從零開始,手摸手搭建前端組件庫

MI-vant組件庫

打造一個內部的組件庫,在我們進行代碼的重構,以及開發新的功能的時候,抽離公共的組件,減少代碼的復用,注重業務與組件的分離,簡化耦合度,便于開發與維護。

預覽地址

https://majunchang.github.io/mi.vant/#/quickStart

https://majunchang.github.io/mi.vant.storybook/?path=/story/mivantbutton--with-text

特性

  • 支持按需引入
  • 預覽模式
    • storybook預覽模式
    • 純markdown預覽模式
  • rem適配
  • 支持主題定制
  • 較為完善的使用文檔和示例

技術選型

最初的時候 考慮過使用vue-cli3.0 vue-loader15+webpack4的配置 后來考慮到穩定性 暫時放棄

  • 使用babel7的插件和配置
  • 使用less作為項目中css的預處理語言
  • 增加rollup的打包方式
  • 引入storybook 來支持項目的預覽功能
  • 引入vue-loader15
  • 引入vue-markdown-loader等相關插件 支持文檔功能

babel7

為什么要升級到babel7

全局配置 babel.config.js 里的配置默認對整個項目生效,包括node_modules。除非通過 exclude 配置進行剔除。換句話來說babel7擁有全局配置能力。是前端走向未來語法的一大步,改造為babel7 的時候,遇到了很多難以解決的問題。但是最終還是堅持下來了。

升級注意事項
  1. 從 babel7 開始,所有的官方插件和主要模塊,都放在了 @babel 的命名空間下,從而可以避免在 npm 倉庫中 babel 相關名稱被搶注的問題

  2. Babel7 是對整個項目都生效的配置。

  3. 移除了之前的stage-x插件,廢棄babel-preset-es201x插件,

  4. 官方升級工具:babel-upgrade

    之前配置的時候,不知道有這個工具,導致走了很多彎路。大家以后在做某個東西的時候,一定要先查查有沒有工具。避免重復造輪子的同時,也可以避免很多不必要的錯誤)。

  5. 優化代碼與使用jsV8補丁做效能調校,編譯速度更快。

  6. webpack中babel-loader的版本不低于@babel/core的版本,否則編譯會報錯

以vue-cli 2.9.6版 的版本舉??,默認是.babelrc。

.babelrc中的配置和相關的依賴

{
  "presets": [
    ["env", {
      "modules": false,
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
      }
    }],
    "stage-2",
  ],
  "plugins": [
    "transform-vue-jsx",
    "transform-runtime"
  ],
}

  • package.json中的配置
    "babel-helper-vue-jsx-merge-props": "^2.0.3",
    "babel-loader": "^7.1.1",
    "babel-plugin-syntax-jsx": "^6.18.0",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-plugin-transform-runtime": "^6.22.0",
    "babel-plugin-transform-vue-jsx": "^3.5.0",
    "babel-preset-env": "^1.3.2",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-stage-2": "^6.22.0",
    "@babel/core": "^7.5.5",

mivant中最終版的babel.config.js中的配置和相關依賴

module.exports = function (api) {
  api.cache(true);

  const presets = [
    "@babel/preset-env",
    "@vue/babel-preset-app",
    [
      '@vue/babel-preset-jsx',
      {
        functional: false
      }
    ]];
  const plugins = [
    "@babel/plugin-transform-runtime",
    '@babel/plugin-transform-object-assign',
    ['import', {
      libraryName: 'vant',
      libraryDirectory: 'es',
      style: true
    }, 'vant']];

  return {
    presets,
    plugins
  };
}

參考資料

Babel7 發布

babelrc和babel.config.js 的區別

升級至babel7

babel7的簡單升級指南

一文讀懂 babel7 的配置文件加載邏輯

Babel快速上手使用指南

babel官網

組件全部加載與按需加載

組件是如何被加載?

解讀vue.use源碼

   Object.keys(components).forEach((key)=>{
     Vue.component(components[key].name,components[key])
   })

附index.js中的代碼

  • 引入相關的組件
  • 提供 公共的install方法
  • 通過export default 實現全部加載,通過export 的方式實現按需加載
import MiButton from './Button/index'
import Modal from './Modal/index'


const components = [
  MiButton,
  Modal
]

const version = '1.0.0'

const install = function (Vue) {
  if (install.installed) return
  components.forEach(item => {
    Vue.component(item.name, item)
  })
}


if (typeof window !== 'undefined' && window.Vue) {
  console.log('運行環境為window');
  install(window.Vue)
}

export {
  MiButton,
  Modal,
  install
}

export default {
  install,
  version
}

按需加載的第一種方式
// 組件中內置了單個組件所需的樣式  無需配置babel-plugin-import
 import { MiButton, Modal } from 'miVant'
 import Vue from 'vue'

 Vue.use(MiButton)
 Vue.use(Modal)

babel-pluhin-import

按需加載的第二種方式
import MiButton from 'miVant/lib/Button'
import Modal from 'miVant/lib/Modal'
import Vue from 'vue'

 Vue.use(MiButton)
 Vue.use(Modal)

按需架加載的基礎

  • 組件中的index.js中引入相關的vue文件,提供install方法
  • XX.vue文件中 引入less文件,內置less
  • 打包的時候對于compont下的文件使用CopyWebpackPlugin復制到lib目錄下,也就是第二種按需加載的方式

less的使用

  • utils中配置less-loader 注意loader的解析規則

附錄一段less使用的示例

@hd: 1px; // 基本單位

// 支付寶錢包默認主題
// https://github.com/ant-design/ant-design-mobile/wiki/設計變量表及命名規范

// 色彩
// ---
// 文字色
@color-text-base: #000;                  // 基本
@color-text-base-inverse: #fff;          // 基本 - 反色
@color-text-secondary: #a4a9b0;          // 輔助色
@color-text-placeholder: #bbb;           // 文本框提示
@color-text-disabled: #bbb;              // 失效
@color-text-caption: #888;               // 輔助描述
@color-text-paragraph: #333;             // 段落
@color-link: @brand-primary;             // 鏈接


@defaultColor: #455a64;
@hoverColor:#1989fa;
@height: 60px;

.navTitle{
  font-size:16px;
  font-weight:600;
  cursor: default;
}
.navItem {
  color: @defaultColor;
  font: 14px/24px PingFang SC;
  padding: 10px 10px 10px 50px;
  text-align: left;
  cursor: pointer;
}


.doc-nav-title,
.doc-comp-title{
  .navItem();
  .navTitle();
}

.doc-nav-item{
  .navItem()
}

.doc-comp-item{
  .navItem()
}

引入storyBook 預覽功能

首先,storyBook是啥?
  1. Storybook是一個輔助UI控件開發的工具。通過story創建獨立的控件,讓每個控件開發都有一個獨立的開發調試環境,可以單獨的查看每個組件的不同狀態,以及交互式開發和測試組件。
  2. Storybook的運行不依賴于項目,開發人員不用擔心由于開發環境、依賴問題導致不能開發控件。
  3. Storybook支持很多主流的框架(React、Vue、Angular)。
  4. 2019年1月份,storybook 發布5.0版本,是自項目開始以來的第一次重大調整。改進了整個生態系統的視圖層,插件和集成
安裝使用
  1. 安裝參考指南storybook for vue

  2. 自定義的webpack配置,解決擴展名問題和less編譯問題

    // 自定義webpack配置
    const path = require('path');
    
    
    module.exports = async ({ config, env }) => {
    
      // Extend it as you need.
      function resolve(dir) {
        return path.join(__dirname, '..', dir);
      }
    
      config.resolve = {
        extensions: ['.js', '.vue', '.json', '.jsx'],
        alias: {
          'vue$': 'vue/dist/vue.esm.js',
          '@': resolve('src')
        },
      }
      config.module.rules.push({
        test: /\.stories.jsx?$/,
        loaders: [require.resolve('@storybook/addon-storysource/loader')],
        enforce: 'pre',
      });
      config.module.rules.push(
        {
          test: /\.(css|less)$/,
          use: [{
            loader: 'style-loader', // creates style nodes from JS strings
          }, {
            loader: 'css-loader',// translates CSS into CommonJS
          },
          { loader: 'postcss-loader' },
          {
            loader: 'less-loader',
            options: {
              javascriptEnabled: true
            } // compiles Less to CSS
          }],
          exclude: /node_modules/
        })
    
    
      return config;
    };
    
    
  3. storybook 5.0 使用vue-loader15,默認使用babelrc進行解析和編譯,需要自定義babelrc

    {
      "presets": [
        "@babel/preset-env",
        "@vue/babel-preset-app",
        [
          "@vue/babel-preset-jsx",
          {
            "functional": false
          }
        ]
      ]
    }
    
    
如何為組件配置Storybook環境
  • stories目錄下 新建 xx.js文件,此處映射為預覽環境中的 左側預覽目錄

  • xx.js文件中 引入vue組件,編寫測試案例。通過addDecorator函數引入 插件的相關功能

    import { storiesOf } from '@storybook/vue';
    import { withKnobs } from '@storybook/addon-knobs';
    import miVantButton from '../src/components/Button/Button.vue'
    import { withStorySource } from '@storybook/addon-storysource'
    import buttonText from '../docs/button.md'
    
    const simpleSourceCode = '<mi-vant-button>storyBook</mi-vant-button>'
    storiesOf('miVantButton', module)
      .addDecorator(withKnobs)
      .addDecorator(withStorySource(simpleSourceCode))
      .addParameters({
        readme: {
          sidebar: buttonText,
        },
      })
      .add('with text', () => {
        return {
          components: { miVantButton },
          template: `<mi-vant-button>storyBook</mi-vant-button>`,
        }
      },
        {
          notes: {
            markdown: buttonText
          }
        }
      )
    
  • 根目錄下的.storybook文件夾中

    • addons.js 中 注冊相關的插件

    • config.js中 配置允許環境,安裝全局插件。類似于vue項目的main.js

      import { configure, addDecorator, addParameters } from '@storybook/vue';
      import { withNotes } from '@storybook/addon-notes'
      import { addReadme } from 'storybook-readme/vue';
      import { setOptions } from '@storybook/addon-options'
      import { Button } from 'vant'
      import Vue from 'vue'
      
      Vue.use(Button)
      
      
      const req = require.context('../stories', true, /\.js$/)
      
      function loadStories() {
        req.keys().forEach((filename) => req(filename))
      }
      
      setOptions({
        name: 'mi-Vant',
        url: '#',
        goFullScreen: false,
        showStoriesPanel: true,
        showAddonPanel: true,
        showSearchBox: true,
        addonPanelInRight: true,
        sortStoriesByKind: false,
        hierarchySeparator: null,
        hierarchyRootSeparator: null,
        sidebarAnimations: true,
        selectedAddonPanel: undefined,
      })
      
      addParameters({
        viewport: { defaultViewport: 'galaxys5' },
      })
      addDecorator(addReadme);
      addDecorator(withNotes)
      // require
      configure(loadStories, module);
      
      

遇到的問題

  • vue-loader的版本使用問題

    • 新版默認支持vue-loader15 而項目中vue-loader是13.3.0。 當時以為vue-loader15 是要搭配webpack4 一起使用的 所以降低了一下storybook的版本

    • 低版本的storybook 默認使用babel6 只能解析.babelrc 且需要自定義webpack的配置 所以只能使用storybook中提供的自定義babel和webpack配置

    • 基礎設置都配置好了,在引入插件的時候 發現插件不能用.......... 不知名的報錯 讓人很蛋疼………..,會提示一個語法錯誤。而實際上我們配置的babel中已經解析了 但是 它還是會報錯。。。。 猜測與插件版本有關

      ReferenceError:  regeneratorRuntime is not defined
      
![image.png](https://upload-images.jianshu.io/upload_images/5703029-75b5cf4521ce4f48.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
  • 按需引入vant庫的時候 報了一個css-loader的錯誤

    解決辦法:增加exclude

     config.module.rules.push(
        {
          test: /\.(css|less)$/,
          use: [{
            loader: 'style-loader', // creates style nodes from JS strings
          }, {
            loader: 'css-loader',// translates CSS into CommonJS
          },
          { loader: 'postcss-loader' },
          {
            loader: 'less-loader',
            options: {
              javascriptEnabled: true
            } // compiles Less to CSS
          }],
          exclude: /node_modules/
        })
    
  • 最終選擇了 目前的穩定版,更改了相關的配置 并引入相關的插件
相關的插件
插件名稱 功能 備注
@storybook/addon-notes 組件中添加notes,裝飾story 注釋文本信息
@storybook/addon-actions 展示event數據
@storybook/addon-backgrounds 改變頁面的背景色
@storybook/addon-storysource 展示組件源碼
@storybook/addon-knobs 動態展示props
storybook-readme 將markdown導入為story
@storybook/addon-viewport/register 增加移動端預覽模式
@storybook/addon-options 配置面板選型

相關文檔

vue-loader升級方案

Storybook 3.2 引入 Vue.js 支持

storybook for vue 官網

@storybook/vue npm

Storybook 5.0 正式發布:有史以來變化最大的版本

引入Rollup打包

介紹

Rollup 是一個 JavaScript 模塊打包器,可以將小塊代碼編譯成大塊復雜的代碼,例如 library 或應用程序。采用 es6 原生的模塊機制進行模塊的打包構建, 編譯之后包 體積會更小。

更多詳情 可以看我之前的一篇文章

rollup的初識

引入之后的問題
  • Cross-env報錯的問題
sudo npm install --global cross-env
  • Rollup 打包 ,如果使用babel.config.js+babel7的話,坑比較多…….有時候會出現一些不知名的錯誤

建議想嘗試的同學 使用babel6 + babelrc這樣的配置

https://chenshenhai.github.io/rollupjs-note/note/chapter00/01.html

  • rollup-plugin-vue在低版本0.68的時候,會報一個找不到input入口的錯,目前項目中的rollup版本是V1.19.3

附rollup.config.prod.js

import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import buble from 'rollup-plugin-buble'
import replace from 'rollup-plugin-replace';
import { uglify } from 'rollup-plugin-uglify';
import vue from 'rollup-plugin-vue'
import postcss from 'rollup-plugin-postcss';
const path = require('path');

const ENV = process.env.NODE_ENV;
const resolveFile = function (filePath) {
  return path.join(__dirname, './', filePath)
}


export default {
  input: resolveFile('src/components/index.js'),
  output: {
    dir: 'es',
    format: 'umd',
    name: 'miVant',
    exports: 'named',
  },
  plugins: [
    resolve({ extensions: ['.js', '.vue'] }),
    postcss({
      extensions: ['.less', '.css'],
      use: [
        ['less', {
          javascriptEnabled: true
        }]
      ],
      extract: true,
      minimize: true,
    }),
    vue({
      template: {
        isProduction: true
      },
      css: false
    }),
    commonjs(),
    buble({
      objectAssign: 'Object.assign'
    }),
    replace({
      exclude: 'node_modules/**',
      ENV: JSON.stringify(process.env.NODE_ENV),
    }),
    (ENV === 'production' && uglify()),
  ],
};

留一個問題: rollup完成按需加載的打包

vue的markdown解析

I want

  1. 將組件中的readme文檔改造為組件的使用文檔
  2. 類似于目前知名組件庫如 antd,element-ui,vant等支持代碼庫高亮顯示,組件動態展示等效果,簡而言之一句話: 能夠在md中運行代碼。
  3. 讓我們的組件庫看起來不那么low??..........

由于之前沒有接觸過類似的功能,于是漫漫的調研之路開始了。。。。

  • Vue-press vue作者開發的 仿照vue的風格 適合靜態文檔 卻不能很好的展示預覽效果vuePress中文文檔
    • 類似于hexo 想搭建個人博客的同學可以用一下
  • vue-markdown-loader 搭配 vue-loader可以實現動態編譯md文檔
    • 搭配vue-loader15版本的時候 需要注意采用兼容寫法
    • 搭配markdown-it系列能夠良好的擴展md
    • 需要注意的是 Vue-markdown-loader在搭配vue-loader15的時候 loader的寫法要注意下
    • 使用highlight.js的主題 支持主題的動態配置

附webpack中關于markdown的解析規則

{
        test: /\.md$/,
        use: [
          {
            loader: 'vue-loader'
          },
          {
            loader: 'vue-markdown-loader/lib/markdown-compiler',
            options: {
              raw: true,
              preventExtract: true,
              use: [
                [
                  require('markdown-it-container'),
                  'demo',
                  {
                    validate: function (params) {
                      return params.trim().match(/^demo\s+(.*)$/)
                    },
                    render: function (tokens, idx) {
                      if (tokens[idx].nesting === 1) {
                        // 1.獲取第一行的內容使用markdown渲染html作為組件的描述
                        let demoInfo = tokens[idx].info.trim().match(/^demo\s+(.*)$/)
                        let description = demoInfo && demoInfo.length > 1 ? demoInfo[1] : ''
                        let descriptionHTML = description ? markdownRender.render(description) : ''
                        // 2.獲取代碼塊內的html和js代碼
                        let content = tokens[idx + 1].content
                        // 3.使用自定義開發組件【DemoBlock】來包裹內容并且渲染成案例和代碼示例
                        return `<demo-block>
                        <div class="source" slot="source">${content}</div>
                        ${descriptionHTML}
                        <div class="highlight" slot="highlight">`
                      } else {
                        return '</div></demo-block>\n'
                      }
                    }
                  }
                ]
              ]
            }
          }
        ]
      },

demo-block中手動補充copy功能

<template>
  <div class="demo-block">
    <div class="demo-block-source">
      <slot name="source"></slot>
      <span class="demo-block-code-icon" v-if="!$slots.default" @click="showCode=!showCode">
        <img
          alt="expand code"
          src="https://gw.alipayobjects.com/zos/rmsportal/wSAkBuJFbdxsosKKpqyq.svg"
          class="code-expand-icon-show"
        />
      </span>
    </div>
    <div class="demo-block-meta" v-if="$slots.default">
      <slot></slot>
      <span v-if="$slots.default" class="demo-block-code-icon" @click="showCode=!showCode">
        <img
          alt="expand code"
          src="https://gw.alipayobjects.com/zos/rmsportal/wSAkBuJFbdxsosKKpqyq.svg"
          class="code-expand-icon-show"
        />
      </span>
    </div>
    <div class="demo-block-code" v-show="showCode">
      <p class="copy" @click="copy">復制</p>
      <slot name="highlight"></slot>
    </div>
  </div>
</template>
<script type="text/babel">
export default {
  data() {
    return {
      showCode: false
    };
  },
  methods: {
    copy(e) {
      const hightext = e.target.nextElementSibling;
      const input = document.createElement("input");
      document.body.appendChild(input);
      let value = hightext.innerText;
      input.value = value;
      input.select();
      if (document.execCommand("copy")) {
        document.execCommand("copy");
        console.log("復制成功");
      }
      document.body.removeChild(input);
    }
  }
};
</script>
<style lang='less'>
@import "./less/demo-block.less";
.copy {
  cursor: pointer;
  position: absolute;
  right: 10px;
  top: 0;
}
</style>

相關文檔

VuePress 手摸手教你搭建一個類Vue文檔風格的技術文檔/博客

從 Vue-cli 開始構建 UI 庫到 Markdown 生成文檔和演示案例

vue-markdown-loader

markdown-it-container

vue-markdown-loader error with vue Loader 15

rem的適配+定制主題

rem的適配功能

  • 通過postcss-px2rem將px單位自動轉化為rem單位

  • 通過項目根目錄下的.postcssrc.js 設置轉化規則

    // https://github.com/michael-ciniawsky/postcss-load-config
    
    module.exports = {
      "plugins": {
        "postcss-import": {},
        "postcss-url": {},
        // to edit target browsers: use "browserslist" field in package.json
        "autoprefixer": {
          browsers: ['Android >= 4.0', 'iOS >= 7']
        },
        "postcss-px2rem": { remUnit: 100 }
      }
    }
    
    
  • css中補充對應的font-size大小

    @import './var.less';
    
    html {
      font-size: 100px; /* no */
    }
    h1{
      font-size: 32px;
    }
    h2{
      font-size: 24px;
    }
    h3{
      font-size: 19px;
    }
    h4{
      font-size: 16px;
    }
    h5{
      font-size: 14px;
    }
    h6{
      font-size: 13px;
    }
    
    li,p,th,td {
      font-size: 16px;
    }
    
    

定制主題

miVant 使用了 Less 對樣式進行預處理,并內置了一些樣式變量,通過替換樣式變量即可定制你自己需要的主題。

配置文件: ~/src/components/less/var.less

@primary-btn-color :#fbb212;

定制方法

  1. 使用 less 提供的 modifyVars 即可對變量進行修改,下面是參考的 webpack 配置。
  2. 這里以vue2.x版本的腳手架舉例 /build/utils目錄下
exports.cssLoaders = function (options) {
  options = options || {}

  const lessLoader = {
    loader: 'less-loader',
    options: {
      sourceMap: options.sourceMap,
      modifyVars: {
        color: 'red'
      }
    }
  }
  const lessConfig = {
    modifyVars: {
      primary-btn-color: 'red'
    }
  };


  const cssLoader = {
    loader: 'css-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  const postcssLoader = {
    loader: 'postcss-loader',
    options: {
      sourceMap: options.sourceMap
    }
  }

  // generate loader string to be used with extract text plugin
  function generateLoaders(loader, loaderOptions) {
    const loaders = options.usePostCSS ? [cssLoader, postcssLoader, lessLoader] : [cssLoader, lessLoader]

    if (loader) {
      loaders.push({
        loader: loader + '-loader',
        options: Object.assign({}, loaderOptions, {
          sourceMap: options.sourceMap
        })
      })
    }

    // Extract CSS when that option is specified
    // (which is the case during production build)
    if (options.extract) {
      return ExtractTextPlugin.extract({
        use: loaders,
        fallback: 'vue-style-loader'
      })
    } else {
      return ['vue-style-loader'].concat(loaders)
    }
  }

  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
  return {
    css: generateLoaders(),
    postcss: generateLoaders(),
    less: generateLoaders('less', lessConfig),
    sass: generateLoaders('sass', { indentedSyntax: true }),
    scss: generateLoaders('sass'),
    stylus: generateLoaders('stylus'),
    styl: generateLoaders('stylus')
  }
}

項目啟動和運行

// 克隆項目到本地
git clone  https://github.com/majunchang/mi-vant.git
// 切換到master分支
// 安裝相關依賴
 npm  i

//  npm 腳本
// storybook 預覽模式
 "start": "npm run storybook",
 "storybook": "start-storybook -p 9001 -c .storybook",
// 打包storybook靜態文件
 "build-storybook": "build-storybook -c .storybook",
// 文檔預覽 端口是8081
 "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
// 打包之后的依賴包分析
 "bundle-report": "webpack-bundle-analyzer --port 8123 dist/stats.json",
// webpack build
 "build": "node build/build.js",
// rollup 編譯
 "clean": "rimraf rollupDist",
 "rollup": "cross-env NODE_ENV=production rollup --config=rollup.config.prod.js"
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,034評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,327評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,337評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,883評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,114評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,625評論 1 332
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,555評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,737評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,973評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,343評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,699評論 2 370

推薦閱讀更多精彩內容