webpack学习笔记

webpack 基本应用

【注意】只过一遍知识点,不再详细代码演示了。

安装和配置 —— 拆分 dev prod 配置,然后 merge

  • 安装 nodejs

  • 初始化 npm init -y

  • 安装插件 npm i webpack webpack-cli webpack-merge --save-dev

  • 新建 src 及其测试 js 代码(包含 ES6 模块化)

  • 创建配置文件

  • 增加 scripts ,运行

  • 安装 npm i clean-webpack-plugin --save-dev

  • 配置 prod

本地服务和代理

  • 新建 index.html
  • 安装 npm i html-webpack-plugin --save-dev ,并配置
  • 安装 npm i webpack-dev-server --save-dev ,并配置
  • 修改 scriptsdev ,运行

处理 ES6

先引入简单的 babel ,polyfill 以后会单独讲

  • 安装 npm i @babel/core @babel/preset-env babel-loader --save-dev
  • 配置 webpack module
  • 配置 .babelrc
  • 运行 dev

处理样式

基本使用

  • 安装 npm i style-loader css-loader less-loader less --save-dev (注意要安装 less)
  • 配置 webpack module
  • 新建 css less 文件,引入 index.js
  • 运行 dev

postcss

  • 安装 npm i postcss-loader autoprefixer -D
  • 新建 postcss.config.js
  • 配置 webpack module ,增加 postcss-loader
  • 增加 css transform: rotate(-45deg);
  • 运行 dev

处理图片

考虑 base64 格式

  • 安装 npm i file-loader url-loader --save-dev
  • 分别配置 webpack.dev 和 webpack.prod
  • 新建图片文件,并引入到 js 中,插入页面
  • 运行 dev
  • 运行 build

webpack 题目

webpack 面试题

  • 前端代码为何要进行构架和打包?
  • module chunk bundle 的区别
  • loader 和 plugin 的区别
  • 常用的 loader 和 plugin 有哪些
  • webpack 性能优化(如上)
  • webpack 构建流程简述
  • 如何产出多页,如何产出 lib

babel 面试题

  • babel 和 webpack 的区别
  • babel-runtime 和 babel-polyfill 区别
  • 为何 Proxy 无法 polyfill

webpack 高级应用

多入口

  • 在src目录下新建 other.htmlother.js
  • 修改 entry


    image.png
  • 修改 output


    image.png
  • 修改 HtmlWebpackPlugin


    image.png
  • 运行 dev
  • 运行 build

抽离 & 压缩 css 文件

抽离
把原common里面的css、less配置移到dev下,prod的需要配置module和plugin,如下所示:

  • 安装 npm i mini-css-extract-plugin -D
  • 将之前 common 中的 css 处理,移动到 dev 配置中
  • 配置 prod (配置 module ,配置 plugin)


    image.png

    image.png
  • 运行 build:dist目录下抽离出对应的css文件


    image.png

压缩

  • 安装 npm i terser-webpack-plugin optimize-css-assets-webpack-plugin -D
  • 配置 prod

抽离公共代码

场景:一般是公共模块或者第三方模块,多处引用如果不提取,可能会存在代码重复冗余。
common和dev基本不用改动,dev能快速用即可,提取可能还稍显麻烦了

  • 配置 splitChunks
    image.png

    image.png

test:/node-module/,表示会进行匹配。比如lodash会安装到node-module里面,当匹配到就符合这个条件
minSize:表示大小限制。比如有的文件很小就几行代码,直接引用即可没必要抽取,所以需要限制一下
minChunks:表示最少复用几次。比如第三方模块,属于可能会在多处引用的情况,所以引用超过一次就提取;但公共模块可能需要2次,如果只在某个地方用到1次就没必要提取

  • 修改 HtmlWebpackPlugin 中的 chunks 。重要!!!


    image.png
  • 安装 lodash npm i lodash --save 做第三方模块的测试,引用 lodash

  • 运行 build

懒加载

webpack不需要做配置,只需要import引入即可

  • 增加 dynamic-data.js 并动态引入

    image.png

  • 运行 dev 查看效果(看加载 js)
    [图片上传中...(image.png-241244-1709365460497-0)]
    1.5s之后出现0.js文件

  • 运行 build 看打包效果

