Webpack4及模块绑定入门手册

本文翻译自https://www.sitepoint.com/beginners-guide-webpack-module-bundling
相关代码已提交至https://github.com/qianhui123/webpack4Demo.

以下是主要内容。

本文在我们的书《现代JavaScript工具和技能》中有所介绍。 现在我们一起来熟悉支持现代JavaScript开发的基本工具。

Webpack4 文档中说到:

Webpack是一个module bundler。 它的主要目的是捆绑JavaScript文件以便在浏览器中使用,同时它也能够转换,捆绑或打包任何资源。

Webpack已成为现代Web开发最重要的工具之一。 它主要是JavaScript的module bundler,同时它可以用来转换所有前端资源,如HTML,CSS,甚至图像。 它可以让您更好地控制应用程序正在进行的HTTP请求的数量,并允许您使用其他类型的这些资源(例如,Pug,Sass和ES8)。 Webpack还允许您轻松地从npm使用包。

本文面向那些刚接触Webpack的人,将介绍初始设置和配置,模块,加载器(loaders),插件,代码拆分和热模块更换。 如果您发现视频教程很有帮助,我强烈推荐Glen Maddern的Webpack from First Principles 作为起点,了解Webpack的特殊之处。 它现在有点旧了,但原则仍然相同,这是一个很好的介绍。

要在家中进行操作,您需要安装Node.js. 您也可以从我们的GitHub仓库下载演示应用程序。

开始

让我们用npm初始化一个新项目并安装webpack和webpack-cli:

mkdir webpack-demo && cd webpack-demo
npm init -y
npm install --save-dev webpack webpack-cli

接下来我们将创建下面文件结构和内容:

webpack-demo
|- package.json
 + |- webpack.config.js
 + |- /src
 +  |- index.js
 + |- /dist
 +  |- index.html

dist/index.html

<!doctype html>
<html>
  <head>
    <title>Hello Webpack</title>
  </head>
  <body>
    <script src="bundle.js"></script>
  </body>
</html>

src/index.js

const root = document.createElement("div")
root.innerHTML = `<p>Hello Webpack.</p>`
document.body.appendChild(root)

webpack.config.js

const path = require('path')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

这告诉Webpack在我们的入口点src / index.js中编译代码并在/dist/bundle.js中输出一个bundle。 我们来添加一个用于运行Webpack的npm脚本。

package.json

 {
    ...
    "scripts": {
 -    "test": "echo \"Error: no test specified\" && exit 1",
 +    "develop": "webpack --mode development --watch",
 +    "build": "webpack --mode production"
    },
    ...
  }

使用npm run develop命令,我们可以创建我们的第一个包!

Asset      Size      Chunks           Chunk Names
bundle.js  2.92 KiB  main  [emitted]  main

您现在应该可以在浏览器中加载dist / index.html并看到“Hello Webpack”。

打开dist / bundle.js以查看Webpack的功能。 顶部是Webpack的模块引导代码,底部是我们的模块。 你可能对此还没有很深刻的印象,但是如果你已经走到这一步,你现在可以开始使用ES模块,而Webpack将能够生成一个适用于所有浏览器的生产包。

使用Ctrl + C重新编译并运行npm run build以在生产模式下编译我们的bundle。

Asset      Size       Chunks           Chunk Names
bundle.js  647 bytes  main  [emitted]  main

请注意,捆绑包大小已从2.92 KiB降至647字节。

再看一下dist / bundle.js,你会看到一堆丑陋的代码。 我们的软件包已经用UglifyJS缩小了:代码将运行完全相同,但它是以尽可能小的文件大小完成的。

  • 模式开发优化了构建速度和调试
  • 模式生产优化了运行时的执行速度和输出文件大小。

模块

使用ES Module,您可以将大型程序拆分为许多小型自包含程序。

开箱即用,Webpack知道如何使用导入和导出语句来使用ES模块。 举个例子,让我们现在通过安装lodash-es并添加第二个模块来尝试这个:

npm install --save-dev lodash-es

src/index.js

import { groupBy } from "lodash-es"
import people from "./people"

