如何使用webpack搭建一個多入口多出口的前端工程


1. 多入口配置

首先我們來看下webpack入口怎么配置:

module.exports = {

    entry: {
        'index' : '../src/index.js'
    }

}

多入口只需要配置多個key:value對即可,key就是你要發(fā)布的包名,主要為了后面的outpu準(zhǔn)備,value就是你入口文件所在的位置

那么問題來了,是不是我每次加一個入口就要手動去添加呢?這樣也太不智能了吧霞幅!

別急,不就是個讀取目錄嗎量瓜,glob可以辦到呀司恳,glob是一個node插件,可以幫助我們按照一定規(guī)則讀取文件的路徑绍傲,我們就可以根據(jù)一定的規(guī)則構(gòu)建出entry了扔傅。

var glob = requir('glob')

var getEntry = function() {
  var entry = {}
  var files = glob.sync(path.resolve(__dirname, '../src/*/index.js'))//你的入口文件相對于當(dāng)前的路徑
  files.forEach(file => {
    var key = file.split('/').splice(-2, 1)[0]
    entry[key] = file
  })
  return entry
}

module.exports = {
  entry: getEntry()
}

這樣就解決了我們的多入口問題了

2. 多出口配置

對于多出口webpack還是比較容易實現(xiàn)的,畢竟我們在entry下功夫拿到了key

{
  output: {
    filename: '[name]/js/vender.[hash].js',
    path: path.resolve(__dirname, '../dist')
  }
}

是不是很簡單,此時你已經(jīng)可以使用這套配置再加上常規(guī)的module配置開發(fā)多入口多出的js項目了铅鲤,但是距離真正的spa項目還差的遠(yuǎn)划提,因為你沒有HTML呀,也不能自動把打包好的js編譯到HTML文件中呀邢享,而且你要想本地調(diào)試鹏往,你得有個服務(wù)器吧,watch太麻煩得刷新頁面骇塘,你得有個HMR支持吧...是不是說到這已經(jīng)懵了伊履,別急別急后面還有更多要配置的-。-哈

3. 添加一個HTML模板

webpack有一個插件叫做html-webpack-plugin款违,這個插件可以幫助我們構(gòu)建出你想要的模板唐瀑。配置如下

{
  plugins: [
    new HtmlWebpackPlugin({
      title: 'your title',
      filename: 'index.html',// 模板要往哪發(fā)布
      template: path.resolve(__dirname, '../dist/src/project1/index.html'), // 模板的存放位置
      chunks: [name], // chunks主要用于多入口文件,也就是你引用哪些打包的文件
      hash: true,
      inject: true, // 默認(rèn)值,決定打包的js在html中的位置插爹,默認(rèn)在body底部
    })
  ]
}

這樣我們就配置完了一個單入口的html-webpack-plugin哄辣。

那怎么實現(xiàn)多入口配置呢?答案是有幾個入口就new幾個HtmlWebpackPlugin赠尾,這個是無限制的力穗,但是我們手動去寫是不是太麻煩了,程序員嘛气嫁,一定要學(xué)會使用工具呀当窗!上面我們構(gòu)建entry的時候就得到了入口文件,那我們是不是也可以仿照上面的方法也構(gòu)建一個HtmlWebpackPlugin的實例數(shù)組呢寸宵?為了不浪費性能崖面,我們決定就在一個循環(huán)里搞定好了!嗯說干就干梯影,上代碼N自薄!

var getEntry = function() {
  var entry = {}
  var htmlPlugins = []
  var files = glob.sync(path.resolve(__dirname, '../src/*/index.js'))//你的入口文件相對于當(dāng)前的路徑
  files.forEach(file => {
    var key = file.split('/').splice(-2, 1)[0]
    entry[key] = file
    htmlPlugins.push(new HtmlWebpackPlugin({
      title: key,
      filename: path.resolve(__dirname, '../dist/' + key + '/index.html'),
      template: path.resolve(__dirname, '../src/' + key + '/index.html'),
      chunk: [key],
      hash: true,
      inject: true
    }))
  })
  return {entry, htmlPlugins}
}
var {htmlPlugins} = getEntry()
module.exports = {
  plugins: [
    ...htmlPlugins
  ]
}

此時你激動地去運行了一下 webpack 確實包如期打好了光酣,但是我們發(fā)現(xiàn)里面的js文件貌似路徑不太對疏遏,恭喜,你又學(xué)到了一個新東西救军,就是publicPath,它是用來為你的資源指定正確的存放位置的倘零。