至此,可以总结一下:

  • module:就是js的模块化webpack支持commonJS、ES6等模块化规范,简单来说就是你通过import语句引入的代码。
  • chunk: chunk是webpack根据功能拆分出来的,包含三种情况:
    • 你的项目入口(entry)
    • 通过import()动态引入的代码
    • 通过splitChunks拆分出来的代码
    • (chunk包含着module,可能是一对多也可能是一对一)
  • bundle:bundle是webpack打包之后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出。

处理 React 和 vue

处理react:在.babelrc加上"@babel/preset-env"即可


image.png
  • vue-loader
  • jsx 的编译,babel 已经支持,配置 @babel/preset-react
    image.png

module、chunk、bundle区别:

  • module:各个源码文件,webpack中一切皆模块。比如js、css文件
  • chunk:多模块合并成的,如可以通过entry、 import() 、splitChunk来定义输出
  • bundle: 最终输出的文件

常见 loader 和 plugin

webpack 性能优化

注意:

  • 以下知识点不再一行一行演示
  • 面试的时候也不会问到很细节
  • 但不要死记硬背一个概念,一定要知道它的基本原理和配置方式

打包效率

1)优化 babel-loader

  • babel-loader cache 未修改的不重新编译
  • babel-loader include 明确范围
    在 module下的rules配置
{
    test: /\.js$/,
    use: ['babel-loader?cacheDirectory'], // 开启缓存
    include: path.resolve(__dirname, 'src'), // 明确范围
    // // 排除范围,include 和 exclude 两者选一个即可
    // exclude: path.resolve(__dirname, 'node_modules')
},

2)IgnorePlugin 避免引入哪些模块

以常用的 moment 为例。安装 npm i moment -d 并且 import moment from 'moment' 之后,monent 默认将所有语言的 js 都加载进来,使得打包文件过大。可以通过 ignorePlugin 插件忽略 locale 下的语言文件,不打包进来。

plugins: [
    // 忽略 moment 下的 /locale 目录
    new webpack.IgnorePlugin(/\.\/locale/, /moment/)
]
import moment from 'moment'
import 'moment/locale/zh-cn' // 这里手动引入中文语言包
moment.locale('zh-cn')

noParse 避免重复打包

module.noParse 配置项可以让 Webpack 忽略对部分没采用模块化的文件的递归解析处理,这样做的好处是能提高构建性能。 原因是一些库,例如 jQuery 、ChartJS, 它们庞大又没有采用模块化标准,让 Webpack 去解析这些文件耗时又没有意义。

module.exports = {
  module: {
    // 独完整的 `react.min.js` 文件就没有采用模块化
    // 忽略对 `react.min.js` 文件的递归解析处理
    noParse: [/react\.min\.js$/],
  },
};

.min.js基本都是一家模块化处理过的,不需要重新再打包

两者对比一下:

  • IgnorePlugin 直接不引入,代码中不存在
  • noParse 引入,但不再打包编译。比如xxx.min

happyPack 多进程打包

【注意】大型项目,构建速度明显变慢时,作用才能明显。否则,反而会有副作用。

webpack 是基于 nodejs 运行,nodejs 是单线程的,happyPack 可以开启多个进程来进行构建,发挥多核 CPU 的优势。

const path = require('path')
const HappyPack = require('happypack')

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        // 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
        use: ['happypack/loader?id=babel'],
        exclude: path.resolve(__dirname, 'node_modules')
      }
    ]
  },
  plugins: [
    new HappyPack({
      // 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
      id: 'babel',
      // 如何处理 .js 文件,用法和 Loader 配置中一样
      loaders: ['babel-loader?cacheDirectory'],
      // ... 其它配置项
    })
  ]
}

ParallelUglifyPlugin 多进程压缩 js

webpack 默认用内置的 uglifyJS 压缩 js 代码。
大型项目压缩 js 代码时,也可能会慢。可以开启多进程压缩,和 happyPack 同理。

const path = require('path')
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