const managerGroups = groupBy(people, "manager")

const root = document.createElement("div")
root.innerHTML = `<pre>${JSON.stringify(managerGroups, null, 2)}</pre>`
document.body.appendChild(root)

src/people.js

const people = [
  {
    manager: "Jen",
    name: "Bob"
  },
  {
    manager: "Jen",
    name: "Sue"
  },
  {
    manager: "Bob",
    name: "Shirley"
  }
]

export default people

运行npm run develop启动Webpack并刷新index.html。 您应该看到按管理器分组的一组人员打印到屏幕上。

注意:导入一个像'es-lodash'这样没有相对路径的模块,是从npm安装到 node_modules的模块。 你自己的模块总是需要一个像'./people'这样的相对路径,因此你可以区分它们。

请注意,在控制台中我们的捆绑包大小已增加到1.41 MiB! 这值得关注,但在这种情况下,没有理由担心。 使用npm run build在生产模式下编译,lodash-es中的所有未使用的lodash模块都将从bundle中删除。 删除未使用的导入的过程称为tree-shaking,并且是Webpack免费获得的。

> npm run develop

Asset      Size      Chunks                  Chunk Names
bundle.js  1.41 MiB  main  [emitted]  [big]  main


> npm run build

Asset      Size      Chunks        Chunk Names
bundle.js  16.7 KiB  0  [emitted]  main

装载

加载程序允许您在导入文件时运行预处理程序。 这允许您将静态资源捆绑到JavaScript之外,但让我们看看在首先加载.js模块时可以做些什么。

让我们通过下一代JavaScript转换器Babel运行所有.js文件来保持代码的现代化:

npm install --save-dev "babel-loader@^8.0.0-beta" @babel/core @babel/preset-env

译者注:这里babel-loader版本在版本库中不存在,所以无法安装。我使用了8.0.0-beta.4可以安装。读者也可以在地址库中选择其他版本安装。附上babel-loader地址https://www.npmjs.com/package/babel-loader

webpack.config.js

const path = require('path')

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
  + module: {
  +     rules: [
  +       {
  +        test: /\.js$/,
  +        exclude: /(node_modules|bower_components)/,
  +        use: {
  +           loader: 'babel-loader',
  +         }
  +       }
  +     ]
  +   }
  }

.babelrc

{
  "presets": [
    ["@babel/env", {
      "modules": false
    }]
  ],
  "plugins": ["syntax-dynamic-import"]
}

此配置可防止Babel将导入和导出语句转换为ES5,并启用动态导入,这个我们将在后面的“代码拆分”一节中介绍。

译者注:注意这里使用了插件babel-plugin-syntax-dynamic-import,所以需要用npm安装,安装命令如下

npm install babel-plugin-syntax-dynamic-import

我们现在可以自由使用现代语言功能,它们将被编译为在所有浏览器中运行的ES5。

Sass

Loaders可以链接在一起进行一系列变换。 从我们的JavaScript导入Sass可以较好地演示它是如何工作的:

npm install --save-dev style-loader css-loader sass-loader node-sass

webpack.config.js

 module.exports = {
...
module: {
  rules: [
    ...
+          {
+            test: /\.scss$/,
+         use: [{
+           loader: 'style-loader'
+         }, {
+           loader: 'css-loader'
+         }, {
+           loader: 'sass-loader'
+         }]
+      }
+      ]
    }
  }

这些加载器以相反的顺序处理:

  • sass-loader将Sass转换为CSS。
  • css-loader将CSS解析为JavaScript并解析任何依赖项。
  • style-loader将我们的CSS输出到文档中的<style>标记中。

您可以将这些视为函数调用。 一个加载器的输出作为输入提供给下一个:

styleLoader(cssLoader(sassLoader("source")))

让我们添加一个Sass源文件,import是一个模块。

src/style.scss

$bluegrey: #2b3a42;

pre {
  padding: 8px 16px;
  background: $bluegrey;
  color: #e1e6e9;
  font-family: Menlo, Courier, monospace;
  font-size: 13px;
  line-height: 1.5;
  text-shadow: 0 1px 0 rgba(23, 31, 35, 0.5);
  border-radius: 3px;
}