{
  output: {
    ....
    publicPath: '../', // 因為我們的html和js打包的路徑都是基于頁面應(yīng)用project1或者project2路徑打包的唱遭,所以需要跳出一級,引用路徑才能正常訪問到呈驶。
  }
}

現(xiàn)在HTML拷泽,JS都具備了,我們就差一個本地服務(wù)就能玩起來了,別急webpack早就幫你安排好了server

4. webpack-dev-server配置

webpack-dev-server可以幫助我們在本地建立一個靜態(tài)資源服務(wù)器司致,并且為我們提供了HMR熱重載技術(shù)拆吆,使得我們不需要刷新就可以進(jìn)行組件更新和調(diào)試,像極了你在devtools中直接修改代碼的樣子脂矫!是不是很爽

devServer : {
  contentBase: path.resolve(__dirname, '../dist'),// 指定了服務(wù)器根目錄
  compress: true, // 啟用gzip壓縮
  hot: true, // 啟用HMR
  port: 8080, 
  publicPath: '/', // 如何訪問資源總是以'/'開頭,需要根據(jù)我們的output決定如何配置
}

此時運行 webpack serve枣耀,就可以在http://localhost:8080/project1/index.html訪問我們打包好的HTML應(yīng)用了。

5. 基礎(chǔ)module配置

此時我們具備了開發(fā)多入口多出口的工程化能力了庭再,那我們就可以開發(fā)react或者vue了捞奕,只要具有相應(yīng)的loader就好了,在webpack中拄轻,每一個文件就是module颅围,而loader其實就是一個函數(shù),可以流式地幫助我們處理文件恨搓。

module: {
  rules: [
    {
      test: '/\.(js|jsx|ts|tsx)$/',
      exclude: '/node_modules/',
      loader: 'babel-loader'
    },
    {
      test: '/\.vue$/',
      loader: 'vue-loader'
    },
    {
      test: '/\.(css | scss)$/',
      use: [
        'style-loader',
        'css-loader',
        'sass-loader'
      ]
    }院促,
    {
        test: '/\.(jpg|png|jpeg|gif|eot|svg|ttf|woff|woff2)$/',
        loader: 'url-loader'
    }
  ]
},
plugins: [          
  new VueLoaderPlugin(),
]

好了,現(xiàn)在你真的可以去開發(fā)項目了8А一疯!但是配置還有很大的優(yōu)化空間

6. 打包優(yōu)化
  1. 抽離公共依賴

    我們知道,我們一個頁面有時候可能會引用多個vue實例化root組件夺姑,但是基于現(xiàn)在的打包情況憎乙,我們只能把vue或者react打包到應(yīng)用程序中,整個包體積就會很大绰疤,那可不可以多個包公用一個依賴呢享完,比如我把依賴用CDN的方式引入可以嗎,當(dāng)然废膘,webpack在入口文件遞歸每一個module的時候竹海,就會組成一個構(gòu)建樹,構(gòu)建樹就叫做chunk丐黄,chunk給我們組織好了依賴關(guān)系斋配,所以我們也可以通知webpack,樹上的哪些東西我們不需要灌闺,webpack就會為我們進(jìn)行tree-shaking把這個依賴從樹上搖下來艰争。

    通過配置externals就可以滿足需求

    {
      externals: {
        'vue' : 'Vue',
         'vue-router': 'VueRouter',  // key就是你import進(jìn)來的包的位置,value就是暴露的包名
         
      }
    }
    

    然后我們在html模板中引入對應(yīng)的js即可桂对,值得注意的是引用時也可以找到性能最佳的引用甩卓,例如vue.runtime.min.js,要比我們的開發(fā)時構(gòu)建時esm體積小蕉斜,性能更好逾柿,因為去掉了模板編譯的代碼缀棍,體積小了30%左右,前提是你的vue文件已經(jīng)被vue-loader編譯為了render函數(shù)机错,才可以使用這個運行時爬范。

  2. 抽離CSS

    現(xiàn)在我們的CSS是被style-loader直接打包到了項目js文件中,style-loader主要是幫助我們把css對象轉(zhuǎn)化成字符串并且通過DOM的api追加到head中的弱匪,大概原理是這樣青瀑,細(xì)節(jié)其實還是有很多考量的。

    那我們?yōu)槭裁匆殡xCSS痢法,單純就是想把CSS拿出來狱窘?在JS中放著不也能正常顯示麼,費這勁干嘛财搁。這就要說到瀏覽器的解析機(jī)制了蘸炸,瀏覽器在得到HTML文檔的時候,從上到下HTML解析為DOM尖奔,然后生成DOM樹搭儒,同時解析CSS的時候生成CSSOM,生成CSSOM樹提茁,DOM樹和CSSOM樹結(jié)合構(gòu)建出了render樹淹禾,render樹確定好以后會根據(jù)dom和定位構(gòu)建出布局layout,最后進(jìn)行paint繪制茴扁,當(dāng)遇到內(nèi)聯(lián)的JS時铃岔,就會阻塞DOM的解析,而且當(dāng)上面的CSS沒有加載完畢的時候峭火,JavaScript 執(zhí)行將暫停毁习,直至 CSSOM 就緒(因為JS能查詢修改CSSOM和DOM)。重新追加的內(nèi)嵌的CSS同理會讓CSSOM不得不重排或重繪卖丸,所以我們找到了問題纺且,我們的JS打包時設(shè)置了output.inject = true,所以就會把代碼放到body后面稍浆,此時DOM已經(jīng)解析完了载碌,但是我們在JS代碼執(zhí)行的時候,又加了一大段CSS到head中衅枫,那瀏覽器不得不重新走一遍構(gòu)建流程嫁艇,不僅如此,用戶在沒有CSS的時候为鳄,看到的就是一塊幾乎沒有意義的首屏裳仆,CSS 是阻塞渲染的資源。需要將它盡早孤钦、盡快地下載到客戶端歧斟,以便縮短首次渲染的時間。所以我們要把CSS放在head中偏形,并且讓他盡快下載并加裝到瀏覽器參與構(gòu)建CSSOM與渲染静袖,而不是等到最后。

