幾種常見(jiàn)webpack-loader源碼淺析蝎亚,以及動(dòng)手實(shí)現(xiàn)一個(gè)md2html-loader

本文會(huì)帶你簡(jiǎn)單的認(rèn)識(shí)一下webpack的loader澄暮,動(dòng)手實(shí)現(xiàn)一個(gè)利用md轉(zhuǎn)成抽象語(yǔ)法樹名段,再轉(zhuǎn)成html字符串的loader阱扬。順便簡(jiǎn)單的了解一下幾個(gè)style-loader,vue-loader伸辟,babel-loader的源碼以及工作流程价认。

### loader簡(jiǎn)介

webpack允許我們使用loader來(lái)處理文件,loader是一個(gè)導(dǎo)出為function的node模塊自娩。可以將匹配到的文件進(jìn)行一次轉(zhuǎn)換渠退,同時(shí)loader可以鏈?zhǔn)絺鬟f忙迁。

loader文件處理器是一個(gè)CommonJs風(fēng)格的函數(shù),該函數(shù)接收一個(gè) String/Buffer 類型的入?yún)⑺槟耍⒎祷匾粋€(gè) String/Buffer 類型的返回值姊扔。

### loader 的配置的兩種形式

方案1:

```js

// webpack.config.js

module.exports = {

? ...

? module: {

? ? rules: [{

? ? ? test: /.vue$/,

? ? ? loader: 'vue-loader'

? ? }, {

? ? ? test: /.scss$/,

? ? ? // 先經(jīng)過(guò) sass-loader,然后將結(jié)果傳入 css-loader梅誓,最后再進(jìn)入 style-loader恰梢。

? ? ? use: [

? ? ? ? 'style-loader',//從JS字符串創(chuàng)建樣式節(jié)點(diǎn)

? ? ? ? 'css-loader',// 把? CSS 翻譯成 CommonJS

? ? ? ? {

? ? ? ? ? loader: 'sass-loader',

? ? ? ? ? options: {

? ? ? ? ? ? data: '$color: red;'// 把 Sass 編譯成 CSS

? ? ? ? ? }

? ? ? ? }

? ? ? ]

? ? }]

? }

? ...

}

```

方法2(右到左地被調(diào)用)

```js

// module

import Styles from 'style-loader!css-loader?modules!./styles.css';

```