src/index.js

import { groupBy } from 'lodash-es'
import people from './people'

+ import './style.scss'

  ...

使用Ctrl + C和npm run develop重新启动构建。 在浏览器中刷新index.html,您应该看到一些样式。

JS中的样式表

我们刚从JavaScript中导入了一个Sass文件作为模块。

打开dist / bundle.js并搜索“pre {”。 实际上,我们的Sass已被编译为一串CSS并在我们的包中保存为模块。 当我们将这个模块导入JavaScript时,style-loader将字符串输出到嵌入的<style>标记中。
译者注:css在bundle.js中的形式如下图所示,下图是我在bundle.js中截出来的部分图。

image.png

你为什么需要做这样的事?

我不会在这里深入研究这个主题,但这里有几个理由需要考虑:

  • 您可能希望包含在项目中的JavaScript组件可能依赖于其他资源才能正常运行(HTML,CSS,图像,SVG)。 如果这些都可以捆绑在一起,则导入和使用起来要容易得多。
  • 消除Dead code:当代码不再导入JS组件时,也不再导入CSS。 生成的包只会包含执行某些操作的代码。
  • CSS模块:CSS的全局命名空间使得很难确信对CSS的更改不会产生任何副作用。 CSS模块通过设置CSS默认本地并显示您可以在JavaScript中引用的唯一类名来解决该问题。
  • 通过巧妙的方式捆绑/拆分代码来减少HTTP请求的数量。

图片

我们将看到的最后一个加载器示例是使用文件加载器处理图像。

在标准HTML文档中,当浏览器遇到img标记或具有background-image属性的元素时,将获取图像。 使用Webpack,您可以在小图像的情况下通过将图像源作为字符串存储在JavaScript中来优化它。 通过执行此操作,您可以预加载它们,浏览器不必在以后使用单独的请求获取它们:

npm install --save-dev file-loader

webpack.config.js

module.exports = {
    ...
    module: {
      rules: [
        ...
 +      {
 +        test: /\.(png|svg|jpg|gif)$/,
 +        use: [
 +          {
 +            loader: 'file-loader'
 +          }
 +        ]
 +      }
      ]
    }
  }

用以下命令下载一个测试图像:

    curl https://raw.githubusercontent.com/sitepoint-editors/webpack-demo/master/src/code.png --output src/code.png

译者注:这里使用的是curl命令,直接在终端将网络图片下载到指定目录src/code.png。运行后如果提示"--output"有错,将其修改成"-o"可以成功。

使用Ctrl + C和npm run develop重新启动构建,您现在可以将图像作为模块导入!

src/index.js

import { groupBy } from 'lodash-es'
import people from './people'

import './style.scss'
import './image-example'

  ...

src/image-example.js

import codeURL from "./code.png"

const img = document.createElement("img")
img.src = codeURL
img.style = "background: #2B3A42; padding: 20px"
img.width = 32
document.body.appendChild(img)

这将包括一个图像,其中src属性包含图像本身的数据URI:

<img src="data:image/png;base64,iVBO..." style="background: #2B3A42; padding: 20px" width="32">

我们的CSS中的背景图像也由文件加载器处理。

src/style.scss

$bluegrey: #2b3a42;

  pre {
    padding: 8px 16px;
    background: $bluegrey;
    background: $bluegrey url("code.png") no-repeat center center / 32px 32px;
    color: #e1e6e9;
    font-family: Menlo, Courier, monospace;
    font-size: 13px;
    line-height: 1.5;
    text-shadow: 0 1px 0 rgba(23, 31, 35, 0.5);
    border-radius: 3px;
  }

在文档中查看更多Loaders示例:

依赖图 Dependency Graph

您现在应该能够看到加载器如何帮助您在资源中构建依赖关系树。 这就是Webpack主页上的图像所展示的内容。

image

尽管JavaScript是切入点,但Webpack明白您的其他资源类型(如HTML,CSS和SVG)各自具有自己的依赖关系,这应该被视为构建过程的一部分。

代码拆分

Webpack文档中写到:

代码拆分是Webpack最引人注目的功能之一。 此功能允许您将代码拆分为各种捆绑包,然后可以按需或并行加载。 它可用于实现更小的捆绑并控制资源负载优先级,如果使用得当,可能会对加载时间产生重大影响。

到目前为止,我们只看到了一个入口点 - src / index.js - 和一个输出包 - dist / bundle.js。 当您的应用程序增长时,您需要将其拆分,以便在开始时不会下载整个代码库。 一种好的方法是当代码路径需要时使用Code Splitting和Lazy Loading来按需获取内容。

我们通过添加一个“聊天”模块来证明这一点,该模块在有人与之交互时被提取和初始化。 我们将创建一个新的入口点并为其命名,我们还将使输出的文件名是动态的,因此每个块的文件名都不同。

webpack.config.js

 const path = require('path')

  module.exports = {
  - entry: './src/index.js',
  + entry: {
     app: './src/app.js'
   },
   output: {
  -  filename: 'bundle.js',
  +  filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist')
    },
    ...
  }

src/app.js

import './app.scss'

const button = document.createElement("button")
button.textContent = 'Open chat'
document.body.appendChild(button)

button.onclick = () => {
  import(/* webpackChunkName: "chat" */ "./chat").then(chat => {
    chat.init()
  })
}

src/chat.js

import people from "./people"

export function init() {
  const root = document.createElement("div")
  root.innerHTML = `<p>There are ${people.length} people in the room.</p>`
  document.body.appendChild(root)
}

src/app.scss

button {
  padding: 10px;
  background: #24b47e;
  border: 1px solid rgba(#000, .1);
  border-width: 1px 1px 3px;
  border-radius: 3px;
  font: inherit;
  color: #fff;
  cursor: pointer;
  text-shadow: 0 1px 0 rgba(#000, .3), 0 1px 1px rgba(#000, .2);
}

注意:尽管/ * webpackChunkName * / comment为包提供了名称,但此语法不是特定于Webpack的。 它是动态导入的建议语法,旨在直接在浏览器中支持。

我们运行npm run build并查看它生成的内容:

Asset           Size       Chunks        Chunk Names
chat.bundle.js  377 bytes  0  [emitted]  chat
app.bundle.js   7.65 KiB   1  [emitted]  app

由于我们的条目包已经更改,我们还需要更新它的路径。

dist/index.html

<!doctype html>
  <html>
    <head>
      <title>Hello Webpack</title>
    </head>
    <body>
    <script src="bundle.js"></script>
    <script src="app.bundle.js"></script>
    </body>
  </html>

我们从dist目录启动一个服务器,看看这个实际应用:

cd dist
npx serve

在浏览器中打开http://localhost:5000,看看会发生什么。 最初只获取bundle.js。 单击该按钮时,将导入并初始化聊天模块。

image

只需很少的工作,我们就可以在我们的应用程序中添加动态代码拆分和延迟加载模块。 这是构建高性能Web应用程序的一个很好的起点。

插件

当加载器对单个文件进行转换时,插件可以在更大的代码块上运行。

现在我们正在打包代码,外部模块和静态资产,我们的包将会迅速增长。 插件可以帮助我们以巧妙的方式分割代码并优化生产。

在不知情的情况下,我们实际上已经使用了许多默认的Webpack插件“模式”

开发模式

  • 提供process.env.NODE_ENV,其值为“development”
  • NamedModulesPlugin

生产模式

  • 提供process.env.NODE_ENV,其值为“production”
  • UglifyJsPlugin
  • ModuleConcatenationPlugin
  • NoEmitOnErrorsPlugin

production

在添加其他插件之前,我们首先拆分我们的配置,以便我们可以应用特定于每个环境的插件。

将webpack.config.js重命名为webpack.common.js并添加用于开发和生产的配置文件。

- |- webpack.config.js
+ |- webpack.common.js
+ |- webpack.dev.js
+ |- webpack.prod.js

我们将使用webpack-merge将我们的公共配置与特定环境的配置相结合:

npm install --save-dev webpack-merge

webpack.dev.js

const merge = require('webpack-merge')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  mode: 'development'
})

webpack.prod.js

const merge = require('webpack-merge')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  mode: 'production'
})