說了這么多題外話俊扭,我們看看如何抽離队橙,只需要在loader中新增一個loader處理,并且一定要把style-loader移除萨惑,因為你抽離了CSS為單獨文件捐康,就沒有document對象了,style-loader不僅沒有意義而且還會報錯庸蔼。

   {
     test: /.(sc|c)ss$/,
       use: [
         MiniCssExtractPlugin.loader,  
         'css-loader',
         'postcss-loader',
         'sass-loader',
       ]
   },
   .......
   
   plugins: [
     new MiniCssExtractPlugin({filename: '[name]/css/[name].css'}) // path就是默認(rèn)的output.path
   ]
  1. 清除dist的冗余包
plugins: [
   new CleanWebpackPlugin() // 默認(rèn)清除output.path
 ]
  1. 我們發(fā)現(xiàn)解总,開發(fā)的久了,項目里的入口文件越來越多姐仅,打包也變得非常慢花枫,有時候我們只想打包某一個指定目錄下的入口文件,并不想全部打包掏膏,有辦法操作嗎劳翰?

    當(dāng)然,我們可以在node中讀取環(huán)境變量馒疹,從而根據(jù)命令行下發(fā)的指令去構(gòu)建entry佳簸,這樣就可以定點打包了。

    具體流程大概是這樣:

    命令行輸入打包目錄 => 通過環(huán)境變量取到目錄名=>根據(jù)目錄名在遍歷時過濾不需要的entry=>構(gòu)建出需要打包的entry=>正常打包即可

    代碼實現(xiàn)也比較容易

    1.命令行增加后綴: npm run dev --run:project1
    2.process.env.npm_config_argv['original'] 獲取到命令值數(shù)組
    3.截取得到project1颖变,具體怎么截取很簡單這里就不羅列了
    4.寫入到環(huán)境變量生均,process.env.BUILD_DIR = 'project1'
    5.let buildFile = process.env.BUILD_DIR
      files = files.filter(file => file.indexOf(buildFile)!==-1)
     過濾一個新的文件集合
    6.進(jìn)行構(gòu)建entry
    7.正常打包
    
  2. 定義一些環(huán)境變量方便webpack打包時候注入環(huán)境變量到我們的js

   plugins: [
  new DefineWebpackPlugins({
        'process.env.ENV_NAME' : "\"" + process.env.ENV_NAME + "\"",
         'process.env.BASE_URL' : "\"" + process.env.BASE_URL + "\"""
     })
   ]
  1. 把錯誤信息從控制臺或者命令行輸出到頁面
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');

devServer: {
  ...
   quiet: true,
   overlay: {
     errors: true
   },
  ...
}
  