module.exports = {
  plugins: [
    // 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
    new ParallelUglifyPlugin({
      // 传递给 UglifyJS 的参数
      // (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
      uglifyJS: {
        output: {
          beautify: false, // 最紧凑的输出
          comments: false, // 删除所有的注释
        },
        compress: {
          // 在UglifyJs删除没有用到的代码时不输出警告
          warnings: false,
          // 删除所有的 `console` 语句,可以兼容ie浏览器
          drop_console: true,
          // 内嵌定义了但是只用到一次的变量
          collapse_vars: true,
          // 提取出出现多次但是没有定义成变量去引用的静态值
          reduce_vars: true,
        }
      },
    }),
  ],
};

适用场景:项目较大,打包较慢,开启多进程能提高速度。项目较小的情况下,不建议,
因为相对而言进程有开销,反而降低了打包速度

自动刷新

watch 默认关闭。但 webpack-dev-server 和 webpack-dev-middleware 里 Watch 模式默认开启。

先验证下 webpack 是否能默认自动刷新页面 ???

module.export = {
  watch: true, // 开启监听,默认为 false
  // 注意,开启监听之后,webpack-dev-server 会自动开启刷新浏览器!!!

  // 监听配置
  watchOptions: {
    ignored: /node_modules/, // 忽略哪些
    // 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
    // 默认为 300ms
    aggregateTimeout: 300,
    // 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
    // 默认每隔1000毫秒询问一次
    poll: 1000
  }
}

自动更新

image.png

一般开启devServer就会自动更新(页面会自动刷新)

热更新

  • 速度更慢
  • 网页当前的状态会丢失,如 input 输入的文字,图片要重新加载,vuex 和 redux 中的数据

操作步骤

  • 把现有的 watch 注释掉
  • 增加以下代码
  • 修改 css less 实验 —— 热替换生效
  • 修改 js 实验 —— 热替换不生效
const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');

module.exports = {
  entry:{
    // 为每个入口都注入代理客户端
    index:[
        'webpack-dev-server/client?http://localhost:8080/',
        'webpack/hot/dev-server',
        path.join(srcPath, 'index.js')
    ],
    // other 先不改了
  },
  plugins: [
    // 该插件的作用就是实现模块热替换,实际上当启动时带上 `--hot` 参数,会注入该插件,生成 .hot-update.json 文件。
    new HotModuleReplacementPlugin(),
  ],
  devServer:{
    // 告诉 DevServer 要开启模块热替换模式
    hot: true,
  }  
};

js 热替换不生效,是因为我们要自己增加代码逻辑。

// 增加,开启热更新之后的代码逻辑
if (module.hot) {
    module.hot.accept(['./math'], () => {
        const sumRes = sum(10, 20)
        console.log('sumRes in hot', sumRes)
    })
}

最后,热替换切勿用于 prod 环境!!!

DllPlugin

Dll 动态链接库,其中可以包含给其他模块调用的函数和数据。

要给 Web 项目构建接入动态链接库的思想,需要完成以下事情:

  • 把网页依赖的基础模块抽离出来,打包到一个个单独的动态链接库中去。一个动态链接库中可以包含多个模块。
  • 当需要导入的模块存在于某个动态链接库中时,这个模块不能被再次被打包,而是去动态链接库中获取。
  • 页面依赖的所有动态链接库需要被加载。

为什么给 Web 项目构建接入动态链接库的思想后,会大大提升构建速度呢?

  • 前端依赖于第三方库 vue react
  • 其特点是:体积大,构建速度慢,版本升级慢
  • 同一个版本,只需要编译一次,之后直接引用即可 —— 不用每次重复构建,提高构建速度

???????
Webpack 已经内置了对动态链接库的支持,需要通过2个内置的插件接入,它们分别是:

  • DllPlugin 插件:打包出 dll 文件
  • DllReferencePlugin 插件:使用 dll 文件

打包出 dll 的过程

  • 增加 webpack.dll.js
  • 修改 package.json scripts "dll": "webpack --config build/webpack.dll.js"
  • npm run dll 并查看输出结果

使用 dll

  • 引入 DllReferencePlugin
  • babel-loader 中排除 node_modules
  • 配置 new DllReferencePlugin({...})
  • index.html 中引入 react.dll.js
  • 运行 dev

总结 - 提高构建效率的方法