package.json

 "scripts": {
    "develop": "webpack --watch --mode development",
    "build": "webpack --mode production"
    "develop": "webpack --watch --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
   },

现在我们可以将特定于开发的插件添加到webpack.dev.js和webpack.prod.js中特定于生产的插件中。

拆分CSS

在使用ExtractTextWebpackPlugin捆绑产品时,最好将CSS与JavaScript分开。

当前的.scss加载器非常适合开发,所以我们将把它们从webpack.common.js移到webpack.dev.js中,并将ExtractTextWebpackPlugin添加到webpack.prod.js中。

npm install --save-dev extract-text-webpack-plugin@4.0.0-beta.0

webpack.common.js

 ...
  module.exports = {
    ...
    module: {
      rules: [
        ...
-       {
-         test: /\.scss$/,
-         use: [
-           {
-             loader: 'style-loader'
-           }, {
-             loader: 'css-loader'
-           }, {
-             loader: 'sass-loader'
-           }
-         ]
-       },
        ...
      ]
    }
  }

webpack.dev.js

 const merge = require('webpack-merge')
  const common = require('./webpack.common.js')

  module.exports = merge(common, {
    mode: 'development',
+   module: {
+     rules: [
+       {
+         test: /\.scss$/,
+         use: [
+           {
+             loader: 'style-loader'
+           }, {
+             loader: 'css-loader'
+           }, {
+             loader: 'sass-loader'
+           }
+         ]
+       }
+     ]
+   }
  })