plugins: [
    new FriendlyErrorsWebpackPlugin(),
]
  1. 寫一個webpack插件
  • 一個 JavaScript 函數(shù)或 JavaScript 類,用于承接這個插件模塊的所有邏輯悼做;
  • 在它原型上定義的 apply 方法疯特,會在安裝插件時被調(diào)用,并被 webpack compiler 調(diào)用一次肛走;
  • 指定一個觸及到 webpack 本身的事件鉤子漓雅,即下文會提及的 hooks,用于特定時機(jī)處理額外的邏輯朽色;
  • 對 webpack 實例內(nèi)部做一些操作處理邻吞;
  • 在功能流程完成后可以調(diào)用 webpack 提供的回調(diào)函數(shù);

插件就是一個函數(shù)葫男,函數(shù)的原型上要有一個apply方法抱冷,webpack會調(diào)用該方法,并且把webpack實例傳入apply中梢褐,webpack執(zhí)行會有一些事件鉤子供我們處理旺遮,并且我們可以對實例內(nèi)部進(jìn)行操作赵讯,還可以執(zhí)行回調(diào)。

根據(jù)插件所能觸及到的 event hook(事件鉤子)耿眉,對其進(jìn)行分類边翼。每個 event hook 都被預(yù)先定義為 synchronous hook(同步), asynchronous hook(異步), waterfall hook(瀑布), parallel hook(并行),而在 webpack 內(nèi)部會使用 call/callAsync 方法調(diào)用這些 hook鸣剪。 —— webpack 中文文檔

如果你不進(jìn)一步追究组底,那么按照如下所示的方式對不同鉤子進(jìn)行 tap 處理即可,其中 tap 方法用于同步處理筐骇,異步方式則可以調(diào)用 tapAsync 方法或 tapPromise 方法债鸡。

class HelloWorldPlugin {
    apply(compiler) {
        console.log(compiler, 'Hello World before!')
        compiler.hooks.done.tap(
            'HelloAsyncPlugin', 
            compilation => {
              console.log("webpack build done.");
            }
        );
    }
}

module.exports = HelloWorldPlugin

最后

webpack為我們提供了十分便利的模塊化開發(fā),推動了前端的組件化工程化铛纬,依靠流程式和豐富的拓展插件配置的打包模式厌均,在目前來說仍是被各大cli主流使用的打包工具,但是隨著ES MODULE的興起與落地饺鹃,基于snowpack的vite已經(jīng)面世莫秆,并且效率更高,速度更快悔详,且更為易用镊屎,有空我會專門出一期vite的新手配置教程供大家參考,最后放上webpack的全部配置與本地的架構(gòu)目錄茄螃。

架構(gòu)
build
    plugins
    
    env_config
        index.js
    webpack.base.conf.js
src
    project1
        index.js
        index.html
    project2
        index.js
        index.html

npm腳本命令

  "scripts": {
    "lint": "eslint --ext .js,.vue src",
    "start:dev": "cross-env NODE_ENV=development webpack serve --config build/webpack.base.conf.js",
    "watch": "npm run dev --m dev",
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "cross-env NODE_ENV=development webpack --config build/webpack.base.conf.js --progress"
  },
環(huán)境變量配置
var env = {
    dev: {
        env_name: 'dev',
        base_url: '//dev.webpack.com/'
    },
    test: {
        env_name: 'test',
        base_url: '//test.webpack.com/'
    },
    build: {
        env_name: 'build',
        base_url: '//build.webpack.com/'
    }
}
let type = JSON.parse(process.env.npm_config_argv)['original'].pop()
let buildDir = ''
if(type.includes('--run:')) {
    buildDir = type.replace('--run:', '')
} 
if('dev_test_build'.includes(type)) {
    type = type.split(':').pop()
} else {
    type = 'dev'
}
console.log(type, 'type')
console.log(process.env.npm_config_argv, 'process.env.npm_config_argv')
process.env.ENV_NAME = env[type || 'dev'].env_name
process.env.BASE_URL = env[type || 'dev'].base_url
process.env.BUILD_DIR = buildDir
module.exports.env = env
webpack.base.conf.js
const path = require('path')
const glob = require('glob')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const {
    VueLoaderPlugin
} = require('vue-loader')

const HelloWorldPlugin = require('./plugins/hell-world-plugin') 