哪些可用于线上,哪些用于线下

  • 优化 babel-loader(可用于线上)

  • IgnorePlugin 避免引入哪些模块(可用于线上)

  • noParse 避免重复打包(可用于线上)

  • happyPack 多进程打包(可用于线上)

  • ParallelUglifyPlugin 多进程压缩 js(可用于线上)

  • 自动刷新(仅开发环境)

  • 热更新(仅开发环境)

  • DllPlugin(仅开发环境)

webpack 性能优化

产出代码优化

使用 production

  • 开启压缩代码
  • 开启 tree shaking:用来去除无用代码的代码(必须是 ES6 Module 语法才行)

ES6 Module 和 commonjs 的区别

  • ES6 Module 是静态引入,编译时引入
  • commonjs 是动态引入,执行时引入
// commonjs
let apiList = require('../config/api.js')
if (isDev) {
    // 可以动态引入,执行时引入
    apiList = require('../config/api_dev.js')
}
import apiList from '../config/api.js'
if (isDev) {
    // 编译时报错,只能静态引入
    import apiList from '../config/api_dev.js'
}

小图片 base64 编码

image.png

bundle 加 hash

image.png

使用 CDN加速

1、配置 publicPath


image.png

产物dist是cdn开头:
image.png

2、将产物地址上传到cdn,成功后就能访问了

提取公共改代码

懒加载

使用production

开启mode:production

scope hosting 将多个函数合并到一个函数中

作用:代码体积更小、创建函数作用域更少、代码可读性更好
使用前后的对比,使用的好处、
优化前:
源码:


image.png

优化前打包后产物:


image.png

会各自创建函数作用域,代码量较大

优化后打包产物:


image.png

体积更小

配置如下:

//1.引入插件
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')

module.exports = {
  resolve: {
    // 针对 Npm 中的第三方模块优先采用 jsnext:main 中指向的 ES6 模块化语法的文件
    mainFields: ['jsnext:main', 'browser', 'main']
  },
  plugins: [
    // 开启 Scope Hoisting
    new ModuleConcatenationPlugin(),
  ]
}

同时,考虑到 Scope Hoisting 依赖源码需采用 ES6 模块化语法,还需要配置 mainFields。因为大部分 Npm 中的第三方库采用了 CommonJS 语法,但部分库会同时提供 ES6 模块化的代码,为了充分发挥 Scope Hoisting 的作用。

webpack 原理和二次开发

这部分是面试的加分项,大家不要深究细节。即便面试被问到,实际工作中应用的概率也非常小。
webpack 本身就是一个工具,只是用来打包构建。而且发展多年已经成熟完善,日常的开发场景都能满足。


webpack 构建流程

几个核心概念

  • Entry:入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
  • Module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。
  • Chunk:代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。
  • Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
  • Plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。

Webpack 的构建流程可以分为以下三大阶段:

  • 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
  • 编译:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
  • 输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。

开发 loader

less-loader 为例,回顾一下使用规则

{
    test: /\.less$/,
    // 注意顺序
    loader: ['style-loader', 'css-loader', 'less-loader']
}

所以,loader 的作用:

  • 一个代码转换器,将 less 代码转换为 css 代码
  • 再例如 vue-template-compiler
  • 再例如 babel 编译 jsx

所以一个 loader 的基本开发模式:

const less = require('node-less')
module.exports = function(source) {
  // source 即 less 代码,需要返回 css 代码
  return less(source)
}

以上是 loader 的基本开发方式,实际开发中可能还会有更多要求

  • 支持 options ,如 url-loader 的使用
  • 支持异步 loader
  • 缓存策略
// options
const loaderUtils = require('loader-utils')
module.exports = function(source) {
  // 获取 loader 的 options
  const options = loaderUtils.getOptions(this)
  return source // 示例,直接返回 source 了
}
// 异步 loader
module.exports = function(source) {
    // 使用异步 loader
    const callback = this.async()
    // 执行异步函数,如读取文件
    someAsyncFn(source, function(err, result, sourceMaps, ast) {
        // 通过 callback 返回异步执行后的结果
        callback(err, result, sourceMaps, ast)
    })
}
// 缓存
module.exports = function(source) {
  // 关闭该 Loader 的缓存功能(webpack 默认开启缓存)
  this.cacheable(false)
  return source
}