webpack.prod.js

const merge = require('webpack-merge')
+ const ExtractTextPlugin = require('extract-text-webpack-plugin')
  const common = require('./webpack.common.js')

  module.exports = merge(common, {
    mode: 'production',
+   module: {
+     rules: [
+       {
+         test: /\.scss$/,
+         use: ExtractTextPlugin.extract({
+           fallback: 'style-loader',
+           use: ['css-loader', 'sass-loader']
+         })
+       }
+     ]
+   },
+   plugins: [
+     new ExtractTextPlugin('style.css')
+   ]
  })

我们来比较两个构建脚本的输出:

> npm run develop

Asset           Size      Chunks           Chunk Names
app.bundle.js   28.5 KiB  app   [emitted]  app
chat.bundle.js  1.4 KiB   chat  [emitted]  chat

> npm run build

Asset           Size       Chunks        Chunk Names
chat.bundle.js  375 bytes  0  [emitted]  chat
app.bundle.js   1.82 KiB   1  [emitted]  app
style.css       424 bytes  1  [emitted]  app

现在CSS被从JavaScript包中提取出来用于生产,我们需要从我们的HTML <link>到它。

dist/index.html

<!DOCTYPE html>
  <html>
    <head>
      <meta charset="UTF-8">
      <title>Code Splitting</title>
      <link href="style.css" rel="stylesheet">
    </head>
    <body>
      <script type="text/javascript" src="app.bundle.js"></script>
    </body>
  </html>

这允许在浏览器中并行下载CSS和JavaScript,因此加载速度比单个捆绑包更快。 它还允许在JavaScript完成下载之前显示样式。

生成HTML

每当我们的输出发生变化时,我们必须不断更新index.html以引用新的文件路径。 这正是html-webpack-plugin自动为我们创建的。

我们也可以在每次构建之前同时添加clean-webpack-plugin来清除/dist目录。

npm install --save-dev html-webpack-plugin clean-webpack-plugin

webpack.common.js

 const path = require('path')
 + const CleanWebpackPlugin = require('clean-webpack-plugin');
 + const HtmlWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    ...
 +  plugins: [
 +    new CleanWebpackPlugin(['dist']),
 +    new HtmlWebpackPlugin({
 +      title: 'My killer app'
 +    })
 +  ]
  }

现在每次我们建立时,dist都会被清除掉。 我们现在也会看到index.html输出,以及我们的入口包的正确路径。