const vueLoaderConfig = require('./vue_config/vue-loader.conf')
require('./env_config/index.js')
function getEntry() {
    let entrys = {}
    let htmlPlugins = []
    let files = glob.sync(path.resolve(__dirname, '../src/*/index.js'))
    let buildFile = process.env.BUILD_DIR
    files = files.filter(file => file.indexOf(buildFile)!==-1)
    files.forEach(item => {
        const name = item.split('/').splice(-2, 1)[0]
        entrys[name] = item
        htmlPlugins.push(new HtmlWebpackPlugin({
            title: name,
            filename: path.resolve(__dirname, '../dist/' + name + '/index.html'),
            chunks: [name],
            hash: true,
            template: path.resolve(__dirname, '../src/' + name + '/index.html'),
            inject: true
        }))
    })
    return {entrys, htmlPlugins}
}
const {entrys, htmlPlugins} = getEntry()
module.exports = (env, option) => {

    return {
        entry: entrys,
        resolve: {
            alias: {
                'vue$': 'vue/dist/vue.esm.js',
                '@' : path.resolve(__dirname, '../src/')
            },
            extensions: ['.js', '.jsx', '.vue']
        },
        output: {
            filename: '[name]/js/vendor.[hash].js',
            path: path.resolve(__dirname, '../dist'),
            publicPath: '../'
        },
        devServer: {
            contentBase: path.resolve(__dirname, '../dist'),
            compress: true,
            port: 8080,
            hot: true,
            quiet: true,
            overlay: {
                errors: true
            },
            publicPath: '/',
            after() {
                console.log(`服務(wù)已啟動---運行在 http://localhost:8080`)
            }
        },
        module: {
            rules: [
                {
                    test: /\.(js|jsx|ts|tsx)$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader',
                    options: {
                        // presets: ['@babel/preset-env', '@babel/preset-react', '@vue/babel-preset-jsx']
                    }
                },
                {
                    test: /.vue$/,
                    loader: 'vue-loader',
                    // options: vueLoaderConfig
                },
                {
                    test: /.(sc|c)ss$/,
                    use: [
                        MiniCssExtractPlugin.loader,  
                        'css-loader',
                        'postcss-loader',
                        'sass-loader',
                        ]
                },
                {
                  test: /\.(jpg|png|jpeg|gif|eot|svg|ttf|woff|woff2)$/,
                  loader: "url-loader"
                },
            ]
        },
        plugins: [
            new CleanWebpackPlugin(),
            new FriendlyErrorsWebpackPlugin(),
            new VueLoaderPlugin(),
            new webpack.DefinePlugin({
                'process.env.ENV_NAME' : "\"" + process.env.ENV_NAME + "\"",
                'process.env.BASE_URL' : "\"" + process.env.BASE_URL + "\""
            }),
            new HelloWorldPlugin({options: true}),
            new MiniCssExtractPlugin({filename: '[name]/css/[name].css'}),
            ...htmlPlugins
            
        ],
        externals: {
            'vue': 'Vue',
            'vue-router': "VueRouter"
        }

    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缝驳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子归苍,更是在濱河造成了極大的恐慌用狱,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拼弃,死亡現(xiàn)場離奇詭異夏伊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)吻氧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門溺忧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人盯孙,你說我怎么就攤上這事鲁森。” “怎么了振惰?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵歌溉,是天一觀的道長。 經(jīng)常有香客問我骑晶,道長痛垛,這世上最難降的妖魔是什么草慧? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮榜晦,結(jié)果婚禮上冠蒋,老公的妹妹穿的比我還像新娘羽圃。我一直安慰自己乾胶,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布朽寞。 她就那樣靜靜地躺著识窿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪脑融。 梳的紋絲不亂的頭發(fā)上喻频,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機(jī)與錄音肘迎,去河邊找鬼甥温。 笑死,一個胖子當(dāng)著我的面吹牛妓布,可吹牛的內(nèi)容都是我干的姻蚓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼匣沼,長吁一口氣:“原來是場噩夢啊……” “哼狰挡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起释涛,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤加叁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后唇撬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體它匕,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年窖认,在試婚紗的時候發(fā)現(xiàn)自己被綠了豫柬。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡耀态,死狀恐怖轮傍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情首装,我是刑警寧澤创夜,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站仙逻,受9級特大地震影響驰吓,放射性物質(zhì)發(fā)生泄漏涧尿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一檬贰、第九天 我趴在偏房一處隱蔽的房頂上張望姑廉。 院中可真熱鬧,春花似錦翁涤、人聲如沸桥言。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽号阿。三九已至,卻和暖如春鸳粉,著一層夾襖步出監(jiān)牢的瞬間扔涧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工届谈, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留枯夜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓艰山,卻偏偏與公主長得像湖雹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子程剥,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

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