babel

本节主要解决以下问题,相信很多同学都很懵

  • babel-polyfill —— 7.4 之后弃用,推荐直接使用 corejs 和 regenerator ??
  • babel-runtime

初始化环境

  • 安装 babel 插件 npm i @babel/cli @babel/core @babel/preset-env -D
  • 新建 .babelrc,配置 preset-env
  • 新建 src/index.js ,写一个箭头函数
  • 运行 npx babel src/index.js ,看结果

使用 polyfill

什么是 babel-polyfill

  • 什么是 polyfill ?—— 即一个补丁,引入以兼容新 API(注意不是新语法,如箭头函数),如搜索“Object.keys polyfill” 和 “Promise polyfill”

  • core-js 集合了所有新 API 的 polyfill 。https://github.com/zloirock/core-js

  • regenerator 是 generator 的 polyfill 。 https://github.com/facebook/regenerator

  • babel-polyfill 即 core-js 和 regenerator 的集合,它只做了一层封装而已。

基本使用

  • src/index.js 中写一个 Promise,打包看结果
  • npm install --save @babel/polyfill 【注意】要 --save
  • 然后引入 import '@babel/polyfill'
  • 再打包,看结果
    • 解释:babel 仅仅是处理 ES6 语法,并不关心模模块化的事情。模块化归 webpack 管理
    • 全部引入 polyfill ,体积很大

按需加载

  • 新增 "useBuiltIns": "usage" (注意要改写 preset 的 json 结构)
  • 删掉入口的 import '@babel/polyfill'
  • 再打包,看结果
    • 提示选择 core-js 的版本,增加 "corejs": 3
    • 只引入了 promise 的 polyfill

使用 runtime

babel-polyfill 的问题 —— 会污染全局变量

  • 如果是一个网站或者系统,无碍
  • 如果是做一个第三方工具,给其他系统使用,则会有问题
  • 不能保证其他系统会用 Promise 和 includes 做什么,即便他们用错了,那你也不能偷偷的给更改了
// 源代码
Promise.resolve(100).then(data => data);
[10, 20, 30].includes(20);

// 结果 —— 可以看到,Promise 和 includes 都未改动,因为以注入全局变量了
"use strict";
require("core-js/modules/es.array.includes.js");
require("core-js/modules/es.object.to-string.js");
require("core-js/modules/es.promise.js");

Promise.resolve(100).then(function (data) {
  return data;
});
[10, 20, 30].includes(20);

使用 babel-runtime


image.png
  • npm install --save-dev @babel/plugin-transform-runtime
  • npm install --save @babel/runtime,注意是 --save
  • 配置 "plugins": ["@babel/plugin-transform-runtime"]
    • 其中 "corejs": 3, v3 支持 API 如数组 includes ,v2.x 不支持
  • 删掉 "useBuiltIns": "usage"
  • 运行代码

总结

  • babel-polyfill 是什么,core-js 是什么
    babel-polyfill是core-js和regenerate的合集,但目前已经废弃

  • babel-polyfill 按需加载的配置

  • babel-polyfill 和 babel-runtime 的不同应用场景

前端为什么要进行打包和构建?
代码层面:
1)体积更小(Tree-Sharking、压缩、合并,加载更快)
2)编译高级语言或语法(TS、ES6+、模块化、scss)
3)兼容性和错误检查(Polyfill、postcss、eslint)

工程化/团队规范方面:
统一高效的开发环境
统一的构建流程和产出标准
集成公司构建规范(提测、上线等)

loader和plugin的区别?
loader模块转换器,如less-》css
plugin扩展插件,如HtmlWebpackPlugin

常用loader和plugin有哪些?

babel和webpack的区别?

  • babel-js新语法编译工具,不关心模块化
    -webpack-打包工具,是多个loader、plugin的集合

如何产出一个lib?

参考dll,
image.png

babel-polyfill和babel-runtime的区别
-babel-polyfill会污染全局,而babel-runtime不会污染全局
产出第三方lib要用babel-runtime

webpack如何实现懒加载
import()

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

推荐阅读更多精彩内容