运行npm run develop会产生以下结果:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My killer app</title>
  </head>
  <body>
    <script type="text/javascript" src="app.bundle.js"></script>
  </body>
</html>

而npm run build产生了这个:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My killer app</title>
    <link href="style.css" rel="stylesheet">
  </head>
  <body>
    <script type="text/javascript" src="app.bundle.js"></script>
  </body>
</html>

开发

webpack-dev-server为您提供了一个简单的Web服务器,并为您提供实时重新加载,因此您无需手动刷新页面即可查看更改。

npm install --save-dev webpack-dev-server

package.json

{
    ...
    "scripts": {
  - "develop": "webpack --watch --config webpack.dev.js",
  + "develop": "webpack-dev-server --config webpack.dev.js",
    }
    ...
  }

> npm run develop

 「wds」: Project is running at http://localhost:8080/
 「wds」: webpack output is served from /

在浏览器中打开http://localhost:8080/并对其中一个JavaScript或CSS文件进行更改。 您应该看到它自动构建和刷新。

HotModuleReplacement 热模块替换

HotModuleReplacement插件比Live Reloading更进一步,并在运行时交换模块而无需刷新。 正确配置后,这可以节省开发单页应用程序的大量时间。 如果页面中有很多状态,则可以对组件进行增量更改,只更换和更新已更改的模块。

webpack.dev.js

+ const webpack = require('webpack')
  const merge = require('webpack-merge')
  const common = require('./webpack.common.js')

  module.exports = merge(common, {
    mode: 'development',
+   devServer: {
+     hot: true
+   },
+   plugins: [
+     new webpack.HotModuleReplacementPlugin()
   ],
    ...
  }

现在我们需要从代码中接受更改的模块来重新初始化事物。

src/app.js

 if (module.hot) {
   module.hot.accept()
 }

  ...

注意:启用热模块替换时,module.hot设置为true以进行开发,false设置为生产,因此这些将从捆绑中剥离。

重新启动构建,看看执行以下操作时会发生什么:

  • 单击打开聊天
  • 将新人添加到people.js模块
  • 再次单击“打开聊天”
image

这是发生了什么:

  1. 单击“打开聊天”时,将获取并初始化chat.js模块
  2. HMR检测people.js何时被修改
  3. index.js中的module.hot.accept()会导致替换此条目块加载的所有模块
  4. 再次单击“打开聊天”时,将使用更新模块中的代码运行chat.init()。

CSS替换

让我们将按钮颜色更改为红色,看看会发生什么:

src/app.scss

  button {
    ...
 -  background: #24b47e;
 +  background: red;
    ...
  }
image

现在我们可以看到我们的样式的即时更新,而不会丢失任何状态。这是一个大大改善的开发人员体验!感觉就像未来。

HTTP/2

使用像Webpack这样的module bundler的主要好处之一是它可以帮助您控制资源的构建方式,然后在客户端上获取,从而帮助您提高性能。多年来,连接文件以减少需要在客户端上进行的请求数量被认为是最佳实践。这仍然有效,但HTTP/2现在允许在单个请求中传递多个文件,因此连接不再是银弹。您的应用程序实际上可能会受益于单独缓存许多小文件。然后,客户端可以获取单个已更改的模块,而不必再次获取具有大部分相同内容的整个包。

Webpack的创建者Tobias Koppers撰写了一篇内容丰富的文章,解释了为什么捆绑仍然很重要,即使在HTTP/2时代也是如此。

在Webpack和HTTP / 2上阅读更多相关信息。

给你的话

我希望你已经发现Webpack的这个介绍很有帮助,并且能够开始使用它并产生很大的效果。可能需要一些时间来围绕Webpack的配置,加载器和插件,但了解这个工具如何工作将会有所回报。

Webpack 4的文档目前正在进行中,但实际上很好地组合在一起。我强烈建议您阅读概念和指南以获取更多信息。以下是您可能感兴趣的一些其他主题:

Webpack 4是您选择的module bundler吗?请在下面的评论中告诉我。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容