當(dāng)鏈?zhǔn)秸{(diào)用多個(gè) loader 的時(shí)候,請(qǐng)記住它們會(huì)以相反的順序執(zhí)行梗掰。取決于數(shù)組寫法格式嵌言,從右向左或者從下向上執(zhí)行。像流水線一樣及穗,挨個(gè)處理每個(gè)loader摧茴,前一個(gè)loader的結(jié)果會(huì)傳遞給下一個(gè)loader,最后的 Loader 將處理后的結(jié)果以 String 或 Buffer 的形式返回給 compiler埂陆。

### 使用 loader-utils 能夠編譯 loader 的配置苛白,還可以通過(guò) schema-utils 進(jìn)行驗(yàn)證

```js

import { getOptions } from 'loader-utils';

import { validateOptions } from 'schema-utils';?

const schema = {

? // ...

}

export default function(content) {

? // 獲取 options

? const options = getOptions(this);

? // 檢驗(yàn)loader的options是否合法

? validateOptions(schema, options, 'Demo Loader');

? // 在這里寫轉(zhuǎn)換 loader 的邏輯

? // ...

? return content;?

};

```

- content: 表示源文件字符串或者buffer

- map: 表示sourcemap對(duì)象

- meta: 表示元數(shù)據(jù),輔助對(duì)象

### 同步loader

同步 loader焚虱,我們可以通過(guò)`return`和`this.callback`返回輸出的內(nèi)容

```js

module.exports = function(content, map, meta) {

? //一些同步操作

? outputContent=someSyncOperation(content)

? return outputContent;

}

```

如果返回結(jié)果只有一個(gè)购裙,也可以直接使用 return 返回結(jié)果。但是鹃栽,如果有些情況下還需要返回其他內(nèi)容躏率,如sourceMap或是AST語(yǔ)法樹,這個(gè)時(shí)候可以借助webpack提供的api `this.callback`

```js

module.exports = function(content, map, meta) {

? this.callback(

? ? err: Error | null,

? ? content: string | Buffer,

? ? sourceMap?: SourceMap,

? ? meta?: any

? );

? return;

}

```

第一個(gè)參數(shù)必須是 Error 或者 null

第二個(gè)參數(shù)是一個(gè) string 或者 Buffer谍咆。

可選的:第三個(gè)參數(shù)必須是一個(gè)可以被這個(gè)模塊解析的 source map禾锤。

可選的:第四個(gè)選項(xiàng),會(huì)被 webpack 忽略摹察,可以是任何東西【可以將抽象語(yǔ)法樹(abstract syntax tree - AST)(例如 ESTree)作為第四個(gè)參數(shù)(meta)恩掷,如果你想在多個(gè) loader 之間共享通用的 AST,這樣做有助于加速編譯時(shí)間供嚎』颇铮】峭状。

### 異步loader

異步loader,使用 this.async 來(lái)獲取 callback 函數(shù)逼争。

```js

// 讓 Loader 緩存

module.exports = function(source) {

? ? var callback = this.async();

? ? // 做異步的事

? ? doSomeAsyncOperation(content, function(err, result) {

? ? ? ? if(err) return callback(err);

? ? ? ? callback(null, result);

? ? });

};

```

詳情請(qǐng)參考[官網(wǎng)API](https://www.webpackjs.com/api/loaders/#%E5%90%8C%E6%AD%A5-loader)

### 開發(fā)一個(gè)簡(jiǎn)單的md-loader

```js

const marked = require("marked");

const loaderUtils = require("loader-utils");

module.exports = function (content) {

? ? this.cacheable && this.cacheable();

? ? const options = loaderUtils.getOptions(this);

? ? try {

? ? ? ? marked.setOptions(options);

? ? ? ? return marked(content)

? ? } catch (err) {

? ? ? ? this.emitError(err);

? ? ? ? return null

? ? }


};

```

上述的例子是通過(guò)現(xiàn)成的插件把markdown文件里的content轉(zhuǎn)成html字符串优床,但是如果沒(méi)有這個(gè)插件,改怎么做呢誓焦?這個(gè)情況下胆敞,我們可以考慮另外一種解法,借助 AST 語(yǔ)法樹杂伟,來(lái)協(xié)助我們更加便捷地操作轉(zhuǎn)換移层。

### 利用 AST 作源碼轉(zhuǎn)換

`markdown-ast`是將markdown文件里的content轉(zhuǎn)成數(shù)組形式的抽象語(yǔ)法樹節(jié)點(diǎn),操作 AST 語(yǔ)法樹遠(yuǎn)比操作字符串要簡(jiǎn)單赫粥、方便得多:

```js

const md = require('markdown-ast');//通過(guò)正則的方法把字符串處理成直觀的AST語(yǔ)法樹

module.exports = function(content) {

? ? this.cacheable && this.cacheable();

? ? const options = loaderUtils.getOptions(this);

? ? try {

? ? ? console.log(md(content))

? ? ? const parser = new MdParser(content);

? ? ? return parser.data

? ? } catch (err) {

? ? ? console.log(err)

? ? ? return null

? ? }

};

```

**md通過(guò)正則切割的方法轉(zhuǎn)成抽象語(yǔ)樹**

![md-ast](https://upload-images.jianshu.io/upload_images/14736553-73e0e42325ccb1de?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

```js

const md = require('markdown-ast');//md通過(guò)正則匹配的方法把buffer轉(zhuǎn)抽象語(yǔ)法樹

const hljs = require('highlight.js');//代碼高亮插件

// 利用 AST 作源碼轉(zhuǎn)換

class MdParser {

constructor(content) {

? ? this.data = md(content);

? ? console.log(this.data)

this.parse()

}

parse() {

this.data = this.traverse(this.data);

}

traverse(ast) {

? ? console.log("md轉(zhuǎn)抽象語(yǔ)法樹操作",ast)

? ? let body = '';

? ? ast.map(item => {

? ? ? switch (item.type) {

? ? ? ? case "bold":

? ? ? ? case "break":

? ? ? ? case "codeBlock":

? ? ? ? ? const highlightedCode = hljs.highlight(item.syntax, item.code).value

? ? ? ? ? body += highlightedCode

? ? ? ? ? break;

? ? ? ? case "codeSpan":

? ? ? ? case "image":

? ? ? ? case "italic":

? ? ? ? case "link":

? ? ? ? case "list":

? ? ? ? ? item.type = (item.bullet === '-') ? 'ul' : 'ol'

? ? ? ? ? if (item.type !== '-') {

? ? ? ? ? ? item.startatt = (` start=${item.indent.length}`)

? ? ? ? ? } else {

? ? ? ? ? ? item.startatt = ''

? ? ? ? ? }

? ? ? ? ? body += '<' + item.type + item.startatt + '>\n' + this.traverse(item.block) + '</' + item.type + '>\n'

? ? ? ? ? break;

? ? ? ? case "quote":

? ? ? ? ? let quoteString = this.traverse(item.block)

? ? ? ? ? body += '<blockquote>\n' + quoteString + '</blockquote>\n';

? ? ? ? ? break;

? ? ? ? case "strike":

? ? ? ? case "text":

? ? ? ? case "title":

? ? ? ? ? body += `<h${item.rank}>${item.text}</h${item.rank}>`

? ? ? ? ? break;

? ? ? ? default:

? ? ? ? ? throw Error("error", `No corresponding treatment when item.type equal${item.type}`);

? ? ? }

? ? })

? ? return body

}

}

```

[完整的代碼參考這里](https://github.com/6fedcom/fe-blog/blob/master/webpack-loader/loaders/md-loader.js)

**ast抽象語(yǔ)法數(shù)轉(zhuǎn)成html字符串**

![md-ast-string](https://upload-images.jianshu.io/upload_images/14736553-b2ef58e8af3a577e?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

### loader的一些開發(fā)技巧

1. 盡量保證一個(gè)loader去做一件事情观话,然后可以用不同的loader組合不同的場(chǎng)景需求

2. 開發(fā)的時(shí)候不應(yīng)該在 loader 中保留狀態(tài)。loader必須是一個(gè)無(wú)任何副作用的純函數(shù)越平,loader支持異步频蛔,因此是可以在 loader 中有 I/O 操作的。

3. 模塊化:保證 loader 是模塊化的秦叛。loader 生成模塊需要遵循和普通模塊一樣的設(shè)計(jì)原則晦溪。

4. 合理的使用緩存

合理的緩存能夠降低重復(fù)編譯帶來(lái)的成本。loader 執(zhí)行時(shí)默認(rèn)是開啟緩存的书闸,這樣一來(lái)尼变, webpack 在編譯過(guò)程中執(zhí)行到判斷是否需要重編譯 loader 實(shí)例的時(shí)候,會(huì)直接跳過(guò) rebuild 環(huán)節(jié)浆劲,節(jié)省不必要重建帶來(lái)的開銷嫌术。

但是當(dāng)且僅當(dāng)有你的 loader 有其他不穩(wěn)定的外部依賴(如 I/O 接口依賴)時(shí),可以關(guān)閉緩存:

```js

this.cacheable&&this.cacheable(false);

```

5. `loader-runner` 是一個(gè)非常實(shí)用的工具牌借,用來(lái)開發(fā)度气、調(diào)試loader,它允許你不依靠 webpack 單獨(dú)運(yùn)行 loader

```npm install loader-runner --save-dev```

```js

// 創(chuàng)建 run-loader.js

const fs = require("fs");

const path = require("path");

const { runLoaders } = require("loader-runner");

runLoaders(

? {

? ? resource: "./readme.md",

? ? loaders: [path.resolve(__dirname, "./loaders/md-loader")],

? ? readResource: fs.readFile.bind(fs),

? },

? (err, result) =>

? ? (err ? console.error(err) : console.log(result))

);

```

執(zhí)行 `node run-loader`

### 認(rèn)識(shí)更多的loader

##### style-loader源碼簡(jiǎn)析

作用:把樣式插入到DOM中,方法是在head中插入一個(gè)style標(biāo)簽膨报,并把樣式寫入到這個(gè)標(biāo)簽的 innerHTML 里

看下源碼磷籍。

先去掉option處理代碼,這樣就比較清晰明了了

![style-loader](https://upload-images.jianshu.io/upload_images/14736553-f309bf5d7f0d43bb?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

返回一段js代碼现柠,通過(guò)require來(lái)獲取css內(nèi)容院领,再通過(guò)addStyle的方法把css插入到dom里

自己實(shí)現(xiàn)一個(gè)簡(jiǎn)陋的`style-loader.js`

```js

module.exports.pitch = function (request) {

? const {stringifyRequest}=loaderUtils

? var result = [

? ? //1. 獲取css內(nèi)容。2.// 調(diào)用addStyle把CSS內(nèi)容插入到DOM中(locals為true够吩,默認(rèn)導(dǎo)出css)

? ? 'var content=require(' + stringifyRequest(this, '!!' + request) + ')’,

? ? 'require(' + stringifyRequest(this, '!' + path.join(__dirname, "addstyle.js")) + ')(content)’,

? ? 'if(content.locals) module.exports = content.locals’

? ]

? return result.join(';')

}

```

需要說(shuō)明的是比然,正常我們都會(huì)用default的方法,這里用到pitch方法周循。pitch 方法有一個(gè)官方的解釋在這里 pitching loader强法。簡(jiǎn)單的解釋一下就是万俗,默認(rèn)的loader都是從右向左執(zhí)行,用 `pitching loader` 是從左到右執(zhí)行的饮怯。

```js

{

? test: /\.css$/,

? use: [

? ? { loader: "style-loader" },

? ? { loader: "css-loader" }

? ]

}

```

為什么要先執(zhí)行`style-loader`呢闰歪,因?yàn)槲覀円裛css-loader`拿到的內(nèi)容最終輸出成CSS樣式中可以用的代碼而不是字符串。

`addstyle.js`

```js

module.exports = function (content) {

? let style = document.createElement("style")

? style.innerHTML = content

? document.head.appendChild(style)

}

```

##### babel-loader源碼簡(jiǎn)析

首先看下跳過(guò)loader的配置處理蓖墅,看下babel-loader輸出

![babel-loader-console](https://upload-images.jianshu.io/upload_images/14736553-6d8545857397a70c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

上圖我們可以看到是輸出`transpile(source, options)`的code和map

再來(lái)看下`transpile`方法做了啥

![babel-loader-transpile](https://upload-images.jianshu.io/upload_images/14736553-285e140bf12f000c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

babel-loader是通過(guò)babel.transform來(lái)實(shí)現(xiàn)對(duì)代碼的編譯的库倘,

這么看來(lái),所以我們只需要幾行代碼就可以實(shí)現(xiàn)一個(gè)簡(jiǎn)單的babel-loader

```js

const babel = require("babel-core")

module.exports = function (source) {

? const babelOptions = {

? ? presets: ['env']

? }

? return babel.transform(source, babelOptions).code

}

```

##### vue-loader源碼簡(jiǎn)析

vue單文件組件(簡(jiǎn)稱sfc)

```vue

<template>

? <div class="text">

? ? {{a}}

? </div>

</template>

<script>

export default {

? data () {

? ? return {

? ? ? a: "vue demo"

? ? };

? }

};

</script>

<style lang="scss" scope>

.text {

? color: red;

}

</style>

```

webpack配置

```js

const VueloaderPlugin = require('vue-loader/lib/plugin')

module.exports = {

? ...

? module: {

? ? rules: [

? ? ? ...

? ? ? {

? ? ? ? test: /\.vue$/,

? ? ? ? loader: 'vue-loader'

? ? ? }

? ? ]

? }

? plugins: [

? ? new VueloaderPlugin()

? ]

? ...

}

```

**VueLoaderPlugin**

作用:將在webpack.config定義過(guò)的其它規(guī)則復(fù)制并應(yīng)用到 .vue 文件里相應(yīng)語(yǔ)言的塊中论矾。

`plugin-webpack4.js`

```js

const vueLoaderUse = vueUse[vueLoaderUseIndex]

? ? vueLoaderUse.ident = 'vue-loader-options'

? ? vueLoaderUse.options = vueLoaderUse.options || {}

? ? // cloneRule會(huì)修改原始rule的resource和resourceQuery配置于樟,

? ? // 攜帶特殊query的文件路徑將被應(yīng)用對(duì)應(yīng)rule

? ? const clonedRules = rules

? ? ? .filter(r => r !== vueRule)

? ? ? .map(cloneRule)

? ? // global pitcher (responsible for injecting template compiler loader & CSS

? ? // post loader)

? ? const pitcher = {

? ? ? loader: require.resolve('./loaders/pitcher'),

? ? ? resourceQuery: query => {

? ? ? ? const parsed = qs.parse(query.slice(1))

? ? ? ? return parsed.vue != null

? ? ? },

? ? ? options: {

? ? ? ? cacheDirectory: vueLoaderUse.options.cacheDirectory,

? ? ? ? cacheIdentifier: vueLoaderUse.options.cacheIdentifier

? ? ? }

? ? }

? ? // 更新webpack的rules配置,這樣vue單文件中的各個(gè)標(biāo)簽可以應(yīng)用clonedRules相關(guān)的配置

? ? compiler.options.module.rules = [

? ? ? pitcher,

? ? ? ...clonedRules,

? ? ? ...rules

? ? ]

```

獲取`webpack.config.js`的rules項(xiàng)拇囊,然后復(fù)制rules九府,為攜帶了`?vue&lang=xx...query`參數(shù)的文件依賴配置xx后綴文件同樣的loader

為Vue文件配置一個(gè)公共的loader:pitcher

將`[pitchLoder, ...clonedRules, ...rules]`作為webapck新的rules逝钥。

再看一下`vue-loader`結(jié)果的輸出

![vue-loader-result](https://upload-images.jianshu.io/upload_images/14736553-f06107b6f8f78862?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

當(dāng)引入一個(gè)vue文件后,vue-loader是將vue單文件組件進(jìn)行parse撒顿,獲取每個(gè) block 的相關(guān)內(nèi)容关霸,將不同類型的 block 組件的 Vue SFC 轉(zhuǎn)化成 js module 字符串传黄。

```js

// vue-loader使用`@vue/component-compiler-utils`將SFC源碼解析成SFC描述符,,根據(jù)不同 module path 的類型(query 參數(shù)上的 type 字段)來(lái)抽離 SFC 當(dāng)中不同類型的 block。

const { parse } = require('@vue/component-compiler-utils')

// 將單個(gè)*.vue文件內(nèi)容解析成一個(gè)descriptor對(duì)象队寇,也稱為SFC(Single-File Components)對(duì)象

// descriptor包含template膘掰、script、style等標(biāo)簽的屬性和內(nèi)容佳遣,方便為每種標(biāo)簽做對(duì)應(yīng)處理

const descriptor = parse({

? source,

? compiler: options.compiler || loadTemplateCompiler(loaderContext),

? filename,

? sourceRoot,

? needMap: sourceMap

})

// 為單文件組件生成唯一哈希id

const id = hash(

? isProduction

? ? (shortFilePath + '\n' + source)

? : shortFilePath

)

// 如果某個(gè)style標(biāo)簽包含scoped屬性识埋,則需要進(jìn)行CSS Scoped處理

const hasScoped = descriptor.styles.some(s => s.scoped)

```

然后下一步將新生成的 js module 加入到 webpack 的編譯環(huán)節(jié),即對(duì)這個(gè) js module 進(jìn)行 AST 的解析以及相關(guān)依賴的收集過(guò)程零渐。

來(lái)看下源碼是怎么操作不同type類型(`template/script/style`)的窒舟,selectBlock 方法內(nèi)部主要就是根據(jù)不同的 type 類型,來(lái)獲取 descriptor 上對(duì)應(yīng)類型的 content 內(nèi)容并傳入到下一個(gè) loader 處理

![vue-loader源碼](https://upload-images.jianshu.io/upload_images/14736553-0dfd2f79d39f085a?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

這三段代碼可以把不同type解析成一個(gè)import的字符串

```js

import { render, staticRenderFns } from "./App.vue?vue&type=template&id=7ba5bd90&"

import script from "./App.vue?vue&type=script&lang=js&"

export * from "./App.vue?vue&type=script&lang=js&"

import style0 from "./App.vue?vue&type=style&index=0&lang=scss&scope=true&"

```

**總結(jié)一下vue-loader的工作流程**

1. 注冊(cè)`VueLoaderPlugin`

在插件中诵盼,會(huì)復(fù)制當(dāng)前項(xiàng)目webpack配置中的rules項(xiàng)惠豺,當(dāng)資源路徑包含query.lang時(shí)通過(guò)resourceQuery匹配相同的rules并執(zhí)行對(duì)應(yīng)loader時(shí)

插入一個(gè)公共的loader,并在pitch階段根據(jù)query.type插入對(duì)應(yīng)的自定義loader

2. 加載*.vue時(shí)會(huì)調(diào)用`vue-loader`,.vue文件被解析成一個(gè)`descriptor`對(duì)象风宁,包含`template洁墙、script、styles`等屬性對(duì)應(yīng)各個(gè)標(biāo)簽戒财,

對(duì)于每個(gè)標(biāo)簽热监,會(huì)根據(jù)標(biāo)簽屬性拼接`src?vue&query`引用代碼,其中src為單頁(yè)面組件路徑固翰,query為一些特性的參數(shù)狼纬,比較重要的有l(wèi)ang羹呵、type和scoped

如果包含lang屬性,會(huì)匹配與該后綴相同的rules并應(yīng)用對(duì)應(yīng)的loaders

根據(jù)type執(zhí)行對(duì)應(yīng)的自定義loader疗琉,`template`將執(zhí)行`templateLoader`冈欢、`style`將執(zhí)行`stylePostLoader`

3. 在`templateLoader`中,會(huì)通過(guò)`vue-template-compiler`將template轉(zhuǎn)換為render函數(shù)盈简,在此過(guò)程中凑耻,

會(huì)將傳入的`scopeId`追加到每個(gè)標(biāo)簽的上,最后作為vnode的配置屬性傳遞給`createElemenet`方法柠贤,

在render函數(shù)調(diào)用并渲染頁(yè)面時(shí)香浩,會(huì)將`scopeId`屬性作為原始屬性渲染到頁(yè)面上

4. 在`stylePostLoader`中,通過(guò)PostCSS解析style標(biāo)簽內(nèi)容

### 參考文獻(xiàn)

1. [webpack官網(wǎng)loader api](https://www.webpackjs.com/api/loaders/)

2. [手把手教你寫webpack yaml-loader](https://mp.weixin.qq.com/s/gTAq5K5pziPT4tmiGqw5_w)

3. [言川-webpack 源碼解析系列](https://github.com/lihongxun945/diving-into-webpack)

4. [從vue-loader源碼分析CSS Scoped的實(shí)現(xiàn)](https://juejin.im/post/5d8627355188253f3a70c22c)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末臼勉,一起剝皮案震驚了整個(gè)濱河市邻吭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宴霸,老刑警劉巖囱晴,帶你破解...
    沈念sama閱讀 221,576評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瓢谢,居然都是意外死亡畸写,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門氓扛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)枯芬,“玉大人,你說(shuō)我怎么就攤上這事采郎∏” “怎么了?”我有些...
    開封第一講書人閱讀 168,017評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵蒜埋,是天一觀的道長(zhǎng)真慢。 經(jīng)常有香客問(wèn)我,道長(zhǎng)理茎,這世上最難降的妖魔是什么黑界? 我笑而不...
    開封第一講書人閱讀 59,626評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮皂林,結(jié)果婚禮上朗鸠,老公的妹妹穿的比我還像新娘。我一直安慰自己础倍,他們只是感情好烛占,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,625評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般忆家。 火紅的嫁衣襯著肌膚如雪犹菇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評(píng)論 1 308
  • 那天芽卿,我揣著相機(jī)與錄音揭芍,去河邊找鬼。 笑死卸例,一個(gè)胖子當(dāng)著我的面吹牛称杨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播筷转,決...
    沈念sama閱讀 40,825評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼姑原,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了呜舒?” 一聲冷哼從身側(cè)響起锭汛,我...
    開封第一講書人閱讀 39,729評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎袭蝗,沒(méi)想到半個(gè)月后店乐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,271評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呻袭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,363評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腺兴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片左电。...
    茶點(diǎn)故事閱讀 40,498評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖页响,靈堂內(nèi)的尸體忽然破棺而出篓足,到底是詐尸還是另有隱情,我是刑警寧澤闰蚕,帶...
    沈念sama閱讀 36,183評(píng)論 5 350
  • 正文 年R本政府宣布栈拖,位于F島的核電站,受9級(jí)特大地震影響没陡,放射性物質(zhì)發(fā)生泄漏涩哟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,867評(píng)論 3 333
  • 文/蒙蒙 一盼玄、第九天 我趴在偏房一處隱蔽的房頂上張望贴彼。 院中可真熱鬧,春花似錦埃儿、人聲如沸器仗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)精钮。三九已至威鹿,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轨香,已是汗流浹背忽你。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留弹沽,地道東北人檀夹。 一個(gè)月前我還...
    沈念sama閱讀 48,906評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像策橘,于是被迫代替她去往敵國(guó)和親炸渡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,507評(píng)論 2 359

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

  • webpack是一個(gè)模塊打包器(module bundler)丽已,提供了一個(gè)核心蚌堵,核心提供了很多開箱即用的功能,同時(shí)...
    2b61575c37fd閱讀 7,400評(píng)論 0 2
  • 確認(rèn)過(guò)眼神沛婴,你還是沒(méi)有準(zhǔn)備秋招的人吼畏?時(shí)間倉(cāng)促。自京東6月8號(hào)開啟管培生的招聘嘁灯,就意味著秋招的開始泻蚊。然而你還在等著秋...
    千鋒H5閱讀 1,820評(píng)論 1 13
  • 記得2004年的時(shí)候,互聯(lián)網(wǎng)開發(fā)就是做網(wǎng)頁(yè)丑婿,那時(shí)也沒(méi)有前端和后端的區(qū)分性雄,有時(shí)一個(gè)網(wǎng)站就是一些純靜態(tài)的html,通過(guò)...
    陽(yáng)陽(yáng)陽(yáng)一堆陽(yáng)閱讀 3,309評(píng)論 0 5
  • ## 框架和庫(kù)的區(qū)別?> 框架(framework):一套完整的軟件設(shè)計(jì)架構(gòu)和**解決方案**羹奉。> > 庫(kù)(lib...
    Rui_bdad閱讀 2,917評(píng)論 1 4
  • 下午看了意大利導(dǎo)演貝納爾多·貝托魯奇1987拍攝的傳記電影《末代皇帝》秒旋。外國(guó)人去重現(xiàn)歷史的人物和場(chǎng)景,視角和觀感的...
    王彥文WP閱讀 132評(píng)論 0 1